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
27using net::URLFetcher;
28using net::URLRequestContextGetter;
29using net::URLRequestStatus;
30
31namespace {
32
33const char kForceValueFalse[] = "false";
34const char kForceValueTrue[] = "true";
35const char kResponseTypeValueNone[] = "none";
36const char kResponseTypeValueToken[] = "token";
37
38const char kOAuth2IssueTokenBodyFormat[] =
39    "force=%s"
40    "&response_type=%s"
41    "&scope=%s"
42    "&client_id=%s"
43    "&origin=%s";
44const char kIssueAdviceKey[] = "issueAdvice";
45const char kIssueAdviceValueConsent[] = "consent";
46const char kAccessTokenKey[] = "token";
47const char kConsentKey[] = "consent";
48const char kExpiresInKey[] = "expiresIn";
49const char kScopesKey[] = "scopes";
50const char kDescriptionKey[] = "description";
51const char kDetailKey[] = "detail";
52const char kDetailSeparators[] = "\n";
53const char kError[] = "error";
54const char kMessage[] = "message";
55
56static 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
91IssueAdviceInfoEntry::IssueAdviceInfoEntry() {}
92IssueAdviceInfoEntry::~IssueAdviceInfoEntry() {}
93
94bool IssueAdviceInfoEntry::operator ==(const IssueAdviceInfoEntry& rhs) const {
95  return description == rhs.description && details == rhs.details;
96}
97
98OAuth2MintTokenFlow::Parameters::Parameters() : mode(MODE_ISSUE_ADVICE) {}
99
100OAuth2MintTokenFlow::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
113OAuth2MintTokenFlow::Parameters::~Parameters() {}
114
115OAuth2MintTokenFlow::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
126OAuth2MintTokenFlow::~OAuth2MintTokenFlow() { }
127
128void 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
136void OAuth2MintTokenFlow::ReportIssueAdviceSuccess(
137    const IssueAdviceInfo& issue_advice) {
138  if (delegate_)
139    delegate_->OnIssueAdviceSuccess(issue_advice);
140
141  // |this| may already be deleted.
142}
143
144void OAuth2MintTokenFlow::ReportFailure(
145    const GoogleServiceAuthError& error) {
146  if (delegate_)
147    delegate_->OnMintTokenFailure(error);
148
149  // |this| may already be deleted.
150}
151
152GURL OAuth2MintTokenFlow::CreateApiCallUrl() {
153  return GaiaUrls::GetInstance()->oauth2_issue_token_url();
154}
155
156std::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
175void 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
215void OAuth2MintTokenFlow::ProcessApiCallFailure(
216    const net::URLFetcher* source) {
217  ReportFailure(CreateAuthError(source));
218}
219void 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}
225void OAuth2MintTokenFlow::ProcessMintAccessTokenFailure(
226    const GoogleServiceAuthError& error) {
227  ReportFailure(error);
228}
229
230// static
231bool 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
244bool 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