OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/common/net/gaia/oauth2_mint_token_flow.h" | 5 #include "chrome/common/net/gaia/oauth2_mint_token_flow.h" |
6 | 6 |
7 #include <string> | 7 #include <string> |
8 #include <vector> | 8 #include <vector> |
9 | 9 |
10 #include "base/basictypes.h" | 10 #include "base/basictypes.h" |
11 #include "base/bind.h" | 11 #include "base/bind.h" |
12 #include "base/command_line.h" | 12 #include "base/command_line.h" |
| 13 #include "base/json/json_reader.h" |
13 #include "base/message_loop.h" | 14 #include "base/message_loop.h" |
| 15 #include "base/string_util.h" |
| 16 #include "base/stringprintf.h" |
| 17 #include "base/values.h" |
14 #include "chrome/common/chrome_switches.h" | 18 #include "chrome/common/chrome_switches.h" |
15 #include "chrome/common/net/gaia/gaia_urls.h" | 19 #include "chrome/common/net/gaia/gaia_urls.h" |
16 #include "chrome/common/net/gaia/google_service_auth_error.h" | 20 #include "chrome/common/net/gaia/google_service_auth_error.h" |
| 21 #include "content/public/common/url_fetcher.h" |
| 22 #include "net/base/escape.h" |
| 23 #include "net/url_request/url_request_context_getter.h" |
| 24 #include "net/url_request/url_request_status.h" |
17 | 25 |
| 26 using content::URLFetcher; |
18 using net::URLRequestContextGetter; | 27 using net::URLRequestContextGetter; |
| 28 using net::URLRequestStatus; |
19 | 29 |
20 namespace { | 30 namespace { |
21 | 31 |
| 32 static const char kForceValueFalse[] = "false"; |
| 33 static const char kForceValueTrue[] = "true"; |
| 34 static const char kResponseTypeValueNone[] = "none"; |
| 35 static const char kResponseTypeValueToken[] = "token"; |
| 36 |
| 37 static const char kOAuth2IssueTokenBodyFormat[] = |
| 38 "force=%s" |
| 39 "&response_type=%s" |
| 40 "&scope=%s" |
| 41 "&client_id=%s" |
| 42 "&origin=%s"; |
| 43 static const char kIssueAdviceKey[] = "issueAdvice"; |
| 44 static const char kIssueAdviceValueAuto[] = "auto"; |
| 45 static const char kIssueAdviceValueConsent[] = "consent"; |
| 46 static const char kAccessTokenKey[] = "token"; |
| 47 static const char kConsentKey[] = "consent"; |
| 48 static const char kScopesKey[] = "scopes"; |
| 49 static const char kDescriptionKey[] = "description"; |
| 50 static const char kDetailKey[] = "detail"; |
| 51 static const char kDetailSeparators[] = "\n"; |
| 52 |
| 53 static GoogleServiceAuthError CreateAuthError(URLRequestStatus status) { |
| 54 CHECK(!status.is_success()); |
| 55 if (status.status() == URLRequestStatus::CANCELED) { |
| 56 return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED); |
| 57 } else { |
| 58 DLOG(WARNING) << "Could not reach Google Accounts servers: errno " |
| 59 << status.error(); |
| 60 return GoogleServiceAuthError::FromConnectionError(status.error()); |
| 61 } |
| 62 } |
| 63 |
22 OAuth2MintTokenFlow::InterceptorForTests* g_interceptor_for_tests = NULL; | 64 OAuth2MintTokenFlow::InterceptorForTests* g_interceptor_for_tests = NULL; |
23 | 65 |
24 } // namespace | 66 } // namespace |
25 | 67 |
| 68 IssueAdviceInfoEntry::IssueAdviceInfoEntry() {} |
| 69 IssueAdviceInfoEntry::~IssueAdviceInfoEntry() {} |
| 70 |
| 71 bool IssueAdviceInfoEntry::operator ==(const IssueAdviceInfoEntry& rhs) const { |
| 72 return description == rhs.description && details == rhs.details; |
| 73 } |
| 74 |
| 75 OAuth2MintTokenFlow::Parameters::Parameters() : mode(MODE_ISSUE_ADVICE) {} |
| 76 |
| 77 OAuth2MintTokenFlow::Parameters::Parameters( |
| 78 const std::string& rt, |
| 79 const std::string& eid, |
| 80 const std::string& cid, |
| 81 const std::vector<std::string>& scopes_arg, |
| 82 Mode mode_arg) |
| 83 : login_refresh_token(rt), |
| 84 extension_id(eid), |
| 85 client_id(cid), |
| 86 scopes(scopes_arg), |
| 87 mode(mode_arg) { |
| 88 } |
| 89 |
| 90 OAuth2MintTokenFlow::Parameters::~Parameters() {} |
| 91 |
26 // static | 92 // static |
27 void OAuth2MintTokenFlow::SetInterceptorForTests( | 93 void OAuth2MintTokenFlow::SetInterceptorForTests( |
28 OAuth2MintTokenFlow::InterceptorForTests* interceptor) { | 94 OAuth2MintTokenFlow::InterceptorForTests* interceptor) { |
29 CHECK(CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType)); | 95 CHECK(CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType)); |
30 CHECK(NULL == g_interceptor_for_tests); // Only one at a time. | 96 CHECK(NULL == g_interceptor_for_tests); // Only one at a time. |
31 g_interceptor_for_tests = interceptor; | 97 g_interceptor_for_tests = interceptor; |
32 } | 98 } |
33 | 99 |
34 OAuth2MintTokenFlow::OAuth2MintTokenFlow( | 100 OAuth2MintTokenFlow::OAuth2MintTokenFlow( |
35 URLRequestContextGetter* context, | 101 URLRequestContextGetter* context, |
36 Delegate* delegate) | 102 Delegate* delegate, |
37 : context_(context), | 103 const Parameters& parameters) |
| 104 : OAuth2ApiCallFlow( |
| 105 context, parameters.login_refresh_token, |
| 106 "", std::vector<std::string>()), |
| 107 context_(context), |
38 delegate_(delegate), | 108 delegate_(delegate), |
39 state_(INITIAL) { | 109 parameters_(parameters) { |
40 } | 110 } |
41 | 111 |
42 OAuth2MintTokenFlow::~OAuth2MintTokenFlow() { } | 112 OAuth2MintTokenFlow::~OAuth2MintTokenFlow() { } |
43 | 113 |
44 void OAuth2MintTokenFlow::Start( | 114 void OAuth2MintTokenFlow::Start() { |
45 const std::string& login_refresh_token, | |
46 const std::string& extension_id, | |
47 const std::string& client_id, | |
48 const std::vector<std::string>& scopes) { | |
49 login_refresh_token_ = login_refresh_token; | |
50 extension_id_ = extension_id; | |
51 client_id_ = client_id; | |
52 scopes_ = scopes; | |
53 | |
54 if (g_interceptor_for_tests) { | 115 if (g_interceptor_for_tests) { |
55 std::string auth_token; | 116 std::string auth_token; |
56 GoogleServiceAuthError error = GoogleServiceAuthError::None(); | 117 GoogleServiceAuthError error = GoogleServiceAuthError::None(); |
57 | 118 |
58 // We use PostTask, instead of calling the delegate directly, because the | 119 // We use PostTask, instead of calling the delegate directly, because the |
59 // message loop will run a few times before we notify the delegate in the | 120 // message loop will run a few times before we notify the delegate in the |
60 // real implementation. | 121 // real implementation. |
61 if (g_interceptor_for_tests->DoIntercept(this, &auth_token, &error)) { | 122 if (g_interceptor_for_tests->DoIntercept(this, &auth_token, &error)) { |
62 MessageLoop::current()->PostTask( | 123 MessageLoop::current()->PostTask( |
63 FROM_HERE, | 124 FROM_HERE, |
64 base::Bind(&OAuth2MintTokenFlow::Delegate::OnMintTokenSuccess, | 125 base::Bind(&OAuth2MintTokenFlow::Delegate::OnMintTokenSuccess, |
65 base::Unretained(delegate_), auth_token)); | 126 base::Unretained(delegate_), auth_token)); |
66 } else { | 127 } else { |
67 MessageLoop::current()->PostTask( | 128 MessageLoop::current()->PostTask( |
68 FROM_HERE, | 129 FROM_HERE, |
69 base::Bind(&OAuth2MintTokenFlow::Delegate::OnMintTokenFailure, | 130 base::Bind(&OAuth2MintTokenFlow::Delegate::OnMintTokenFailure, |
70 base::Unretained(delegate_), error)); | 131 base::Unretained(delegate_), error)); |
71 } | 132 } |
72 return; | 133 return; |
73 } | 134 } |
74 | 135 |
75 BeginGetLoginAccessToken(); | 136 OAuth2ApiCallFlow::Start(); |
76 } | 137 } |
77 | 138 |
78 void OAuth2MintTokenFlow::OnGetTokenSuccess( | 139 void OAuth2MintTokenFlow::ReportSuccess(const std::string& access_token) { |
79 const std::string& access_token) { | 140 if (delegate_) { |
80 login_access_token_ = access_token; | 141 delegate_->OnMintTokenSuccess(access_token); |
81 EndGetLoginAccessToken(NULL); | |
82 } | |
83 | |
84 void OAuth2MintTokenFlow::OnGetTokenFailure( | |
85 const GoogleServiceAuthError& error) { | |
86 EndGetLoginAccessToken(&error); | |
87 } | |
88 | |
89 void OAuth2MintTokenFlow::BeginGetLoginAccessToken() { | |
90 CHECK_EQ(INITIAL, state_); | |
91 state_ = FETCH_LOGIN_ACCESS_TOKEN_STARTED; | |
92 | |
93 oauth2_access_token_fetcher_.reset(CreateAccessTokenFetcher()); | |
94 oauth2_access_token_fetcher_->Start( | |
95 GaiaUrls::GetInstance()->oauth2_chrome_client_id(), | |
96 GaiaUrls::GetInstance()->oauth2_chrome_client_secret(), | |
97 login_refresh_token_, | |
98 std::vector<std::string>()); | |
99 } | |
100 | |
101 void OAuth2MintTokenFlow::EndGetLoginAccessToken( | |
102 const GoogleServiceAuthError* error) { | |
103 CHECK_EQ(FETCH_LOGIN_ACCESS_TOKEN_STARTED, state_); | |
104 if (!error) { | |
105 state_ = FETCH_LOGIN_ACCESS_TOKEN_DONE; | |
106 BeginMintAccessToken(); | |
107 } else { | |
108 state_ = ERROR_STATE; | |
109 ReportFailure(*error); | |
110 } | 142 } |
111 } | 143 } |
112 | 144 |
113 void OAuth2MintTokenFlow::OnMintTokenSuccess( | 145 void OAuth2MintTokenFlow::ReportSuccess(const IssueAdviceInfo& issue_advice) { |
114 const std::string& access_token) { | |
115 app_access_token_ = access_token; | |
116 EndMintAccessToken(NULL); | |
117 } | |
118 void OAuth2MintTokenFlow::OnMintTokenFailure( | |
119 const GoogleServiceAuthError& error) { | |
120 EndMintAccessToken(&error); | |
121 } | |
122 | |
123 void OAuth2MintTokenFlow::BeginMintAccessToken() { | |
124 CHECK_EQ(FETCH_LOGIN_ACCESS_TOKEN_DONE, state_); | |
125 state_ = MINT_ACCESS_TOKEN_STARTED; | |
126 | |
127 oauth2_mint_token_fetcher_.reset(CreateMintTokenFetcher()); | |
128 oauth2_mint_token_fetcher_->Start( | |
129 login_access_token_, | |
130 client_id_, | |
131 scopes_, | |
132 extension_id_); | |
133 } | |
134 | |
135 void OAuth2MintTokenFlow::EndMintAccessToken( | |
136 const GoogleServiceAuthError* error) { | |
137 CHECK_EQ(MINT_ACCESS_TOKEN_STARTED, state_); | |
138 | |
139 if (!error) { | |
140 state_ = MINT_ACCESS_TOKEN_DONE; | |
141 ReportSuccess(); | |
142 } else { | |
143 state_ = ERROR_STATE; | |
144 ReportFailure(*error); | |
145 } | |
146 } | |
147 | |
148 void OAuth2MintTokenFlow::ReportSuccess() { | |
149 CHECK_EQ(MINT_ACCESS_TOKEN_DONE, state_); | |
150 | |
151 if (delegate_) { | 146 if (delegate_) { |
152 delegate_->OnMintTokenSuccess(app_access_token_); | 147 delegate_->OnIssueAdviceSuccess(issue_advice); |
153 } | 148 } |
154 } | 149 } |
155 | 150 |
156 void OAuth2MintTokenFlow::ReportFailure( | 151 void OAuth2MintTokenFlow::ReportFailure( |
157 const GoogleServiceAuthError& error) { | 152 const GoogleServiceAuthError& error) { |
158 CHECK_EQ(ERROR_STATE, state_); | |
159 | |
160 if (delegate_) { | 153 if (delegate_) { |
161 delegate_->OnMintTokenFailure(error); | 154 delegate_->OnMintTokenFailure(error); |
162 } | 155 } |
163 } | 156 } |
164 | 157 |
165 OAuth2AccessTokenFetcher* OAuth2MintTokenFlow::CreateAccessTokenFetcher() { | 158 GURL OAuth2MintTokenFlow::CreateApiCallUrl() { |
166 return new OAuth2AccessTokenFetcher(this, context_); | 159 return GURL(GaiaUrls::GetInstance()->oauth2_issue_token_url()); |
167 } | 160 } |
168 | 161 |
169 OAuth2MintTokenFetcher* OAuth2MintTokenFlow::CreateMintTokenFetcher() { | 162 std::string OAuth2MintTokenFlow::CreateApiCallBody() { |
170 return new OAuth2MintTokenFetcher(this, context_, "OAuth2MintTokenFlow"); | 163 const char* force_value = |
| 164 (parameters_.mode == MODE_MINT_TOKEN_FORCE || |
| 165 parameters_.mode == MODE_RECORD_GRANT) |
| 166 ? kForceValueTrue : kForceValueFalse; |
| 167 const char* response_type_value = |
| 168 (parameters_.mode == MODE_MINT_TOKEN_NO_FORCE || |
| 169 parameters_.mode == MODE_MINT_TOKEN_FORCE) |
| 170 ? kResponseTypeValueToken : kResponseTypeValueNone; |
| 171 return StringPrintf( |
| 172 kOAuth2IssueTokenBodyFormat, |
| 173 net::EscapeUrlEncodedData(force_value, true).c_str(), |
| 174 net::EscapeUrlEncodedData(response_type_value, true).c_str(), |
| 175 net::EscapeUrlEncodedData( |
| 176 JoinString(parameters_.scopes, ' '), true).c_str(), |
| 177 net::EscapeUrlEncodedData(parameters_.client_id, true).c_str(), |
| 178 net::EscapeUrlEncodedData(parameters_.extension_id, true).c_str()); |
171 } | 179 } |
| 180 |
| 181 void OAuth2MintTokenFlow::ProcessApiCallSuccess( |
| 182 const content::URLFetcher* source) { |
| 183 // TODO(munjal): Change error code paths in this method to report an |
| 184 // internal error. |
| 185 std::string response_body; |
| 186 source->GetResponseAsString(&response_body); |
| 187 base::JSONReader reader; |
| 188 scoped_ptr<base::Value> value(reader.Read(response_body, false)); |
| 189 DictionaryValue* dict = NULL; |
| 190 if (!value.get() || !value->GetAsDictionary(&dict)) { |
| 191 ReportFailure(GoogleServiceAuthError::FromConnectionError(101)); |
| 192 return; |
| 193 } |
| 194 |
| 195 std::string issue_advice; |
| 196 if (!dict->GetString(kIssueAdviceKey, &issue_advice)) { |
| 197 ReportFailure(GoogleServiceAuthError::FromConnectionError(101)); |
| 198 return; |
| 199 } |
| 200 if (issue_advice == kIssueAdviceValueConsent) { |
| 201 IssueAdviceInfo issue_advice; |
| 202 if (ParseIssueAdviceResponse(dict, &issue_advice)) |
| 203 ReportSuccess(issue_advice); |
| 204 else |
| 205 ReportFailure(GoogleServiceAuthError::FromConnectionError(101)); |
| 206 } else { |
| 207 std::string access_token; |
| 208 if (ParseMintTokenResponse(dict, &access_token)) |
| 209 ReportSuccess(access_token); |
| 210 else |
| 211 ReportFailure(GoogleServiceAuthError::FromConnectionError(101)); |
| 212 } |
| 213 } |
| 214 |
| 215 void OAuth2MintTokenFlow::ProcessApiCallFailure( |
| 216 const content::URLFetcher* source) { |
| 217 ReportFailure(CreateAuthError(source->GetStatus())); |
| 218 } |
| 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 } |
| 225 void OAuth2MintTokenFlow::ProcessMintAccessTokenFailure( |
| 226 const GoogleServiceAuthError& error) { |
| 227 ReportFailure(error); |
| 228 } |
| 229 |
| 230 // static |
| 231 bool OAuth2MintTokenFlow::ParseMintTokenResponse( |
| 232 const base::DictionaryValue* dict, std::string* access_token) { |
| 233 CHECK(dict); |
| 234 CHECK(access_token); |
| 235 return dict->GetString(kAccessTokenKey, access_token); |
| 236 } |
| 237 |
| 238 // static |
| 239 bool OAuth2MintTokenFlow::ParseIssueAdviceResponse( |
| 240 const base::DictionaryValue* dict, IssueAdviceInfo* issue_advice) { |
| 241 CHECK(dict); |
| 242 CHECK(issue_advice); |
| 243 |
| 244 base::DictionaryValue* consent_dict = NULL; |
| 245 if (!dict->GetDictionary(kConsentKey, &consent_dict)) |
| 246 return false; |
| 247 |
| 248 base::ListValue* scopes_list = NULL; |
| 249 if (!consent_dict->GetList(kScopesKey, &scopes_list)) |
| 250 return false; |
| 251 |
| 252 bool success = true; |
| 253 for (size_t index = 0; index < scopes_list->GetSize(); ++index) { |
| 254 base::DictionaryValue* scopes_entry = NULL; |
| 255 IssueAdviceInfoEntry entry; |
| 256 std::string detail; |
| 257 if (!scopes_list->GetDictionary(index, &scopes_entry) || |
| 258 !scopes_entry->GetString(kDescriptionKey, &entry.description) || |
| 259 !scopes_entry->GetString(kDetailKey, &detail)) { |
| 260 success = false; |
| 261 break; |
| 262 } |
| 263 |
| 264 Tokenize(detail, kDetailSeparators, &entry.details); |
| 265 issue_advice->push_back(entry); |
| 266 } |
| 267 |
| 268 if (!success) |
| 269 issue_advice->clear(); |
| 270 |
| 271 return success; |
| 272 } |
OLD | NEW |