• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
CreateAuthError(const net::URLFetcher * source)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<Value> value(base::JSONReader::Read(response_body));
69   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   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 
IssueAdviceInfoEntry()91 IssueAdviceInfoEntry::IssueAdviceInfoEntry() {}
~IssueAdviceInfoEntry()92 IssueAdviceInfoEntry::~IssueAdviceInfoEntry() {}
93 
operator ==(const IssueAdviceInfoEntry & rhs) const94 bool IssueAdviceInfoEntry::operator ==(const IssueAdviceInfoEntry& rhs) const {
95   return description == rhs.description && details == rhs.details;
96 }
97 
Parameters()98 OAuth2MintTokenFlow::Parameters::Parameters() : mode(MODE_ISSUE_ADVICE) {}
99 
Parameters(const std::string & at,const std::string & eid,const std::string & cid,const std::vector<std::string> & scopes_arg,Mode mode_arg)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 
~Parameters()113 OAuth2MintTokenFlow::Parameters::~Parameters() {}
114 
OAuth2MintTokenFlow(URLRequestContextGetter * context,Delegate * delegate,const Parameters & parameters)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 
~OAuth2MintTokenFlow()126 OAuth2MintTokenFlow::~OAuth2MintTokenFlow() { }
127 
ReportSuccess(const std::string & access_token,int time_to_live)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 
ReportIssueAdviceSuccess(const IssueAdviceInfo & issue_advice)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 
ReportFailure(const GoogleServiceAuthError & error)144 void OAuth2MintTokenFlow::ReportFailure(
145     const GoogleServiceAuthError& error) {
146   if (delegate_)
147     delegate_->OnMintTokenFailure(error);
148 
149   // |this| may already be deleted.
150 }
151 
CreateApiCallUrl()152 GURL OAuth2MintTokenFlow::CreateApiCallUrl() {
153   return GaiaUrls::GetInstance()->oauth2_issue_token_url();
154 }
155 
CreateApiCallBody()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 
ProcessApiCallSuccess(const net::URLFetcher * source)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 
ProcessApiCallFailure(const net::URLFetcher * source)215 void OAuth2MintTokenFlow::ProcessApiCallFailure(
216     const net::URLFetcher* source) {
217   ReportFailure(CreateAuthError(source));
218 }
ProcessNewAccessToken(const std::string & access_token)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 }
ProcessMintAccessTokenFailure(const GoogleServiceAuthError & error)225 void OAuth2MintTokenFlow::ProcessMintAccessTokenFailure(
226     const GoogleServiceAuthError& error) {
227   ReportFailure(error);
228 }
229 
230 // static
ParseMintTokenResponse(const base::DictionaryValue * dict,std::string * access_token,int * time_to_live)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
ParseIssueAdviceResponse(const base::DictionaryValue * dict,IssueAdviceInfo * issue_advice)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     TrimWhitespace(entry.description, TRIM_ALL, &entry.description);
270     static const base::string16 detail_separators =
271         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