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 29 namespace { 30 31 // Properties of the Javascript object representing a token. 32 const char kExtensionId[] = "extensionId"; 33 const char kExtensionName[] = "extensionName"; 34 const char kScopes[] = "scopes"; 35 const char kStatus[] = "status"; 36 const char kTokenExpirationTime[] = "expirationTime"; 37 const char kAccessToken[] = "accessToken"; 38 39 // RevokeToken message parameter offsets. 40 const int kRevokeTokenExtensionOffset = 0; 41 const int kRevokeTokenTokenOffset = 1; 42 43 class IdentityInternalsTokenRevoker; 44 45 // Class acting as a controller of the chrome://identity-internals WebUI. 46 class 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. 104 class 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 137 IdentityInternalsUIMessageHandler::IdentityInternalsUIMessageHandler() {} 138 139 IdentityInternalsUIMessageHandler::~IdentityInternalsUIMessageHandler() {} 140 141 void 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 162 const 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 173 base::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 184 const 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 200 const 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 206 base::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 220 void 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 235 void 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 244 void 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 254 IdentityInternalsTokenRevoker::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 268 IdentityInternalsTokenRevoker::~IdentityInternalsTokenRevoker() {} 269 270 void IdentityInternalsTokenRevoker::OnOAuth2RevokeTokenCompleted() { 271 consumer_->OnTokenRevokerDone(this); 272 } 273 274 } // namespace 275 276 IdentityInternalsUI::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 314 IdentityInternalsUI::~IdentityInternalsUI() {} 315