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_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"
27 using net::URLFetcher
;
28 using net::URLRequestContextGetter
;
29 using net::URLRequestStatus
;
33 static const char kForceValueFalse
[] = "false";
34 static const char kForceValueTrue
[] = "true";
35 static const char kResponseTypeValueNone
[] = "none";
36 static const char kResponseTypeValueToken
[] = "token";
38 static const char kOAuth2IssueTokenBodyFormat
[] =
44 static const char kIssueAdviceKey
[] = "issueAdvice";
45 static const char kIssueAdviceValueAuto
[] = "auto";
46 static const char kIssueAdviceValueConsent
[] = "consent";
47 static const char kAccessTokenKey
[] = "token";
48 static const char kConsentKey
[] = "consent";
49 static const char kExpiresInKey
[] = "expiresIn";
50 static const char kScopesKey
[] = "scopes";
51 static const char kDescriptionKey
[] = "description";
52 static const char kDetailKey
[] = "detail";
53 static const char kDetailSeparators
[] = "\n";
54 static const char kError
[] = "error";
55 static const char kMessage
[] = "message";
57 static GoogleServiceAuthError
CreateAuthError(const net::URLFetcher
* source
) {
58 URLRequestStatus status
= source
->GetStatus();
59 if (status
.status() == URLRequestStatus::CANCELED
) {
60 return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED
);
62 if (status
.status() == URLRequestStatus::FAILED
) {
63 DLOG(WARNING
) << "Server returned error: errno " << status
.error();
64 return GoogleServiceAuthError::FromConnectionError(status
.error());
67 std::string response_body
;
68 source
->GetResponseAsString(&response_body
);
69 scoped_ptr
<Value
> value(base::JSONReader::Read(response_body
));
70 DictionaryValue
* response
;
71 if (!value
.get() || !value
->GetAsDictionary(&response
)) {
72 return GoogleServiceAuthError::FromUnexpectedServiceResponse(
74 "Not able to parse a JSON object from a service response. "
75 "HTTP Status of the response is: %d", source
->GetResponseCode()));
77 DictionaryValue
* error
;
78 if (!response
->GetDictionary(kError
, &error
)) {
79 return GoogleServiceAuthError::FromUnexpectedServiceResponse(
80 "Not able to find a detailed error in a service response.");
83 if (!error
->GetString(kMessage
, &message
)) {
84 return GoogleServiceAuthError::FromUnexpectedServiceResponse(
85 "Not able to find an error message within a service error.");
87 return GoogleServiceAuthError::FromServiceError(message
);
92 IssueAdviceInfoEntry::IssueAdviceInfoEntry() {}
93 IssueAdviceInfoEntry::~IssueAdviceInfoEntry() {}
95 bool IssueAdviceInfoEntry::operator ==(const IssueAdviceInfoEntry
& rhs
) const {
96 return description
== rhs
.description
&& details
== rhs
.details
;
99 OAuth2MintTokenFlow::Parameters::Parameters() : mode(MODE_ISSUE_ADVICE
) {}
101 OAuth2MintTokenFlow::Parameters::Parameters(
102 const std::string
& at
,
103 const std::string
& eid
,
104 const std::string
& cid
,
105 const std::vector
<std::string
>& scopes_arg
,
114 OAuth2MintTokenFlow::Parameters::~Parameters() {}
116 OAuth2MintTokenFlow::OAuth2MintTokenFlow(URLRequestContextGetter
* context
,
118 const Parameters
& parameters
)
119 : OAuth2ApiCallFlow(context
,
121 parameters
.access_token
,
122 std::vector
<std::string
>()),
124 parameters_(parameters
),
125 weak_factory_(this) {}
127 OAuth2MintTokenFlow::~OAuth2MintTokenFlow() { }
129 void OAuth2MintTokenFlow::ReportSuccess(const std::string
& access_token
,
132 delegate_
->OnMintTokenSuccess(access_token
, time_to_live
);
134 // |this| may already be deleted.
137 void OAuth2MintTokenFlow::ReportIssueAdviceSuccess(
138 const IssueAdviceInfo
& issue_advice
) {
140 delegate_
->OnIssueAdviceSuccess(issue_advice
);
142 // |this| may already be deleted.
145 void OAuth2MintTokenFlow::ReportFailure(
146 const GoogleServiceAuthError
& error
) {
148 delegate_
->OnMintTokenFailure(error
);
150 // |this| may already be deleted.
153 GURL
OAuth2MintTokenFlow::CreateApiCallUrl() {
154 return GURL(GaiaUrls::GetInstance()->oauth2_issue_token_url());
157 std::string
OAuth2MintTokenFlow::CreateApiCallBody() {
158 const char* force_value
=
159 (parameters_
.mode
== MODE_MINT_TOKEN_FORCE
||
160 parameters_
.mode
== MODE_RECORD_GRANT
)
161 ? kForceValueTrue
: kForceValueFalse
;
162 const char* response_type_value
=
163 (parameters_
.mode
== MODE_MINT_TOKEN_NO_FORCE
||
164 parameters_
.mode
== MODE_MINT_TOKEN_FORCE
)
165 ? kResponseTypeValueToken
: kResponseTypeValueNone
;
166 return base::StringPrintf(
167 kOAuth2IssueTokenBodyFormat
,
168 net::EscapeUrlEncodedData(force_value
, true).c_str(),
169 net::EscapeUrlEncodedData(response_type_value
, true).c_str(),
170 net::EscapeUrlEncodedData(
171 JoinString(parameters_
.scopes
, ' '), true).c_str(),
172 net::EscapeUrlEncodedData(parameters_
.client_id
, true).c_str(),
173 net::EscapeUrlEncodedData(parameters_
.extension_id
, true).c_str());
176 void OAuth2MintTokenFlow::ProcessApiCallSuccess(
177 const net::URLFetcher
* source
) {
178 std::string response_body
;
179 source
->GetResponseAsString(&response_body
);
180 scoped_ptr
<base::Value
> value(base::JSONReader::Read(response_body
));
181 base::DictionaryValue
* dict
= NULL
;
182 if (!value
.get() || !value
->GetAsDictionary(&dict
)) {
183 ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse(
184 "Not able to parse a JSON object from a service response."));
188 std::string issue_advice_value
;
189 if (!dict
->GetString(kIssueAdviceKey
, &issue_advice_value
)) {
190 ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse(
191 "Not able to find an issueAdvice in a service response."));
194 if (issue_advice_value
== kIssueAdviceValueConsent
) {
195 IssueAdviceInfo issue_advice
;
196 if (ParseIssueAdviceResponse(dict
, &issue_advice
))
197 ReportIssueAdviceSuccess(issue_advice
);
199 ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse(
200 "Not able to parse the contents of consent "
201 "from a service response."));
203 std::string access_token
;
205 if (ParseMintTokenResponse(dict
, &access_token
, &time_to_live
))
206 ReportSuccess(access_token
, time_to_live
);
208 ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse(
209 "Not able to parse the contents of access token "
210 "from a service response."));
213 // |this| may be deleted!
216 void OAuth2MintTokenFlow::ProcessApiCallFailure(
217 const net::URLFetcher
* source
) {
218 ReportFailure(CreateAuthError(source
));
220 void OAuth2MintTokenFlow::ProcessNewAccessToken(
221 const std::string
& access_token
) {
222 // We don't currently store new access tokens. We generate one every time.
223 // So we have nothing to do here.
226 void OAuth2MintTokenFlow::ProcessMintAccessTokenFailure(
227 const GoogleServiceAuthError
& error
) {
228 ReportFailure(error
);
232 bool OAuth2MintTokenFlow::ParseMintTokenResponse(
233 const base::DictionaryValue
* dict
, std::string
* access_token
,
238 std::string ttl_string
;
239 return dict
->GetString(kExpiresInKey
, &ttl_string
) &&
240 base::StringToInt(ttl_string
, time_to_live
) &&
241 dict
->GetString(kAccessTokenKey
, access_token
);
245 bool OAuth2MintTokenFlow::ParseIssueAdviceResponse(
246 const base::DictionaryValue
* dict
, IssueAdviceInfo
* issue_advice
) {
250 const base::DictionaryValue
* consent_dict
= NULL
;
251 if (!dict
->GetDictionary(kConsentKey
, &consent_dict
))
254 const base::ListValue
* scopes_list
= NULL
;
255 if (!consent_dict
->GetList(kScopesKey
, &scopes_list
))
259 for (size_t index
= 0; index
< scopes_list
->GetSize(); ++index
) {
260 const base::DictionaryValue
* scopes_entry
= NULL
;
261 IssueAdviceInfoEntry entry
;
263 if (!scopes_list
->GetDictionary(index
, &scopes_entry
) ||
264 !scopes_entry
->GetString(kDescriptionKey
, &entry
.description
) ||
265 !scopes_entry
->GetString(kDetailKey
, &detail
)) {
270 TrimWhitespace(entry
.description
, TRIM_ALL
, &entry
.description
);
271 static const string16 detail_separators
= 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
);
279 issue_advice
->clear();