oauth2_mint_token_flow.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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/string_util.h"
16#include "base/stringprintf.h"
17#include "base/utf_string_conversions.h"
18#include "base/values.h"
19#include "google_apis/gaia/gaia_urls.h"
20#include "google_apis/gaia/google_service_auth_error.h"
21#include "net/base/escape.h"
22#include "net/url_request/url_fetcher.h"
23#include "net/url_request/url_request_context_getter.h"
24#include "net/url_request/url_request_status.h"
25
26using net::URLFetcher;
27using net::URLRequestContextGetter;
28using net::URLRequestStatus;
29
30namespace {
31
32static const char kForceValueFalse[] = "false";
33static const char kForceValueTrue[] = "true";
34static const char kResponseTypeValueNone[] = "none";
35static const char kResponseTypeValueToken[] = "token";
36
37static const char kOAuth2IssueTokenBodyFormat[] =
38    "force=%s"
39    "&response_type=%s"
40    "&scope=%s"
41    "&client_id=%s"
42    "&origin=%s";
43static const char kIssueAdviceKey[] = "issueAdvice";
44static const char kIssueAdviceValueAuto[] = "auto";
45static const char kIssueAdviceValueConsent[] = "consent";
46static const char kAccessTokenKey[] = "token";
47static const char kConsentKey[] = "consent";
48static const char kScopesKey[] = "scopes";
49static const char kDescriptionKey[] = "description";
50static const char kDetailKey[] = "detail";
51static const char kDetailSeparators[] = "\n";
52
53static GoogleServiceAuthError CreateAuthError(URLRequestStatus status) {
54  if (status.status() == URLRequestStatus::CANCELED) {
55    return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
56  } else {
57    // TODO(munjal): Improve error handling. Currently we return connection
58    // error for even application level errors. We need to either expand the
59    // GoogleServiceAuthError enum or create a new one to report better
60    // errors.
61    DLOG(WARNING) << "Server returned error: errno " << status.error();
62    return GoogleServiceAuthError::FromConnectionError(status.error());
63  }
64}
65
66}  // namespace
67
68IssueAdviceInfoEntry::IssueAdviceInfoEntry() {}
69IssueAdviceInfoEntry::~IssueAdviceInfoEntry() {}
70
71bool IssueAdviceInfoEntry::operator ==(const IssueAdviceInfoEntry& rhs) const {
72  return description == rhs.description && details == rhs.details;
73}
74
75OAuth2MintTokenFlow::Parameters::Parameters() : mode(MODE_ISSUE_ADVICE) {}
76
77OAuth2MintTokenFlow::Parameters::Parameters(
78    const std::string& rt,
79    const std::string& eid,
80    const std::string& cid,
81    const std::vector<std::string>& scopes_arg,
82    Mode mode_arg)
83    : login_refresh_token(rt),
84      extension_id(eid),
85      client_id(cid),
86      scopes(scopes_arg),
87      mode(mode_arg) {
88}
89
90OAuth2MintTokenFlow::Parameters::~Parameters() {}
91
92OAuth2MintTokenFlow::OAuth2MintTokenFlow(
93    URLRequestContextGetter* context,
94    Delegate* delegate,
95    const Parameters& parameters)
96    : OAuth2ApiCallFlow(
97          context, parameters.login_refresh_token,
98          "", std::vector<std::string>()),
99      delegate_(delegate),
100      parameters_(parameters),
101      ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
102}
103
104OAuth2MintTokenFlow::~OAuth2MintTokenFlow() { }
105
106void OAuth2MintTokenFlow::ReportSuccess(const std::string& access_token) {
107  if (delegate_)
108    delegate_->OnMintTokenSuccess(access_token);
109
110  // |this| may already be deleted.
111}
112
113void OAuth2MintTokenFlow::ReportIssueAdviceSuccess(
114    const IssueAdviceInfo& issue_advice) {
115  if (delegate_)
116    delegate_->OnIssueAdviceSuccess(issue_advice);
117
118  // |this| may already be deleted.
119}
120
121void OAuth2MintTokenFlow::ReportFailure(
122    const GoogleServiceAuthError& error) {
123  if (delegate_)
124    delegate_->OnMintTokenFailure(error);
125
126  // |this| may already be deleted.
127}
128
129GURL OAuth2MintTokenFlow::CreateApiCallUrl() {
130  return GURL(GaiaUrls::GetInstance()->oauth2_issue_token_url());
131}
132
133std::string OAuth2MintTokenFlow::CreateApiCallBody() {
134  const char* force_value =
135      (parameters_.mode == MODE_MINT_TOKEN_FORCE ||
136       parameters_.mode == MODE_RECORD_GRANT)
137          ? kForceValueTrue : kForceValueFalse;
138  const char* response_type_value =
139      (parameters_.mode == MODE_MINT_TOKEN_NO_FORCE ||
140       parameters_.mode == MODE_MINT_TOKEN_FORCE)
141          ? kResponseTypeValueToken : kResponseTypeValueNone;
142  return base::StringPrintf(
143      kOAuth2IssueTokenBodyFormat,
144      net::EscapeUrlEncodedData(force_value, true).c_str(),
145      net::EscapeUrlEncodedData(response_type_value, true).c_str(),
146      net::EscapeUrlEncodedData(
147          JoinString(parameters_.scopes, ' '), true).c_str(),
148      net::EscapeUrlEncodedData(parameters_.client_id, true).c_str(),
149      net::EscapeUrlEncodedData(parameters_.extension_id, true).c_str());
150}
151
152void OAuth2MintTokenFlow::ProcessApiCallSuccess(
153    const net::URLFetcher* source) {
154  // TODO(munjal): Change error code paths in this method to report an
155  // internal error.
156  std::string response_body;
157  source->GetResponseAsString(&response_body);
158  scoped_ptr<base::Value> value(base::JSONReader::Read(response_body));
159  DictionaryValue* dict = NULL;
160  if (!value.get() || !value->GetAsDictionary(&dict)) {
161    ReportFailure(GoogleServiceAuthError::FromConnectionError(101));
162    return;
163  }
164
165  std::string issue_advice;
166  if (!dict->GetString(kIssueAdviceKey, &issue_advice)) {
167    ReportFailure(GoogleServiceAuthError::FromConnectionError(101));
168    return;
169  }
170  if (issue_advice == kIssueAdviceValueConsent) {
171    IssueAdviceInfo issue_advice;
172    if (ParseIssueAdviceResponse(dict, &issue_advice))
173      ReportIssueAdviceSuccess(issue_advice);
174    else
175      ReportFailure(GoogleServiceAuthError::FromConnectionError(101));
176  } else {
177    std::string access_token;
178    if (ParseMintTokenResponse(dict, &access_token))
179      ReportSuccess(access_token);
180    else
181      ReportFailure(GoogleServiceAuthError::FromConnectionError(101));
182  }
183
184  // |this| may be deleted!
185}
186
187void OAuth2MintTokenFlow::ProcessApiCallFailure(
188    const net::URLFetcher* source) {
189  ReportFailure(CreateAuthError(source->GetStatus()));
190}
191void OAuth2MintTokenFlow::ProcessNewAccessToken(
192    const std::string& access_token) {
193  // We don't currently store new access tokens. We generate one every time.
194  // So we have nothing to do here.
195  return;
196}
197void OAuth2MintTokenFlow::ProcessMintAccessTokenFailure(
198    const GoogleServiceAuthError& error) {
199  ReportFailure(error);
200}
201
202// static
203bool OAuth2MintTokenFlow::ParseMintTokenResponse(
204    const base::DictionaryValue* dict, std::string* access_token) {
205  CHECK(dict);
206  CHECK(access_token);
207  return dict->GetString(kAccessTokenKey, access_token);
208}
209
210// static
211bool OAuth2MintTokenFlow::ParseIssueAdviceResponse(
212    const base::DictionaryValue* dict, IssueAdviceInfo* issue_advice) {
213  CHECK(dict);
214  CHECK(issue_advice);
215
216  const base::DictionaryValue* consent_dict = NULL;
217  if (!dict->GetDictionary(kConsentKey, &consent_dict))
218    return false;
219
220  const base::ListValue* scopes_list = NULL;
221  if (!consent_dict->GetList(kScopesKey, &scopes_list))
222    return false;
223
224  bool success = true;
225  for (size_t index = 0; index < scopes_list->GetSize(); ++index) {
226    const base::DictionaryValue* scopes_entry = NULL;
227    IssueAdviceInfoEntry entry;
228    string16 detail;
229    if (!scopes_list->GetDictionary(index, &scopes_entry) ||
230        !scopes_entry->GetString(kDescriptionKey, &entry.description) ||
231        !scopes_entry->GetString(kDetailKey, &detail)) {
232      success = false;
233      break;
234    }
235
236    TrimWhitespace(entry.description, TRIM_ALL, &entry.description);
237    static const string16 detail_separators = ASCIIToUTF16(kDetailSeparators);
238    Tokenize(detail, detail_separators, &entry.details);
239    for (size_t i = 0; i < entry.details.size(); i++)
240      TrimWhitespace(entry.details[i], TRIM_ALL, &entry.details[i]);
241    issue_advice->push_back(entry);
242  }
243
244  if (!success)
245    issue_advice->clear();
246
247  return success;
248}
249