Home | History | Annotate | Download | only in identity
      1 // Copyright (c) 2012 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/extensions/api/identity/identity_api.h"
      6 
      7 #include <set>
      8 #include <string>
      9 #include <utility>
     10 #include <vector>
     11 
     12 #include "base/lazy_instance.h"
     13 #include "base/prefs/pref_service.h"
     14 #include "base/strings/string_number_conversions.h"
     15 #include "base/strings/stringprintf.h"
     16 #include "base/values.h"
     17 #include "chrome/browser/app_mode/app_mode_utils.h"
     18 #include "chrome/browser/browser_process.h"
     19 #include "chrome/browser/chrome_notification_types.h"
     20 #include "chrome/browser/extensions/extension_function_dispatcher.h"
     21 #include "chrome/browser/extensions/extension_service.h"
     22 #include "chrome/browser/extensions/extension_system.h"
     23 #include "chrome/browser/policy/browser_policy_connector.h"
     24 #include "chrome/browser/profiles/profile.h"
     25 #include "chrome/browser/signin/profile_oauth2_token_service.h"
     26 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
     27 #include "chrome/browser/signin/signin_global_error.h"
     28 #include "chrome/common/extensions/api/identity.h"
     29 #include "chrome/common/extensions/api/identity/oauth2_manifest_handler.h"
     30 #include "chrome/common/pref_names.h"
     31 #include "chrome/common/url_constants.h"
     32 #include "extensions/browser/event_router.h"
     33 #include "extensions/common/extension.h"
     34 #include "google_apis/gaia/gaia_urls.h"
     35 #include "url/gurl.h"
     36 
     37 #if defined(OS_CHROMEOS)
     38 #include "chrome/browser/chromeos/login/user_manager.h"
     39 #include "chrome/browser/chromeos/settings/device_oauth2_token_service.h"
     40 #include "chrome/browser/chromeos/settings/device_oauth2_token_service_factory.h"
     41 #include "google_apis/gaia/gaia_constants.h"
     42 #endif
     43 
     44 namespace extensions {
     45 
     46 namespace identity_constants {
     47 const char kInvalidClientId[] = "Invalid OAuth2 Client ID.";
     48 const char kInvalidScopes[] = "Invalid OAuth2 scopes.";
     49 const char kAuthFailure[] = "OAuth2 request failed: ";
     50 const char kNoGrant[] = "OAuth2 not granted or revoked.";
     51 const char kUserRejected[] = "The user did not approve access.";
     52 const char kUserNotSignedIn[] = "The user is not signed in.";
     53 const char kInteractionRequired[] = "User interaction required.";
     54 const char kInvalidRedirect[] = "Did not redirect to the right URL.";
     55 const char kOffTheRecord[] = "Identity API is disabled in incognito windows.";
     56 const char kPageLoadFailure[] = "Authorization page could not be loaded.";
     57 
     58 const int kCachedIssueAdviceTTLSeconds = 1;
     59 }  // namespace identity_constants
     60 
     61 namespace {
     62 
     63 static const char kChromiumDomainRedirectUrlPattern[] =
     64     "https://%s.chromiumapp.org/";
     65 
     66 }  // namespace
     67 
     68 namespace identity = api::identity;
     69 
     70 IdentityGetAuthTokenFunction::IdentityGetAuthTokenFunction()
     71     : should_prompt_for_scopes_(false),
     72       should_prompt_for_signin_(false) {}
     73 
     74 IdentityGetAuthTokenFunction::~IdentityGetAuthTokenFunction() {}
     75 
     76 bool IdentityGetAuthTokenFunction::RunImpl() {
     77   if (GetProfile()->IsOffTheRecord()) {
     78     error_ = identity_constants::kOffTheRecord;
     79     return false;
     80   }
     81 
     82   scoped_ptr<identity::GetAuthToken::Params> params(
     83       identity::GetAuthToken::Params::Create(*args_));
     84   EXTENSION_FUNCTION_VALIDATE(params.get());
     85   bool interactive = params->details.get() &&
     86       params->details->interactive.get() &&
     87       *params->details->interactive;
     88 
     89   should_prompt_for_scopes_ = interactive;
     90   should_prompt_for_signin_ = interactive;
     91 
     92   const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
     93 
     94   // Check that the necessary information is present in the manifest.
     95   oauth2_client_id_ = GetOAuth2ClientId();
     96   if (oauth2_client_id_.empty()) {
     97     error_ = identity_constants::kInvalidClientId;
     98     return false;
     99   }
    100 
    101   if (oauth2_info.scopes.size() == 0) {
    102     error_ = identity_constants::kInvalidScopes;
    103     return false;
    104   }
    105 
    106   ProfileOAuth2TokenService* token_service =
    107       ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile());
    108 
    109   std::set<std::string> scopes(oauth2_info.scopes.begin(),
    110                                oauth2_info.scopes.end());
    111   token_key_.reset(new ExtensionTokenKey(
    112       GetExtension()->id(), token_service->GetPrimaryAccountId(), scopes));
    113 
    114   // Balanced in CompleteFunctionWithResult|CompleteFunctionWithError
    115   AddRef();
    116 
    117 #if defined(OS_CHROMEOS)
    118   if (chromeos::UserManager::Get()->IsLoggedInAsKioskApp() &&
    119       g_browser_process->browser_policy_connector()->IsEnterpriseManaged()) {
    120     StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE);
    121     return true;
    122   }
    123 #endif
    124 
    125   if (!HasLoginToken()) {
    126     if (!should_prompt_for_signin_) {
    127       error_ = identity_constants::kUserNotSignedIn;
    128       Release();
    129       return false;
    130     }
    131     // Display a login prompt.
    132     StartSigninFlow();
    133   } else {
    134     StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE);
    135   }
    136 
    137   return true;
    138 }
    139 
    140 void IdentityGetAuthTokenFunction::CompleteFunctionWithResult(
    141     const std::string& access_token) {
    142   SetResult(new base::StringValue(access_token));
    143   SendResponse(true);
    144   Release();  // Balanced in RunImpl.
    145 }
    146 
    147 void IdentityGetAuthTokenFunction::CompleteFunctionWithError(
    148     const std::string& error) {
    149   error_ = error;
    150   SendResponse(false);
    151   Release();  // Balanced in RunImpl.
    152 }
    153 
    154 void IdentityGetAuthTokenFunction::StartSigninFlow() {
    155   // All cached tokens are invalid because the user is not signed in.
    156   IdentityAPI* id_api =
    157       extensions::IdentityAPI::GetFactoryInstance()->GetForProfile(
    158           GetProfile());
    159   id_api->EraseAllCachedTokens();
    160   // Display a login prompt. If the subsequent mint fails, don't display the
    161   // login prompt again.
    162   should_prompt_for_signin_ = false;
    163   ShowLoginPopup();
    164 }
    165 
    166 void IdentityGetAuthTokenFunction::StartMintTokenFlow(
    167     IdentityMintRequestQueue::MintType type) {
    168   mint_token_flow_type_ = type;
    169 
    170   // Flows are serialized to prevent excessive traffic to GAIA, and
    171   // to consolidate UI pop-ups.
    172   IdentityAPI* id_api =
    173       extensions::IdentityAPI::GetFactoryInstance()->GetForProfile(
    174           GetProfile());
    175 
    176   if (!should_prompt_for_scopes_) {
    177     // Caller requested no interaction.
    178 
    179     if (type == IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE) {
    180       // GAIA told us to do a consent UI.
    181       CompleteFunctionWithError(identity_constants::kNoGrant);
    182       return;
    183     }
    184     if (!id_api->mint_queue()->empty(
    185             IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE, *token_key_)) {
    186       // Another call is going through a consent UI.
    187       CompleteFunctionWithError(identity_constants::kNoGrant);
    188       return;
    189     }
    190   }
    191   id_api->mint_queue()->RequestStart(type, *token_key_, this);
    192 }
    193 
    194 void IdentityGetAuthTokenFunction::CompleteMintTokenFlow() {
    195   IdentityMintRequestQueue::MintType type = mint_token_flow_type_;
    196 
    197   const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
    198   std::set<std::string> scopes(oauth2_info.scopes.begin(),
    199                                oauth2_info.scopes.end());
    200 
    201   extensions::IdentityAPI::GetFactoryInstance()
    202       ->GetForProfile(GetProfile())
    203       ->mint_queue()
    204       ->RequestComplete(type, *token_key_, this);
    205 }
    206 
    207 void IdentityGetAuthTokenFunction::StartMintToken(
    208     IdentityMintRequestQueue::MintType type) {
    209   const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
    210   IdentityAPI* id_api =
    211       IdentityAPI::GetFactoryInstance()->GetForProfile(GetProfile());
    212   IdentityTokenCacheValue cache_entry = id_api->GetCachedToken(*token_key_);
    213   IdentityTokenCacheValue::CacheValueStatus cache_status =
    214       cache_entry.status();
    215 
    216   if (type == IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE) {
    217     switch (cache_status) {
    218       case IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND:
    219 #if defined(OS_CHROMEOS)
    220         // Always force minting token for ChromeOS kiosk app.
    221         if (chromeos::UserManager::Get()->IsLoggedInAsKioskApp()) {
    222           gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE;
    223           if (g_browser_process->browser_policy_connector()->
    224                   IsEnterpriseManaged()) {
    225             StartDeviceLoginAccessTokenRequest();
    226           } else {
    227             StartLoginAccessTokenRequest();
    228           }
    229           return;
    230         }
    231 #endif
    232 
    233         if (oauth2_info.auto_approve)
    234           // oauth2_info.auto_approve is protected by a whitelist in
    235           // _manifest_features.json hence only selected extensions take
    236           // advantage of forcefully minting the token.
    237           gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE;
    238         else
    239           gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE;
    240         StartLoginAccessTokenRequest();
    241         break;
    242 
    243       case IdentityTokenCacheValue::CACHE_STATUS_TOKEN:
    244         CompleteMintTokenFlow();
    245         CompleteFunctionWithResult(cache_entry.token());
    246         break;
    247 
    248       case IdentityTokenCacheValue::CACHE_STATUS_ADVICE:
    249         CompleteMintTokenFlow();
    250         should_prompt_for_signin_ = false;
    251         issue_advice_ = cache_entry.issue_advice();
    252         StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE);
    253         break;
    254     }
    255   } else {
    256     DCHECK(type == IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE);
    257 
    258     if (cache_status == IdentityTokenCacheValue::CACHE_STATUS_TOKEN) {
    259       CompleteMintTokenFlow();
    260       CompleteFunctionWithResult(cache_entry.token());
    261     } else {
    262       ShowOAuthApprovalDialog(issue_advice_);
    263     }
    264   }
    265 }
    266 
    267 void IdentityGetAuthTokenFunction::OnMintTokenSuccess(
    268     const std::string& access_token, int time_to_live) {
    269   IdentityTokenCacheValue token(access_token,
    270                                 base::TimeDelta::FromSeconds(time_to_live));
    271   IdentityAPI::GetFactoryInstance()
    272       ->GetForProfile(GetProfile())
    273       ->SetCachedToken(*token_key_, token);
    274 
    275   CompleteMintTokenFlow();
    276   CompleteFunctionWithResult(access_token);
    277 }
    278 
    279 void IdentityGetAuthTokenFunction::OnMintTokenFailure(
    280     const GoogleServiceAuthError& error) {
    281   CompleteMintTokenFlow();
    282 
    283   switch (error.state()) {
    284     case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS:
    285     case GoogleServiceAuthError::SERVICE_ERROR:
    286     case GoogleServiceAuthError::ACCOUNT_DELETED:
    287     case GoogleServiceAuthError::ACCOUNT_DISABLED:
    288       extensions::IdentityAPI::GetFactoryInstance()
    289           ->GetForProfile(GetProfile())
    290           ->ReportAuthError(error);
    291       if (should_prompt_for_signin_) {
    292         // Display a login prompt and try again (once).
    293         StartSigninFlow();
    294         return;
    295       }
    296       break;
    297     default:
    298       // Return error to caller.
    299       break;
    300   }
    301 
    302   CompleteFunctionWithError(
    303       std::string(identity_constants::kAuthFailure) + error.ToString());
    304 }
    305 
    306 void IdentityGetAuthTokenFunction::OnIssueAdviceSuccess(
    307     const IssueAdviceInfo& issue_advice) {
    308   IdentityAPI::GetFactoryInstance()
    309       ->GetForProfile(GetProfile())
    310       ->SetCachedToken(*token_key_,
    311                        IdentityTokenCacheValue(issue_advice));
    312   CompleteMintTokenFlow();
    313 
    314   should_prompt_for_signin_ = false;
    315   // Existing grant was revoked and we used NO_FORCE, so we got info back
    316   // instead. Start a consent UI if we can.
    317   issue_advice_ = issue_advice;
    318   StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE);
    319 }
    320 
    321 void IdentityGetAuthTokenFunction::SigninSuccess() {
    322   StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE);
    323 }
    324 
    325 void IdentityGetAuthTokenFunction::SigninFailed() {
    326   CompleteFunctionWithError(identity_constants::kUserNotSignedIn);
    327 }
    328 
    329 void IdentityGetAuthTokenFunction::OnGaiaFlowFailure(
    330     GaiaWebAuthFlow::Failure failure,
    331     GoogleServiceAuthError service_error,
    332     const std::string& oauth_error) {
    333   CompleteMintTokenFlow();
    334   std::string error;
    335 
    336   switch (failure) {
    337     case GaiaWebAuthFlow::WINDOW_CLOSED:
    338       error = identity_constants::kUserRejected;
    339       break;
    340 
    341     case GaiaWebAuthFlow::INVALID_REDIRECT:
    342       error = identity_constants::kInvalidRedirect;
    343       break;
    344 
    345     case GaiaWebAuthFlow::SERVICE_AUTH_ERROR:
    346       error = std::string(identity_constants::kAuthFailure) +
    347           service_error.ToString();
    348       break;
    349 
    350     case GaiaWebAuthFlow::OAUTH_ERROR:
    351       error = MapOAuth2ErrorToDescription(oauth_error);
    352       break;
    353 
    354     case GaiaWebAuthFlow::LOAD_FAILED:
    355       error = identity_constants::kPageLoadFailure;
    356       break;
    357 
    358     default:
    359       NOTREACHED() << "Unexpected error from gaia web auth flow: " << failure;
    360       error = identity_constants::kInvalidRedirect;
    361       break;
    362   }
    363 
    364   CompleteFunctionWithError(error);
    365 }
    366 
    367 void IdentityGetAuthTokenFunction::OnGaiaFlowCompleted(
    368     const std::string& access_token,
    369     const std::string& expiration) {
    370 
    371   int time_to_live;
    372   if (!expiration.empty() && base::StringToInt(expiration, &time_to_live)) {
    373     IdentityTokenCacheValue token_value(
    374         access_token, base::TimeDelta::FromSeconds(time_to_live));
    375     IdentityAPI::GetFactoryInstance()
    376         ->GetForProfile(GetProfile())
    377         ->SetCachedToken(*token_key_, token_value);
    378   }
    379 
    380   CompleteMintTokenFlow();
    381   CompleteFunctionWithResult(access_token);
    382 }
    383 
    384 void IdentityGetAuthTokenFunction::OnGetTokenSuccess(
    385     const OAuth2TokenService::Request* request,
    386     const std::string& access_token,
    387     const base::Time& expiration_time) {
    388   login_token_request_.reset();
    389   StartGaiaRequest(access_token);
    390 }
    391 
    392 void IdentityGetAuthTokenFunction::OnGetTokenFailure(
    393     const OAuth2TokenService::Request* request,
    394     const GoogleServiceAuthError& error) {
    395   login_token_request_.reset();
    396   OnGaiaFlowFailure(GaiaWebAuthFlow::SERVICE_AUTH_ERROR, error, std::string());
    397 }
    398 
    399 #if defined(OS_CHROMEOS)
    400 void IdentityGetAuthTokenFunction::StartDeviceLoginAccessTokenRequest() {
    401   chromeos::DeviceOAuth2TokenServiceFactory::Get(
    402       base::Bind(&IdentityGetAuthTokenFunction::DidGetTokenService,
    403                  this));
    404 }
    405 
    406 void IdentityGetAuthTokenFunction::DidGetTokenService(
    407     chromeos::DeviceOAuth2TokenService* service) {
    408   if (!service) {
    409     CompleteFunctionWithError(identity_constants::kUserNotSignedIn);
    410     return;
    411   }
    412   // Since robot account refresh tokens are scoped down to [any-api] only,
    413   // request access token for [any-api] instead of login.
    414   OAuth2TokenService::ScopeSet scopes;
    415   scopes.insert(GaiaConstants::kAnyApiOAuth2Scope);
    416   login_token_request_ =
    417       service->StartRequest(service->GetRobotAccountId(),
    418                             scopes,
    419                             this);
    420 }
    421 #endif
    422 
    423 void IdentityGetAuthTokenFunction::StartLoginAccessTokenRequest() {
    424   ProfileOAuth2TokenService* service =
    425       ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile());
    426 #if defined(OS_CHROMEOS)
    427   if (chrome::IsRunningInForcedAppMode()) {
    428     std::string app_client_id;
    429     std::string app_client_secret;
    430     if (chromeos::UserManager::Get()->GetAppModeChromeClientOAuthInfo(
    431            &app_client_id, &app_client_secret)) {
    432       login_token_request_ =
    433           service->StartRequestForClient(service->GetPrimaryAccountId(),
    434                                          app_client_id,
    435                                          app_client_secret,
    436                                          OAuth2TokenService::ScopeSet(),
    437                                          this);
    438       return;
    439     }
    440   }
    441 #endif
    442   login_token_request_ = service->StartRequest(
    443       service->GetPrimaryAccountId(), OAuth2TokenService::ScopeSet(), this);
    444 }
    445 
    446 void IdentityGetAuthTokenFunction::StartGaiaRequest(
    447     const std::string& login_access_token) {
    448   DCHECK(!login_access_token.empty());
    449   mint_token_flow_.reset(CreateMintTokenFlow(login_access_token));
    450   mint_token_flow_->Start();
    451 }
    452 
    453 void IdentityGetAuthTokenFunction::ShowLoginPopup() {
    454   signin_flow_.reset(new IdentitySigninFlow(this, GetProfile()));
    455   signin_flow_->Start();
    456 }
    457 
    458 void IdentityGetAuthTokenFunction::ShowOAuthApprovalDialog(
    459     const IssueAdviceInfo& issue_advice) {
    460   const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
    461   const std::string locale = g_browser_process->local_state()->GetString(
    462       prefs::kApplicationLocale);
    463 
    464   gaia_web_auth_flow_.reset(new GaiaWebAuthFlow(
    465       this, GetProfile(), GetExtension()->id(), oauth2_info, locale));
    466   gaia_web_auth_flow_->Start();
    467 }
    468 
    469 OAuth2MintTokenFlow* IdentityGetAuthTokenFunction::CreateMintTokenFlow(
    470     const std::string& login_access_token) {
    471   const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
    472 
    473   OAuth2MintTokenFlow* mint_token_flow = new OAuth2MintTokenFlow(
    474       GetProfile()->GetRequestContext(),
    475       this,
    476       OAuth2MintTokenFlow::Parameters(login_access_token,
    477                                       GetExtension()->id(),
    478                                       oauth2_client_id_,
    479                                       oauth2_info.scopes,
    480                                       gaia_mint_token_mode_));
    481   return mint_token_flow;
    482 }
    483 
    484 bool IdentityGetAuthTokenFunction::HasLoginToken() const {
    485   ProfileOAuth2TokenService* token_service =
    486       ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile());
    487   return token_service->RefreshTokenIsAvailable(
    488       token_service->GetPrimaryAccountId());
    489 }
    490 
    491 std::string IdentityGetAuthTokenFunction::MapOAuth2ErrorToDescription(
    492     const std::string& error) {
    493   const char kOAuth2ErrorAccessDenied[] = "access_denied";
    494   const char kOAuth2ErrorInvalidScope[] = "invalid_scope";
    495 
    496   if (error == kOAuth2ErrorAccessDenied)
    497     return std::string(identity_constants::kUserRejected);
    498   else if (error == kOAuth2ErrorInvalidScope)
    499     return std::string(identity_constants::kInvalidScopes);
    500   else
    501     return std::string(identity_constants::kAuthFailure) + error;
    502 }
    503 
    504 std::string IdentityGetAuthTokenFunction::GetOAuth2ClientId() const {
    505   const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
    506   std::string client_id = oauth2_info.client_id;
    507 
    508   // Component apps using auto_approve may use Chrome's client ID by
    509   // omitting the field.
    510   if (client_id.empty() && GetExtension()->location() == Manifest::COMPONENT &&
    511       oauth2_info.auto_approve) {
    512     client_id = GaiaUrls::GetInstance()->oauth2_chrome_client_id();
    513   }
    514   return client_id;
    515 }
    516 
    517 IdentityRemoveCachedAuthTokenFunction::IdentityRemoveCachedAuthTokenFunction() {
    518 }
    519 
    520 IdentityRemoveCachedAuthTokenFunction::
    521     ~IdentityRemoveCachedAuthTokenFunction() {
    522 }
    523 
    524 bool IdentityRemoveCachedAuthTokenFunction::RunImpl() {
    525   if (GetProfile()->IsOffTheRecord()) {
    526     error_ = identity_constants::kOffTheRecord;
    527     return false;
    528   }
    529 
    530   scoped_ptr<identity::RemoveCachedAuthToken::Params> params(
    531       identity::RemoveCachedAuthToken::Params::Create(*args_));
    532   EXTENSION_FUNCTION_VALIDATE(params.get());
    533   IdentityAPI::GetFactoryInstance()
    534       ->GetForProfile(GetProfile())
    535       ->EraseCachedToken(GetExtension()->id(), params->details.token);
    536   return true;
    537 }
    538 
    539 IdentityLaunchWebAuthFlowFunction::IdentityLaunchWebAuthFlowFunction() {}
    540 
    541 IdentityLaunchWebAuthFlowFunction::~IdentityLaunchWebAuthFlowFunction() {
    542   if (auth_flow_)
    543     auth_flow_.release()->DetachDelegateAndDelete();
    544 }
    545 
    546 bool IdentityLaunchWebAuthFlowFunction::RunImpl() {
    547   if (GetProfile()->IsOffTheRecord()) {
    548     error_ = identity_constants::kOffTheRecord;
    549     return false;
    550   }
    551 
    552   scoped_ptr<identity::LaunchWebAuthFlow::Params> params(
    553       identity::LaunchWebAuthFlow::Params::Create(*args_));
    554   EXTENSION_FUNCTION_VALIDATE(params.get());
    555 
    556   GURL auth_url(params->details.url);
    557   WebAuthFlow::Mode mode =
    558       params->details.interactive && *params->details.interactive ?
    559       WebAuthFlow::INTERACTIVE : WebAuthFlow::SILENT;
    560 
    561   // Set up acceptable target URLs. (Does not include chrome-extension
    562   // scheme for this version of the API.)
    563   InitFinalRedirectURLPrefix(GetExtension()->id());
    564 
    565   AddRef();  // Balanced in OnAuthFlowSuccess/Failure.
    566 
    567   auth_flow_.reset(new WebAuthFlow(this, GetProfile(), auth_url, mode));
    568   auth_flow_->Start();
    569   return true;
    570 }
    571 
    572 void IdentityLaunchWebAuthFlowFunction::InitFinalRedirectURLPrefixForTest(
    573     const std::string& extension_id) {
    574   InitFinalRedirectURLPrefix(extension_id);
    575 }
    576 
    577 void IdentityLaunchWebAuthFlowFunction::InitFinalRedirectURLPrefix(
    578     const std::string& extension_id) {
    579   if (final_url_prefix_.is_empty()) {
    580     final_url_prefix_ = GURL(base::StringPrintf(
    581         kChromiumDomainRedirectUrlPattern, extension_id.c_str()));
    582   }
    583 }
    584 
    585 void IdentityLaunchWebAuthFlowFunction::OnAuthFlowFailure(
    586     WebAuthFlow::Failure failure) {
    587   switch (failure) {
    588     case WebAuthFlow::WINDOW_CLOSED:
    589       error_ = identity_constants::kUserRejected;
    590       break;
    591     case WebAuthFlow::INTERACTION_REQUIRED:
    592       error_ = identity_constants::kInteractionRequired;
    593       break;
    594     case WebAuthFlow::LOAD_FAILED:
    595       error_ = identity_constants::kPageLoadFailure;
    596       break;
    597     default:
    598       NOTREACHED() << "Unexpected error from web auth flow: " << failure;
    599       error_ = identity_constants::kInvalidRedirect;
    600       break;
    601   }
    602   SendResponse(false);
    603   Release();  // Balanced in RunImpl.
    604 }
    605 
    606 void IdentityLaunchWebAuthFlowFunction::OnAuthFlowURLChange(
    607     const GURL& redirect_url) {
    608   if (redirect_url.GetWithEmptyPath() == final_url_prefix_) {
    609     SetResult(new base::StringValue(redirect_url.spec()));
    610     SendResponse(true);
    611     Release();  // Balanced in RunImpl.
    612   }
    613 }
    614 
    615 IdentityTokenCacheValue::IdentityTokenCacheValue()
    616     : status_(CACHE_STATUS_NOTFOUND) {
    617 }
    618 
    619 IdentityTokenCacheValue::IdentityTokenCacheValue(
    620     const IssueAdviceInfo& issue_advice) : status_(CACHE_STATUS_ADVICE),
    621                                            issue_advice_(issue_advice) {
    622   expiration_time_ = base::Time::Now() + base::TimeDelta::FromSeconds(
    623       identity_constants::kCachedIssueAdviceTTLSeconds);
    624 }
    625 
    626 IdentityTokenCacheValue::IdentityTokenCacheValue(
    627     const std::string& token, base::TimeDelta time_to_live)
    628     : status_(CACHE_STATUS_TOKEN),
    629       token_(token) {
    630   // Remove 20 minutes from the ttl so cached tokens will have some time
    631   // to live any time they are returned.
    632   time_to_live -= base::TimeDelta::FromMinutes(20);
    633 
    634   base::TimeDelta zero_delta;
    635   if (time_to_live < zero_delta)
    636     time_to_live = zero_delta;
    637 
    638   expiration_time_ = base::Time::Now() + time_to_live;
    639 }
    640 
    641 IdentityTokenCacheValue::~IdentityTokenCacheValue() {
    642 }
    643 
    644 IdentityTokenCacheValue::CacheValueStatus
    645     IdentityTokenCacheValue::status() const {
    646   if (is_expired())
    647     return IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND;
    648   else
    649     return status_;
    650 }
    651 
    652 const IssueAdviceInfo& IdentityTokenCacheValue::issue_advice() const {
    653   return issue_advice_;
    654 }
    655 
    656 const std::string& IdentityTokenCacheValue::token() const {
    657   return token_;
    658 }
    659 
    660 bool IdentityTokenCacheValue::is_expired() const {
    661   return status_ == CACHE_STATUS_NOTFOUND ||
    662       expiration_time_ < base::Time::Now();
    663 }
    664 
    665 const base::Time& IdentityTokenCacheValue::expiration_time() const {
    666   return expiration_time_;
    667 }
    668 
    669 IdentityAPI::IdentityAPI(Profile* profile)
    670     : profile_(profile),
    671       account_tracker_(profile) {
    672   account_tracker_.AddObserver(this);
    673 }
    674 
    675 IdentityAPI::~IdentityAPI() {}
    676 
    677 IdentityMintRequestQueue* IdentityAPI::mint_queue() {
    678     return &mint_queue_;
    679 }
    680 
    681 void IdentityAPI::SetCachedToken(const ExtensionTokenKey& key,
    682                                  const IdentityTokenCacheValue& token_data) {
    683   CachedTokens::iterator it = token_cache_.find(key);
    684   if (it != token_cache_.end() && it->second.status() <= token_data.status())
    685     token_cache_.erase(it);
    686 
    687   token_cache_.insert(std::make_pair(key, token_data));
    688 }
    689 
    690 void IdentityAPI::EraseCachedToken(const std::string& extension_id,
    691                                    const std::string& token) {
    692   CachedTokens::iterator it;
    693   for (it = token_cache_.begin(); it != token_cache_.end(); ++it) {
    694     if (it->first.extension_id == extension_id &&
    695         it->second.status() == IdentityTokenCacheValue::CACHE_STATUS_TOKEN &&
    696         it->second.token() == token) {
    697       token_cache_.erase(it);
    698       break;
    699     }
    700   }
    701 }
    702 
    703 void IdentityAPI::EraseAllCachedTokens() {
    704   token_cache_.clear();
    705 }
    706 
    707 const IdentityTokenCacheValue& IdentityAPI::GetCachedToken(
    708     const ExtensionTokenKey& key) {
    709   return token_cache_[key];
    710 }
    711 
    712 const IdentityAPI::CachedTokens& IdentityAPI::GetAllCachedTokens() {
    713   return token_cache_;
    714 }
    715 
    716 void IdentityAPI::ReportAuthError(const GoogleServiceAuthError& error) {
    717   ProfileOAuth2TokenService* token_service =
    718       ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
    719   account_tracker_.ReportAuthError(token_service->GetPrimaryAccountId(), error);
    720 }
    721 
    722 void IdentityAPI::Shutdown() {
    723   account_tracker_.RemoveObserver(this);
    724   account_tracker_.Shutdown();
    725 }
    726 
    727 static base::LazyInstance<ProfileKeyedAPIFactory<IdentityAPI> >
    728     g_factory = LAZY_INSTANCE_INITIALIZER;
    729 
    730 // static
    731 ProfileKeyedAPIFactory<IdentityAPI>* IdentityAPI::GetFactoryInstance() {
    732   return &g_factory.Get();
    733 }
    734 
    735 void IdentityAPI::OnAccountAdded(const AccountIds& ids) {}
    736 
    737 void IdentityAPI::OnAccountRemoved(const AccountIds& ids) {}
    738 
    739 void IdentityAPI::OnAccountSignInChanged(const AccountIds& ids,
    740                                          bool is_signed_in) {
    741   api::identity::AccountInfo account_info;
    742   account_info.id = ids.gaia;
    743 
    744   scoped_ptr<base::ListValue> args =
    745       api::identity::OnSignInChanged::Create(account_info, is_signed_in);
    746   scoped_ptr<Event> event(new Event(
    747       api::identity::OnSignInChanged::kEventName, args.Pass(), profile_));
    748 
    749   ExtensionSystem::Get(profile_)->event_router()->BroadcastEvent(event.Pass());
    750 }
    751 
    752 template <>
    753 void ProfileKeyedAPIFactory<IdentityAPI>::DeclareFactoryDependencies() {
    754   DependsOn(ExtensionSystemFactory::GetInstance());
    755   DependsOn(ProfileOAuth2TokenServiceFactory::GetInstance());
    756 }
    757 
    758 }  // namespace extensions
    759