| // 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/strings/utf_string_conversions.h" |
| #include "base/values.h" |
| #include "chrome/browser/extensions/api/identity/identity_api.h" |
| #include "chrome/browser/extensions/extension_service.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_system.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) { |
| // Remove token from the cache. |
| extensions::IdentityAPI::GetFactoryInstance() |
| ->Get(Profile::FromWebUI(web_ui())) |
| ->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()->CallJavascriptFunction("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) { |
| ExtensionService* extension_service = extensions::ExtensionSystem::Get( |
| Profile::FromWebUI(web_ui()))->extension_service(); |
| const extensions::Extension* extension = |
| extension_service->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 = |
| extensions::IdentityAPI::GetFactoryInstance() |
| ->Get(Profile::FromWebUI(web_ui())) |
| ->GetAllCachedTokens(); |
| for (extensions::IdentityAPI::CachedTokens::const_iterator |
| iter = tokens.begin(); iter != tokens.end(); ++iter) { |
| results.Append(GetInfoForToken(iter->first, iter->second)); |
| } |
| |
| web_ui()->CallJavascriptFunction("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() {} |