blob: 5b8da5f6ff682a485b0c505c30829aa4126b19c3 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/webui/identity_internals_ui.h"
#include <set>
#include <string>
#include "base/bind.h"
#include "base/i18n/time_formatting.h"
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/extensions/api/identity/identity_api.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "content/public/browser/web_ui.h"
#include "content/public/browser/web_ui_controller.h"
#include "content/public/browser/web_ui_data_source.h"
#include "content/public/browser/web_ui_message_handler.h"
#include "extensions/browser/extension_registry.h"
#include "google_apis/gaia/gaia_auth_fetcher.h"
#include "google_apis/gaia/gaia_constants.h"
#include "grit/browser_resources.h"
#include "ui/base/l10n/l10n_util.h"
namespace {
// Properties of the Javascript object representing a token.
const char kExtensionId[] = "extensionId";
const char kExtensionName[] = "extensionName";
const char kScopes[] = "scopes";
const char kStatus[] = "status";
const char kTokenExpirationTime[] = "expirationTime";
const char kAccessToken[] = "accessToken";
// RevokeToken message parameter offsets.
const int kRevokeTokenExtensionOffset = 0;
const int kRevokeTokenTokenOffset = 1;
class IdentityInternalsTokenRevoker;
// Class acting as a controller of the chrome://identity-internals WebUI.
class IdentityInternalsUIMessageHandler : public content::WebUIMessageHandler {
public:
IdentityInternalsUIMessageHandler();
~IdentityInternalsUIMessageHandler() override;
// Ensures that a proper clean up happens after a token is revoked. That
// includes deleting the |token_revoker|, removing the token from Identity API
// cache and updating the UI that the token is gone.
void OnTokenRevokerDone(IdentityInternalsTokenRevoker* token_revoker);
// WebUIMessageHandler implementation.
void RegisterMessages() override;
private:
// Gets the name of an extension referred to by |token_cache_key| as a string.
const std::string GetExtensionName(
const extensions::ExtensionTokenKey& token_cache_key);
// Gets a list of scopes specified in |token_cache_key| and returns a pointer
// to a ListValue containing the scopes. The caller gets ownership of the
// returned object.
base::ListValue* GetScopes(
const extensions::ExtensionTokenKey& token_cache_key);
// Gets a localized status of the access token in |token_cache_value|.
const base::string16 GetStatus(
const extensions::IdentityTokenCacheValue& token_cache_value);
// Gets a string representation of an expiration time of the access token in
// |token_cache_value|.
const std::string GetExpirationTime(
const extensions::IdentityTokenCacheValue& token_cache_value);
// Converts a pair of |token_cache_key| and |token_cache_value| to a
// DictionaryValue object with corresponding information in a localized and
// readable form and returns a pointer to created object. Caller gets the
// ownership of the returned object.
base::DictionaryValue* GetInfoForToken(
const extensions::ExtensionTokenKey& token_cache_key,
const extensions::IdentityTokenCacheValue& token_cache_value);
// Gets all of the tokens stored in IdentityAPI token cache and returns them
// to the caller using Javascript callback function
// |identity_internals.returnTokens()|.
void GetInfoForAllTokens(const base::ListValue* args);
// Initiates revoking of the token, based on the extension ID and token
// passed as entries in the |args| list. Updates the caller of completion
// using Javascript callback function |identity_internals.tokenRevokeDone()|.
void RevokeToken(const base::ListValue* args);
// A vector of token revokers that are currently revoking tokens.
ScopedVector<IdentityInternalsTokenRevoker> token_revokers_;
};
// Handles the revoking of an access token and helps performing the clean up
// after it is revoked by holding information about the access token and related
// extension ID.
class IdentityInternalsTokenRevoker : public GaiaAuthConsumer {
public:
// Revokes |access_token| from extension with |extension_id|.
// |profile| is required for its request context. |consumer| will be
// notified when revocation succeeds via |OnTokenRevokerDone()|.
IdentityInternalsTokenRevoker(const std::string& extension_id,
const std::string& access_token,
Profile* profile,
IdentityInternalsUIMessageHandler* consumer);
~IdentityInternalsTokenRevoker() override;
// Returns the access token being revoked.
const std::string& access_token() const { return access_token_; }
// Returns the ID of the extension the access token is related to.
const std::string& extension_id() const { return extension_id_; }
// GaiaAuthConsumer implementation.
void OnOAuth2RevokeTokenCompleted() override;
private:
// An object used to start a token revoke request.
GaiaAuthFetcher fetcher_;
// An ID of an extension the access token is related to.
const std::string extension_id_;
// The access token to revoke.
const std::string access_token_;
// An object that needs to be notified once the access token is revoked.
IdentityInternalsUIMessageHandler* consumer_; // weak.
DISALLOW_COPY_AND_ASSIGN(IdentityInternalsTokenRevoker);
};
IdentityInternalsUIMessageHandler::IdentityInternalsUIMessageHandler() {}
IdentityInternalsUIMessageHandler::~IdentityInternalsUIMessageHandler() {}
void IdentityInternalsUIMessageHandler::OnTokenRevokerDone(
IdentityInternalsTokenRevoker* token_revoker) {
extensions::IdentityAPI* api =
extensions::IdentityAPI::GetFactoryInstance()->Get(
Profile::FromWebUI(web_ui()));
// API can be null in incognito, but then we shouldn't be in this function.
// This case is handled because this is called from a renderer process
// which could conceivably be compromised.
CHECK(api);
// Remove token from the cache.
api->EraseCachedToken(token_revoker->extension_id(),
token_revoker->access_token());
// Update view about the token being removed.
base::ListValue result;
result.AppendString(token_revoker->access_token());
web_ui()->CallJavascriptFunctionUnsafe("identity_internals.tokenRevokeDone",
result);
// Erase the revoker.
ScopedVector<IdentityInternalsTokenRevoker>::iterator iter =
std::find(token_revokers_.begin(), token_revokers_.end(), token_revoker);
DCHECK(iter != token_revokers_.end());
token_revokers_.erase(iter);
}
const std::string IdentityInternalsUIMessageHandler::GetExtensionName(
const extensions::ExtensionTokenKey& token_cache_key) {
const extensions::ExtensionRegistry* registry =
extensions::ExtensionRegistry::Get(Profile::FromWebUI(web_ui()));
const extensions::Extension* extension =
registry->enabled_extensions().GetByID(token_cache_key.extension_id);
if (!extension)
return std::string();
return extension->name();
}
base::ListValue* IdentityInternalsUIMessageHandler::GetScopes(
const extensions::ExtensionTokenKey& token_cache_key) {
base::ListValue* scopes_value = new base::ListValue();
for (std::set<std::string>::const_iterator
iter = token_cache_key.scopes.begin();
iter != token_cache_key.scopes.end(); ++iter) {
scopes_value->AppendString(*iter);
}
return scopes_value;
}
const base::string16 IdentityInternalsUIMessageHandler::GetStatus(
const extensions::IdentityTokenCacheValue& token_cache_value) {
switch (token_cache_value.status()) {
case extensions::IdentityTokenCacheValue::CACHE_STATUS_ADVICE:
// Fallthrough to NOT FOUND case, as ADVICE is short lived.
case extensions::IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND:
return l10n_util::GetStringUTF16(
IDS_IDENTITY_INTERNALS_TOKEN_NOT_FOUND);
case extensions::IdentityTokenCacheValue::CACHE_STATUS_TOKEN:
return l10n_util::GetStringUTF16(
IDS_IDENTITY_INTERNALS_TOKEN_PRESENT);
}
NOTREACHED();
return base::string16();
}
const std::string IdentityInternalsUIMessageHandler::GetExpirationTime(
const extensions::IdentityTokenCacheValue& token_cache_value) {
return base::UTF16ToUTF8(base::TimeFormatFriendlyDateAndTime(
token_cache_value.expiration_time()));
}
base::DictionaryValue* IdentityInternalsUIMessageHandler::GetInfoForToken(
const extensions::ExtensionTokenKey& token_cache_key,
const extensions::IdentityTokenCacheValue& token_cache_value) {
base::DictionaryValue* token_data = new base::DictionaryValue();
token_data->SetString(kExtensionId, token_cache_key.extension_id);
token_data->SetString(kExtensionName, GetExtensionName(token_cache_key));
token_data->Set(kScopes, GetScopes(token_cache_key));
token_data->SetString(kStatus, GetStatus(token_cache_value));
token_data->SetString(kAccessToken, token_cache_value.token());
token_data->SetString(kTokenExpirationTime,
GetExpirationTime(token_cache_value));
return token_data;
}
void IdentityInternalsUIMessageHandler::GetInfoForAllTokens(
const base::ListValue* args) {
base::ListValue results;
extensions::IdentityAPI::CachedTokens tokens;
// The API can be null in incognito.
extensions::IdentityAPI* api =
extensions::IdentityAPI::GetFactoryInstance()->Get(
Profile::FromWebUI(web_ui()));
if (api)
tokens = api->GetAllCachedTokens();
for (extensions::IdentityAPI::CachedTokens::const_iterator
iter = tokens.begin(); iter != tokens.end(); ++iter) {
results.Append(GetInfoForToken(iter->first, iter->second));
}
web_ui()->CallJavascriptFunctionUnsafe("identity_internals.returnTokens",
results);
}
void IdentityInternalsUIMessageHandler::RegisterMessages() {
web_ui()->RegisterMessageCallback("identityInternalsGetTokens",
base::Bind(&IdentityInternalsUIMessageHandler::GetInfoForAllTokens,
base::Unretained(this)));
web_ui()->RegisterMessageCallback("identityInternalsRevokeToken",
base::Bind(&IdentityInternalsUIMessageHandler::RevokeToken,
base::Unretained(this)));
}
void IdentityInternalsUIMessageHandler::RevokeToken(
const base::ListValue* args) {
std::string extension_id;
std::string access_token;
args->GetString(kRevokeTokenExtensionOffset, &extension_id);
args->GetString(kRevokeTokenTokenOffset, &access_token);
token_revokers_.push_back(new IdentityInternalsTokenRevoker(
extension_id, access_token, Profile::FromWebUI(web_ui()), this));
}
IdentityInternalsTokenRevoker::IdentityInternalsTokenRevoker(
const std::string& extension_id,
const std::string& access_token,
Profile* profile,
IdentityInternalsUIMessageHandler* consumer)
: fetcher_(this, GaiaConstants::kChromeSource,
profile->GetRequestContext()),
extension_id_(extension_id),
access_token_(access_token),
consumer_(consumer) {
DCHECK(consumer_);
fetcher_.StartRevokeOAuth2Token(access_token);
}
IdentityInternalsTokenRevoker::~IdentityInternalsTokenRevoker() {}
void IdentityInternalsTokenRevoker::OnOAuth2RevokeTokenCompleted() {
consumer_->OnTokenRevokerDone(this);
}
} // namespace
IdentityInternalsUI::IdentityInternalsUI(content::WebUI* web_ui)
: content::WebUIController(web_ui) {
// chrome://identity-internals source.
content::WebUIDataSource* html_source =
content::WebUIDataSource::Create(chrome::kChromeUIIdentityInternalsHost);
// Localized strings
html_source->AddLocalizedString("tokenCacheHeader",
IDS_IDENTITY_INTERNALS_TOKEN_CACHE_TEXT);
html_source->AddLocalizedString("accessToken",
IDS_IDENTITY_INTERNALS_ACCESS_TOKEN);
html_source->AddLocalizedString("extensionName",
IDS_IDENTITY_INTERNALS_EXTENSION_NAME);
html_source->AddLocalizedString("extensionId",
IDS_IDENTITY_INTERNALS_EXTENSION_ID);
html_source->AddLocalizedString("tokenStatus",
IDS_IDENTITY_INTERNALS_TOKEN_STATUS);
html_source->AddLocalizedString("expirationTime",
IDS_IDENTITY_INTERNALS_EXPIRATION_TIME);
html_source->AddLocalizedString("scopes",
IDS_IDENTITY_INTERNALS_SCOPES);
html_source->AddLocalizedString("revoke",
IDS_IDENTITY_INTERNALS_REVOKE);
html_source->SetJsonPath("strings.js");
// Required resources
html_source->AddResourcePath("identity_internals.css",
IDR_IDENTITY_INTERNALS_CSS);
html_source->AddResourcePath("identity_internals.js",
IDR_IDENTITY_INTERNALS_JS);
html_source->SetDefaultResource(IDR_IDENTITY_INTERNALS_HTML);
content::WebUIDataSource::Add(Profile::FromWebUI(web_ui), html_source);
web_ui->AddMessageHandler(new IdentityInternalsUIMessageHandler());
}
IdentityInternalsUI::~IdentityInternalsUI() {}