Home | History | Annotate | Download | only in gaia
      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 "google_apis/gaia/oauth2_mint_token_flow.h"
      6 
      7 #include <string>
      8 #include <vector>
      9 
     10 #include "base/basictypes.h"
     11 #include "base/bind.h"
     12 #include "base/command_line.h"
     13 #include "base/json/json_reader.h"
     14 #include "base/message_loop/message_loop.h"
     15 #include "base/strings/string_number_conversions.h"
     16 #include "base/strings/string_util.h"
     17 #include "base/strings/stringprintf.h"
     18 #include "base/strings/utf_string_conversions.h"
     19 #include "base/values.h"
     20 #include "google_apis/gaia/gaia_urls.h"
     21 #include "google_apis/gaia/google_service_auth_error.h"
     22 #include "net/base/escape.h"
     23 #include "net/url_request/url_fetcher.h"
     24 #include "net/url_request/url_request_context_getter.h"
     25 #include "net/url_request/url_request_status.h"
     26 
     27 using net::URLFetcher;
     28 using net::URLRequestContextGetter;
     29 using net::URLRequestStatus;
     30 
     31 namespace {
     32 
     33 const char kForceValueFalse[] = "false";
     34 const char kForceValueTrue[] = "true";
     35 const char kResponseTypeValueNone[] = "none";
     36 const char kResponseTypeValueToken[] = "token";
     37 
     38 const char kOAuth2IssueTokenBodyFormat[] =
     39     "force=%s"
     40     "&response_type=%s"
     41     "&scope=%s"
     42     "&client_id=%s"
     43     "&origin=%s";
     44 const char kIssueAdviceKey[] = "issueAdvice";
     45 const char kIssueAdviceValueConsent[] = "consent";
     46 const char kAccessTokenKey[] = "token";
     47 const char kConsentKey[] = "consent";
     48 const char kExpiresInKey[] = "expiresIn";
     49 const char kScopesKey[] = "scopes";
     50 const char kDescriptionKey[] = "description";
     51 const char kDetailKey[] = "detail";
     52 const char kDetailSeparators[] = "\n";
     53 const char kError[] = "error";
     54 const char kMessage[] = "message";
     55 
     56 static GoogleServiceAuthError CreateAuthError(const net::URLFetcher* source) {
     57   URLRequestStatus status = source->GetStatus();
     58   if (status.status() == URLRequestStatus::CANCELED) {
     59     return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
     60   }
     61   if (status.status() == URLRequestStatus::FAILED) {
     62     DLOG(WARNING) << "Server returned error: errno " << status.error();
     63     return GoogleServiceAuthError::FromConnectionError(status.error());
     64   }
     65 
     66   std::string response_body;
     67   source->GetResponseAsString(&response_body);
     68   scoped_ptr<base::Value> value(base::JSONReader::Read(response_body));
     69   base::DictionaryValue* response;
     70   if (!value.get() || !value->GetAsDictionary(&response)) {
     71     return GoogleServiceAuthError::FromUnexpectedServiceResponse(
     72         base::StringPrintf(
     73             "Not able to parse a JSON object from a service response. "
     74             "HTTP Status of the response is: %d", source->GetResponseCode()));
     75   }
     76   base::DictionaryValue* error;
     77   if (!response->GetDictionary(kError, &error)) {
     78     return GoogleServiceAuthError::FromUnexpectedServiceResponse(
     79         "Not able to find a detailed error in a service response.");
     80   }
     81   std::string message;
     82   if (!error->GetString(kMessage, &message)) {
     83     return GoogleServiceAuthError::FromUnexpectedServiceResponse(
     84         "Not able to find an error message within a service error.");
     85   }
     86   return GoogleServiceAuthError::FromServiceError(message);
     87 }
     88 
     89 }  // namespace
     90 
     91 IssueAdviceInfoEntry::IssueAdviceInfoEntry() {}
     92 IssueAdviceInfoEntry::~IssueAdviceInfoEntry() {}
     93 
     94 bool IssueAdviceInfoEntry::operator ==(const IssueAdviceInfoEntry& rhs) const {
     95   return description == rhs.description && details == rhs.details;
     96 }
     97 
     98 OAuth2MintTokenFlow::Parameters::Parameters() : mode(MODE_ISSUE_ADVICE) {}
     99 
    100 OAuth2MintTokenFlow::Parameters::Parameters(
    101     const std::string& at,
    102     const std::string& eid,
    103     const std::string& cid,
    104     const std::vector<std::string>& scopes_arg,
    105     Mode mode_arg)
    106     : access_token(at),
    107       extension_id(eid),
    108       client_id(cid),
    109       scopes(scopes_arg),
    110       mode(mode_arg) {
    111 }
    112 
    113 OAuth2MintTokenFlow::Parameters::~Parameters() {}
    114 
    115 OAuth2MintTokenFlow::OAuth2MintTokenFlow(URLRequestContextGetter* context,
    116                                          Delegate* delegate,
    117                                          const Parameters& parameters)
    118     : OAuth2ApiCallFlow(context,
    119                         std::string(),
    120                         parameters.access_token,
    121                         std::vector<std::string>()),
    122       delegate_(delegate),
    123       parameters_(parameters),
    124       weak_factory_(this) {}
    125 
    126 OAuth2MintTokenFlow::~OAuth2MintTokenFlow() { }
    127 
    128 void OAuth2MintTokenFlow::ReportSuccess(const std::string& access_token,
    129                                         int time_to_live) {
    130   if (delegate_)
    131     delegate_->OnMintTokenSuccess(access_token, time_to_live);
    132 
    133   // |this| may already be deleted.
    134 }
    135 
    136 void OAuth2MintTokenFlow::ReportIssueAdviceSuccess(
    137     const IssueAdviceInfo& issue_advice) {
    138   if (delegate_)
    139     delegate_->OnIssueAdviceSuccess(issue_advice);
    140 
    141   // |this| may already be deleted.
    142 }
    143 
    144 void OAuth2MintTokenFlow::ReportFailure(
    145     const GoogleServiceAuthError& error) {
    146   if (delegate_)
    147     delegate_->OnMintTokenFailure(error);
    148 
    149   // |this| may already be deleted.
    150 }
    151 
    152 GURL OAuth2MintTokenFlow::CreateApiCallUrl() {
    153   return GaiaUrls::GetInstance()->oauth2_issue_token_url();
    154 }
    155 
    156 std::string OAuth2MintTokenFlow::CreateApiCallBody() {
    157   const char* force_value =
    158       (parameters_.mode == MODE_MINT_TOKEN_FORCE ||
    159        parameters_.mode == MODE_RECORD_GRANT)
    160           ? kForceValueTrue : kForceValueFalse;
    161   const char* response_type_value =
    162       (parameters_.mode == MODE_MINT_TOKEN_NO_FORCE ||
    163        parameters_.mode == MODE_MINT_TOKEN_FORCE)
    164           ? kResponseTypeValueToken : kResponseTypeValueNone;
    165   return base::StringPrintf(
    166       kOAuth2IssueTokenBodyFormat,
    167       net::EscapeUrlEncodedData(force_value, true).c_str(),
    168       net::EscapeUrlEncodedData(response_type_value, true).c_str(),
    169       net::EscapeUrlEncodedData(
    170           JoinString(parameters_.scopes, ' '), true).c_str(),
    171       net::EscapeUrlEncodedData(parameters_.client_id, true).c_str(),
    172       net::EscapeUrlEncodedData(parameters_.extension_id, true).c_str());
    173 }
    174 
    175 void OAuth2MintTokenFlow::ProcessApiCallSuccess(
    176     const net::URLFetcher* source) {
    177   std::string response_body;
    178   source->GetResponseAsString(&response_body);
    179   scoped_ptr<base::Value> value(base::JSONReader::Read(response_body));
    180   base::DictionaryValue* dict = NULL;
    181   if (!value.get() || !value->GetAsDictionary(&dict)) {
    182     ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse(
    183         "Not able to parse a JSON object from a service response."));
    184     return;
    185   }
    186 
    187   std::string issue_advice_value;
    188   if (!dict->GetString(kIssueAdviceKey, &issue_advice_value)) {
    189     ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse(
    190         "Not able to find an issueAdvice in a service response."));
    191     return;
    192   }
    193   if (issue_advice_value == kIssueAdviceValueConsent) {
    194     IssueAdviceInfo issue_advice;
    195     if (ParseIssueAdviceResponse(dict, &issue_advice))
    196       ReportIssueAdviceSuccess(issue_advice);
    197     else
    198       ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse(
    199           "Not able to parse the contents of consent "
    200           "from a service response."));
    201   } else {
    202     std::string access_token;
    203     int time_to_live;
    204     if (ParseMintTokenResponse(dict, &access_token, &time_to_live))
    205       ReportSuccess(access_token, time_to_live);
    206     else
    207       ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse(
    208           "Not able to parse the contents of access token "
    209           "from a service response."));
    210   }
    211 
    212   // |this| may be deleted!
    213 }
    214 
    215 void OAuth2MintTokenFlow::ProcessApiCallFailure(
    216     const net::URLFetcher* source) {
    217   ReportFailure(CreateAuthError(source));
    218 }
    219 void OAuth2MintTokenFlow::ProcessNewAccessToken(
    220     const std::string& access_token) {
    221   // We don't currently store new access tokens. We generate one every time.
    222   // So we have nothing to do here.
    223   return;
    224 }
    225 void OAuth2MintTokenFlow::ProcessMintAccessTokenFailure(
    226     const GoogleServiceAuthError& error) {
    227   ReportFailure(error);
    228 }
    229 
    230 // static
    231 bool OAuth2MintTokenFlow::ParseMintTokenResponse(
    232     const base::DictionaryValue* dict, std::string* access_token,
    233     int* time_to_live) {
    234   CHECK(dict);
    235   CHECK(access_token);
    236   CHECK(time_to_live);
    237   std::string ttl_string;
    238   return dict->GetString(kExpiresInKey, &ttl_string) &&
    239       base::StringToInt(ttl_string, time_to_live) &&
    240       dict->GetString(kAccessTokenKey, access_token);
    241 }
    242 
    243 // static
    244 bool OAuth2MintTokenFlow::ParseIssueAdviceResponse(
    245     const base::DictionaryValue* dict, IssueAdviceInfo* issue_advice) {
    246   CHECK(dict);
    247   CHECK(issue_advice);
    248 
    249   const base::DictionaryValue* consent_dict = NULL;
    250   if (!dict->GetDictionary(kConsentKey, &consent_dict))
    251     return false;
    252 
    253   const base::ListValue* scopes_list = NULL;
    254   if (!consent_dict->GetList(kScopesKey, &scopes_list))
    255     return false;
    256 
    257   bool success = true;
    258   for (size_t index = 0; index < scopes_list->GetSize(); ++index) {
    259     const base::DictionaryValue* scopes_entry = NULL;
    260     IssueAdviceInfoEntry entry;
    261     base::string16 detail;
    262     if (!scopes_list->GetDictionary(index, &scopes_entry) ||
    263         !scopes_entry->GetString(kDescriptionKey, &entry.description) ||
    264         !scopes_entry->GetString(kDetailKey, &detail)) {
    265       success = false;
    266       break;
    267     }
    268 
    269     base::TrimWhitespace(entry.description, base::TRIM_ALL, &entry.description);
    270     static const base::string16 detail_separators =
    271         base::ASCIIToUTF16(kDetailSeparators);
    272     Tokenize(detail, detail_separators, &entry.details);
    273     for (size_t i = 0; i < entry.details.size(); i++)
    274       base::TrimWhitespace(entry.details[i], base::TRIM_ALL, &entry.details[i]);
    275     issue_advice->push_back(entry);
    276   }
    277 
    278   if (!success)
    279     issue_advice->clear();
    280 
    281   return success;
    282 }
    283