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