Chromium Code Reviews| 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 |
| 26 // static | 68 // static |
| 27 void OAuth2MintTokenFlow::SetInterceptorForTests( | 69 void OAuth2MintTokenFlow::SetInterceptorForTests( |
| 28 OAuth2MintTokenFlow::InterceptorForTests* interceptor) { | 70 OAuth2MintTokenFlow::InterceptorForTests* interceptor) { |
| 29 CHECK(CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType)); | 71 CHECK(CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType)); |
| 30 CHECK(NULL == g_interceptor_for_tests); // Only one at a time. | 72 CHECK(NULL == g_interceptor_for_tests); // Only one at a time. |
| 31 g_interceptor_for_tests = interceptor; | 73 g_interceptor_for_tests = interceptor; |
| 32 } | 74 } |
| 33 | 75 |
| 34 OAuth2MintTokenFlow::OAuth2MintTokenFlow( | 76 OAuth2MintTokenFlow::OAuth2MintTokenFlow( |
| 35 URLRequestContextGetter* context, | 77 URLRequestContextGetter* context, |
| 36 Delegate* delegate) | 78 Delegate* delegate, |
| 37 : context_(context), | 79 const Parameters& parameters) |
| 80 : OAuth2ApiCallFlow( | |
| 81 context, parameters.login_refresh_token, | |
| 82 "", std::vector<std::string>()), | |
| 83 context_(context), | |
| 38 delegate_(delegate), | 84 delegate_(delegate), |
| 39 state_(INITIAL) { | 85 parameters_(parameters) { |
| 40 } | 86 } |
| 41 | 87 |
| 42 OAuth2MintTokenFlow::~OAuth2MintTokenFlow() { } | 88 OAuth2MintTokenFlow::~OAuth2MintTokenFlow() { } |
| 43 | 89 |
| 44 void OAuth2MintTokenFlow::Start( | 90 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) { | 91 if (g_interceptor_for_tests) { |
| 55 std::string auth_token; | 92 std::string auth_token; |
| 56 GoogleServiceAuthError error = GoogleServiceAuthError::None(); | 93 GoogleServiceAuthError error = GoogleServiceAuthError::None(); |
| 57 | 94 |
| 58 // We use PostTask, instead of calling the delegate directly, because the | 95 // 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 | 96 // message loop will run a few times before we notify the delegate in the |
| 60 // real implementation. | 97 // real implementation. |
| 61 if (g_interceptor_for_tests->DoIntercept(this, &auth_token, &error)) { | 98 if (g_interceptor_for_tests->DoIntercept(this, &auth_token, &error)) { |
| 62 MessageLoop::current()->PostTask( | 99 MessageLoop::current()->PostTask( |
| 63 FROM_HERE, | 100 FROM_HERE, |
| 64 base::Bind(&OAuth2MintTokenFlow::Delegate::OnMintTokenSuccess, | 101 base::Bind(&OAuth2MintTokenFlow::Delegate::OnMintTokenSuccess, |
| 65 base::Unretained(delegate_), auth_token)); | 102 base::Unretained(delegate_), auth_token)); |
| 66 } else { | 103 } else { |
| 67 MessageLoop::current()->PostTask( | 104 MessageLoop::current()->PostTask( |
| 68 FROM_HERE, | 105 FROM_HERE, |
| 69 base::Bind(&OAuth2MintTokenFlow::Delegate::OnMintTokenFailure, | 106 base::Bind(&OAuth2MintTokenFlow::Delegate::OnMintTokenFailure, |
| 70 base::Unretained(delegate_), error)); | 107 base::Unretained(delegate_), error)); |
| 71 } | 108 } |
| 72 return; | 109 return; |
| 73 } | 110 } |
| 74 | 111 |
| 75 BeginGetLoginAccessToken(); | 112 OAuth2ApiCallFlow::Start(); |
| 76 } | 113 } |
| 77 | 114 |
| 78 void OAuth2MintTokenFlow::OnGetTokenSuccess( | 115 void OAuth2MintTokenFlow::ReportSuccess(const std::string& access_token) { |
| 79 const std::string& access_token) { | 116 if (delegate_) { |
| 80 login_access_token_ = access_token; | 117 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 } | 118 } |
| 111 } | 119 } |
| 112 | 120 |
| 113 void OAuth2MintTokenFlow::OnMintTokenSuccess( | 121 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_) { | 122 if (delegate_) { |
| 152 delegate_->OnMintTokenSuccess(app_access_token_); | 123 delegate_->OnIssueAdviceSuccess(issue_advice); |
| 153 } | 124 } |
| 154 } | 125 } |
| 155 | 126 |
| 156 void OAuth2MintTokenFlow::ReportFailure( | 127 void OAuth2MintTokenFlow::ReportFailure( |
| 157 const GoogleServiceAuthError& error) { | 128 const GoogleServiceAuthError& error) { |
| 158 CHECK_EQ(ERROR_STATE, state_); | |
| 159 | |
| 160 if (delegate_) { | 129 if (delegate_) { |
| 161 delegate_->OnMintTokenFailure(error); | 130 delegate_->OnMintTokenFailure(error); |
| 162 } | 131 } |
| 163 } | 132 } |
| 164 | 133 |
| 165 OAuth2AccessTokenFetcher* OAuth2MintTokenFlow::CreateAccessTokenFetcher() { | 134 GURL OAuth2MintTokenFlow::CreateApiCallUrl() { |
| 166 return new OAuth2AccessTokenFetcher(this, context_); | 135 return GURL(GaiaUrls::GetInstance()->oauth2_issue_token_url()); |
| 167 } | 136 } |
| 168 | 137 |
| 169 OAuth2MintTokenFetcher* OAuth2MintTokenFlow::CreateMintTokenFetcher() { | 138 std::string OAuth2MintTokenFlow::CreateApiCallBody() { |
| 170 return new OAuth2MintTokenFetcher(this, context_, "OAuth2MintTokenFlow"); | 139 const char* force_value = |
| 140 (parameters_.mode == MODE_MINT_TOKEN_FORCE || | |
| 141 parameters_.mode == MODE_RECORD_GRANT) | |
| 142 ? kForceValueTrue : kForceValueFalse; | |
| 143 const char* response_type_value = | |
| 144 (parameters_.mode == MODE_MINT_TOKEN_NO_FORCE || | |
| 145 parameters_.mode == MODE_MINT_TOKEN_FORCE) | |
| 146 ? kResponseTypeValueToken : kResponseTypeValueNone; | |
| 147 return StringPrintf( | |
| 148 kOAuth2IssueTokenBodyFormat, | |
| 149 net::EscapeUrlEncodedData(force_value, true).c_str(), | |
| 150 net::EscapeUrlEncodedData(response_type_value, true).c_str(), | |
| 151 net::EscapeUrlEncodedData( | |
| 152 JoinString(parameters_.scopes, ' '), true).c_str(), | |
| 153 net::EscapeUrlEncodedData(parameters_.client_id, true).c_str(), | |
| 154 net::EscapeUrlEncodedData(parameters_.extension_id, true).c_str()); | |
| 171 } | 155 } |
| 156 | |
| 157 void OAuth2MintTokenFlow::ProcessApiCallSuccess( | |
| 158 const content::URLFetcher* source) { | |
| 159 // TODO(munjal): Change error code paths in this method to report an | |
| 160 // internal error. | |
| 161 scoped_ptr<base::DictionaryValue> dict(ParseResponse(source)); | |
| 162 if (!dict.get()) { | |
| 163 ReportFailure(GoogleServiceAuthError::FromConnectionError(101)); | |
| 164 return; | |
| 165 } | |
| 166 std::string issue_advice; | |
| 167 if (!dict->GetString(kIssueAdviceKey, &issue_advice)) { | |
| 168 ReportFailure(GoogleServiceAuthError::FromConnectionError(101)); | |
| 169 return; | |
| 170 } | |
| 171 if (issue_advice == kIssueAdviceValueConsent) { | |
| 172 IssueAdviceInfo issue_advice; | |
| 173 if (ParseIssueAdviceResponse(dict.get(), &issue_advice)) | |
| 174 ReportSuccess(issue_advice); | |
| 175 else | |
| 176 ReportFailure(GoogleServiceAuthError::FromConnectionError(101)); | |
| 177 } else { | |
| 178 std::string access_token; | |
| 179 if (ParseMintTokenResponse(dict.get(), &access_token)) | |
| 180 ReportSuccess(access_token); | |
| 181 else | |
| 182 ReportFailure(GoogleServiceAuthError::FromConnectionError(101)); | |
| 183 } | |
| 184 } | |
| 185 | |
| 186 void OAuth2MintTokenFlow::ProcessApiCallFailure( | |
| 187 const content::URLFetcher* source) { | |
| 188 ReportFailure(CreateAuthError(source->GetStatus())); | |
| 189 } | |
| 190 void OAuth2MintTokenFlow::ProcessNewAccessToken( | |
| 191 const std::string& access_token) { | |
| 192 // We don't currently store new access tokens. We generate one every time. | |
| 193 // So we have nothing to do here. | |
| 194 return; | |
| 195 } | |
| 196 void OAuth2MintTokenFlow::ProcessMintAccessTokenFailure( | |
| 197 const GoogleServiceAuthError& error) { | |
| 198 ReportFailure(error); | |
| 199 } | |
| 200 | |
| 201 // static | |
| 202 base::DictionaryValue* OAuth2MintTokenFlow::ParseResponse( | |
| 203 const URLFetcher* url_fetcher) { | |
| 204 CHECK(url_fetcher); | |
| 205 std::string response_body; | |
| 206 url_fetcher->GetResponseAsString(&response_body); | |
| 207 base::JSONReader reader; | |
|
jstritar
2012/04/11 19:14:29
Do we need to be parsing this in a sandboxed utili
Munjal (Google)
2012/04/11 19:37:55
I don't think we need sandboxing here. There is al
| |
| 208 scoped_ptr<base::Value> value(reader.Read(response_body, false)); | |
| 209 DictionaryValue* result = NULL; | |
| 210 if (!value.get() || !value->GetAsDictionary(&result)) | |
| 211 return NULL; | |
| 212 | |
| 213 value.release(); | |
| 214 return result; | |
| 215 } | |
| 216 | |
| 217 // static | |
| 218 bool OAuth2MintTokenFlow::ParseMintTokenResponse( | |
| 219 const base::DictionaryValue* dict, std::string* access_token) { | |
| 220 CHECK(dict); | |
| 221 CHECK(access_token); | |
| 222 return dict->GetString(kAccessTokenKey, access_token); | |
| 223 } | |
| 224 | |
| 225 // static | |
| 226 bool OAuth2MintTokenFlow::ParseIssueAdviceResponse( | |
| 227 const base::DictionaryValue* dict, IssueAdviceInfo* issue_advice) { | |
| 228 CHECK(dict); | |
| 229 CHECK(issue_advice); | |
| 230 | |
| 231 base::DictionaryValue* consent_dict = NULL; | |
| 232 if (!dict->GetDictionary(kConsentKey, &consent_dict)) | |
| 233 return false; | |
| 234 | |
| 235 base::ListValue* scopes_list = NULL; | |
| 236 if (!consent_dict->GetList(kScopesKey, &scopes_list)) | |
| 237 return false; | |
| 238 | |
| 239 bool success = true; | |
| 240 for (size_t index = 0; index < scopes_list->GetSize(); ++index) { | |
| 241 base::DictionaryValue* scopes_entry = NULL; | |
| 242 if (!scopes_list->GetDictionary(index, &scopes_entry)) { | |
|
jstritar
2012/04/11 19:14:29
Not required, but you could compact this a bit by
Munjal (Google)
2012/04/11 19:37:55
Done.
I like it. I think I have a tendency to do
| |
| 243 success = false; | |
| 244 break; | |
| 245 } | |
| 246 IssueAdviceInfoEntry entry; | |
| 247 if (!scopes_entry->GetString(kDescriptionKey, &entry.description)) { | |
| 248 success = false; | |
| 249 break; | |
| 250 } | |
| 251 std::string detail; | |
| 252 if (!scopes_entry->GetString(kDetailKey, &detail)) { | |
| 253 success = false; | |
| 254 break; | |
| 255 } | |
| 256 | |
| 257 Tokenize(detail, kDetailSeparators, &entry.details); | |
| 258 issue_advice->push_back(entry); | |
| 259 } | |
| 260 | |
| 261 if (!success) | |
| 262 issue_advice->clear(); | |
| 263 | |
| 264 return success; | |
| 265 } | |
| OLD | NEW |