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