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.
5 #include "google_apis/gaia/oauth2_mint_token_flow.h"
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_split.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/values.h"
21 #include "google_apis/gaia/gaia_urls.h"
22 #include "google_apis/gaia/google_service_auth_error.h"
23 #include "net/base/escape.h"
24 #include "net/url_request/url_fetcher.h"
25 #include "net/url_request/url_request_context_getter.h"
26 #include "net/url_request/url_request_status.h"
28 using net::URLFetcher
;
29 using net::URLRequestContextGetter
;
30 using net::URLRequestStatus
;
34 const char kForceValueFalse
[] = "false";
35 const char kForceValueTrue
[] = "true";
36 const char kResponseTypeValueNone
[] = "none";
37 const char kResponseTypeValueToken
[] = "token";
39 const char kOAuth2IssueTokenBodyFormat
[] =
45 // TODO(pavely): lib_ver is passed to differentiate IssueToken requests from
46 // different code locations. Remove once device_id mismatch is understood.
48 const char kOAuth2IssueTokenBodyFormatDeviceIdAddendum
[] =
49 "&device_id=%s&device_type=chrome&lib_ver=extension";
50 const char kIssueAdviceKey
[] = "issueAdvice";
51 const char kIssueAdviceValueConsent
[] = "consent";
52 const char kAccessTokenKey
[] = "token";
53 const char kConsentKey
[] = "consent";
54 const char kExpiresInKey
[] = "expiresIn";
55 const char kScopesKey
[] = "scopes";
56 const char kDescriptionKey
[] = "description";
57 const char kDetailKey
[] = "detail";
58 const char kDetailSeparators
[] = "\n";
59 const char kError
[] = "error";
60 const char kMessage
[] = "message";
62 static GoogleServiceAuthError
CreateAuthError(const net::URLFetcher
* source
) {
63 URLRequestStatus status
= source
->GetStatus();
64 if (status
.status() == URLRequestStatus::CANCELED
) {
65 return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED
);
67 if (status
.status() == URLRequestStatus::FAILED
) {
68 DLOG(WARNING
) << "Server returned error: errno " << status
.error();
69 return GoogleServiceAuthError::FromConnectionError(status
.error());
72 std::string response_body
;
73 source
->GetResponseAsString(&response_body
);
74 scoped_ptr
<base::Value
> value
= base::JSONReader::Read(response_body
);
75 base::DictionaryValue
* response
;
76 if (!value
.get() || !value
->GetAsDictionary(&response
)) {
77 return GoogleServiceAuthError::FromUnexpectedServiceResponse(
79 "Not able to parse a JSON object from a service response. "
80 "HTTP Status of the response is: %d", source
->GetResponseCode()));
82 base::DictionaryValue
* error
;
83 if (!response
->GetDictionary(kError
, &error
)) {
84 return GoogleServiceAuthError::FromUnexpectedServiceResponse(
85 "Not able to find a detailed error in a service response.");
88 if (!error
->GetString(kMessage
, &message
)) {
89 return GoogleServiceAuthError::FromUnexpectedServiceResponse(
90 "Not able to find an error message within a service error.");
92 return GoogleServiceAuthError::FromServiceError(message
);
97 IssueAdviceInfoEntry::IssueAdviceInfoEntry() {}
98 IssueAdviceInfoEntry::~IssueAdviceInfoEntry() {}
100 bool IssueAdviceInfoEntry::operator ==(const IssueAdviceInfoEntry
& rhs
) const {
101 return description
== rhs
.description
&& details
== rhs
.details
;
104 OAuth2MintTokenFlow::Parameters::Parameters() : mode(MODE_ISSUE_ADVICE
) {}
106 OAuth2MintTokenFlow::Parameters::Parameters(
107 const std::string
& eid
,
108 const std::string
& cid
,
109 const std::vector
<std::string
>& scopes_arg
,
110 const std::string
& device_id
,
115 device_id(device_id
),
119 OAuth2MintTokenFlow::Parameters::~Parameters() {}
121 OAuth2MintTokenFlow::OAuth2MintTokenFlow(Delegate
* delegate
,
122 const Parameters
& parameters
)
123 : delegate_(delegate
), parameters_(parameters
), weak_factory_(this) {
126 OAuth2MintTokenFlow::~OAuth2MintTokenFlow() { }
128 void OAuth2MintTokenFlow::ReportSuccess(const std::string
& access_token
,
131 delegate_
->OnMintTokenSuccess(access_token
, time_to_live
);
133 // |this| may already be deleted.
136 void OAuth2MintTokenFlow::ReportIssueAdviceSuccess(
137 const IssueAdviceInfo
& issue_advice
) {
139 delegate_
->OnIssueAdviceSuccess(issue_advice
);
141 // |this| may already be deleted.
144 void OAuth2MintTokenFlow::ReportFailure(
145 const GoogleServiceAuthError
& error
) {
147 delegate_
->OnMintTokenFailure(error
);
149 // |this| may already be deleted.
152 GURL
OAuth2MintTokenFlow::CreateApiCallUrl() {
153 return GaiaUrls::GetInstance()->oauth2_issue_token_url();
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 std::string body
= 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 base::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 if (!parameters_
.device_id
.empty()) {
174 body
.append(base::StringPrintf(
175 kOAuth2IssueTokenBodyFormatDeviceIdAddendum
,
176 net::EscapeUrlEncodedData(parameters_
.device_id
, true).c_str()));
181 void OAuth2MintTokenFlow::ProcessApiCallSuccess(
182 const net::URLFetcher
* source
) {
183 std::string response_body
;
184 source
->GetResponseAsString(&response_body
);
185 scoped_ptr
<base::Value
> value
= base::JSONReader::Read(response_body
);
186 base::DictionaryValue
* dict
= NULL
;
187 if (!value
.get() || !value
->GetAsDictionary(&dict
)) {
188 ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse(
189 "Not able to parse a JSON object from a service response."));
193 std::string issue_advice_value
;
194 if (!dict
->GetString(kIssueAdviceKey
, &issue_advice_value
)) {
195 ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse(
196 "Not able to find an issueAdvice in a service response."));
199 if (issue_advice_value
== kIssueAdviceValueConsent
) {
200 IssueAdviceInfo issue_advice
;
201 if (ParseIssueAdviceResponse(dict
, &issue_advice
))
202 ReportIssueAdviceSuccess(issue_advice
);
204 ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse(
205 "Not able to parse the contents of consent "
206 "from a service response."));
208 std::string access_token
;
210 if (ParseMintTokenResponse(dict
, &access_token
, &time_to_live
))
211 ReportSuccess(access_token
, time_to_live
);
213 ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse(
214 "Not able to parse the contents of access token "
215 "from a service response."));
218 // |this| may be deleted!
221 void OAuth2MintTokenFlow::ProcessApiCallFailure(
222 const net::URLFetcher
* source
) {
223 ReportFailure(CreateAuthError(source
));
227 bool OAuth2MintTokenFlow::ParseMintTokenResponse(
228 const base::DictionaryValue
* dict
, std::string
* access_token
,
233 std::string ttl_string
;
234 return dict
->GetString(kExpiresInKey
, &ttl_string
) &&
235 base::StringToInt(ttl_string
, time_to_live
) &&
236 dict
->GetString(kAccessTokenKey
, access_token
);
240 bool OAuth2MintTokenFlow::ParseIssueAdviceResponse(
241 const base::DictionaryValue
* dict
, IssueAdviceInfo
* issue_advice
) {
245 const base::DictionaryValue
* consent_dict
= NULL
;
246 if (!dict
->GetDictionary(kConsentKey
, &consent_dict
))
249 const base::ListValue
* scopes_list
= NULL
;
250 if (!consent_dict
->GetList(kScopesKey
, &scopes_list
))
254 for (size_t index
= 0; index
< scopes_list
->GetSize(); ++index
) {
255 const base::DictionaryValue
* scopes_entry
= NULL
;
256 IssueAdviceInfoEntry entry
;
257 base::string16 detail
;
258 if (!scopes_list
->GetDictionary(index
, &scopes_entry
) ||
259 !scopes_entry
->GetString(kDescriptionKey
, &entry
.description
) ||
260 !scopes_entry
->GetString(kDetailKey
, &detail
)) {
265 base::TrimWhitespace(entry
.description
, base::TRIM_ALL
, &entry
.description
);
266 entry
.details
= base::SplitString(
267 detail
, base::ASCIIToUTF16(kDetailSeparators
),
268 base::TRIM_WHITESPACE
, base::SPLIT_WANT_NONEMPTY
);
269 issue_advice
->push_back(entry
);
273 issue_advice
->clear();