oauth2_mint_token_flow.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
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.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
27using net::URLFetcher;
28using net::URLRequestContextGetter;
29using net::URLRequestStatus;
30
31namespace {
32
33static const char kForceValueFalse[] = "false";
34static const char kForceValueTrue[] = "true";
35static const char kResponseTypeValueNone[] = "none";
36static const char kResponseTypeValueToken[] = "token";
37
38static const char kOAuth2IssueTokenBodyFormat[] =
39    "force=%s"
40    "&response_type=%s"
41    "&scope=%s"
42    "&client_id=%s"
43    "&origin=%s";
44static const char kIssueAdviceKey[] = "issueAdvice";
45static const char kIssueAdviceValueAuto[] = "auto";
46static const char kIssueAdviceValueConsent[] = "consent";
47static const char kAccessTokenKey[] = "token";
48static const char kConsentKey[] = "consent";
49static const char kExpiresInKey[] = "expiresIn";
50static const char kScopesKey[] = "scopes";
51static const char kDescriptionKey[] = "description";
52static const char kDetailKey[] = "detail";
53static const char kDetailSeparators[] = "\n";
54static const char kError[] = "error";
55static const char kMessage[] = "message";
56
57static 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
92IssueAdviceInfoEntry::IssueAdviceInfoEntry() {}
93IssueAdviceInfoEntry::~IssueAdviceInfoEntry() {}
94
95bool IssueAdviceInfoEntry::operator ==(const IssueAdviceInfoEntry& rhs) const {
96  return description == rhs.description && details == rhs.details;
97}
98
99OAuth2MintTokenFlow::Parameters::Parameters() : mode(MODE_ISSUE_ADVICE) {}
100
101OAuth2MintTokenFlow::Parameters::Parameters(
102    const std::string& rt,
103    const std::string& eid,
104    const std::string& cid,
105    const std::vector<std::string>& scopes_arg,
106    Mode mode_arg)
107    : login_refresh_token(rt),
108      extension_id(eid),
109      client_id(cid),
110      scopes(scopes_arg),
111      mode(mode_arg) {
112}
113
114OAuth2MintTokenFlow::Parameters::~Parameters() {}
115
116OAuth2MintTokenFlow::OAuth2MintTokenFlow(URLRequestContextGetter* context,
117                                         Delegate* delegate,
118                                         const Parameters& parameters)
119    : OAuth2ApiCallFlow(context,
120                        parameters.login_refresh_token,
121                        std::string(),
122                        std::vector<std::string>()),
123      delegate_(delegate),
124      parameters_(parameters),
125      weak_factory_(this) {}
126
127OAuth2MintTokenFlow::~OAuth2MintTokenFlow() { }
128
129void 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
137void OAuth2MintTokenFlow::ReportIssueAdviceSuccess(
138    const IssueAdviceInfo& issue_advice) {
139  if (delegate_)
140    delegate_->OnIssueAdviceSuccess(issue_advice);
141
142  // |this| may already be deleted.
143}
144
145void OAuth2MintTokenFlow::ReportFailure(
146    const GoogleServiceAuthError& error) {
147  if (delegate_)
148    delegate_->OnMintTokenFailure(error);
149
150  // |this| may already be deleted.
151}
152
153GURL OAuth2MintTokenFlow::CreateApiCallUrl() {
154  return GURL(GaiaUrls::GetInstance()->oauth2_issue_token_url());
155}
156
157std::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
176void 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
216void OAuth2MintTokenFlow::ProcessApiCallFailure(
217    const net::URLFetcher* source) {
218  ReportFailure(CreateAuthError(source));
219}
220void 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}
226void OAuth2MintTokenFlow::ProcessMintAccessTokenFailure(
227    const GoogleServiceAuthError& error) {
228  ReportFailure(error);
229}
230
231// static
232bool 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
245bool 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