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