Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(6182)

Unified Diff: chrome/browser/ui/desktop_ios_promotion/sms_service.cc

Issue 2646823002: D2MIOS: Add plumbing to query verified number and send SMS from growth server. (Closed)
Patch Set: Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/ui/desktop_ios_promotion/sms_service.cc
diff --git a/chrome/browser/ui/desktop_ios_promotion/sms_service.cc b/chrome/browser/ui/desktop_ios_promotion/sms_service.cc
new file mode 100644
index 0000000000000000000000000000000000000000..8f109d8184705aac860699dd99eccde595361efe
--- /dev/null
+++ b/chrome/browser/ui/desktop_ios_promotion/sms_service.cc
@@ -0,0 +1,321 @@
+// Copyright (c) 2017 The Chromium Authors. All rights reserved.
msramek 2017/01/23 21:13:56 nit: We don't use (c) anymore. s/\(c\)//g in all f
justincohen 2017/01/25 22:52:38 Done.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/desktop_ios_promotion/sms_service.h"
+
+#include "base/bind.h"
+#include "base/json/json_reader.h"
+#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/optional.h"
+#include "base/strings/stringprintf.h"
+#include "components/signin/core/browser/signin_manager.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "google_apis/gaia/oauth2_token_service.h"
+#include "net/base/load_flags.h"
+#include "net/base/url_util.h"
+#include "net/http/http_status_code.h"
+#include "net/http/http_util.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_request_context_getter.h"
+
+namespace {
+
+const char kDesktopIOSPromotionOAuthScope[] =
+ "https://www.googleapis.com/auth/mobile_user_preferences";
+
+const char kDesktopIOSPromotionQueryPhoneNumber[] =
+ "https://growth-pa.googleapis.com/v1/get_verified_phone_numbers";
+
+const char kDesktopIOSPromotionSendSMS[] =
+ "https://growth-pa.googleapis.com/v1/send_sms";
+
+const char kPostDataMimeType[] = "application/json";
+
+const char kSendSMSPromoFormat[] = "{promo_id:%s}";
+
+// The maximum number of retries for the URLFetcher requests.
+const size_t kMaxRetries = 1;
+
+class RequestImpl : public DesktopIOSPromotion::SMSService::Request,
msramek 2017/01/23 21:13:57 WebHistoryService:Request is public. Couldn't we j
justincohen 2017/01/25 22:52:38 Wouldn't we need to reimplement that virtuals? Ad
msramek 2017/01/27 16:38:57 Ah, right. I think it would make sense to have a s
+ private OAuth2TokenService::Consumer,
+ private net::URLFetcherDelegate {
+ public:
+ ~RequestImpl() override {}
+
+ // Returns the response code received from the server, which will only be
+ // valid if the request succeeded.
+ int GetResponseCode() override { return response_code_; }
+
+ // Returns the contents of the response body received from the server.
+ const std::string& GetResponseBody() override { return response_body_; }
+
+ bool IsPending() override { return is_pending_; }
+
+ private:
+ friend class DesktopIOSPromotion::SMSService;
+
+ RequestImpl(
+ OAuth2TokenService* token_service,
+ SigninManagerBase* signin_manager,
+ const scoped_refptr<net::URLRequestContextGetter>& request_context,
+ const GURL& url,
+ const DesktopIOSPromotion::SMSService::CompletionCallback& callback)
+ : OAuth2TokenService::Consumer("desktop_ios_promotion"),
+ token_service_(token_service),
+ signin_manager_(signin_manager),
+ request_context_(request_context),
+ url_(url),
+ post_data_mime_type_(kPostDataMimeType),
+ response_code_(0),
+ auth_retry_count_(0),
+ callback_(callback),
+ is_pending_(false) {
+ DCHECK(token_service_);
+ DCHECK(signin_manager_);
+ DCHECK(request_context_);
+ }
+
+ void Start() override {
+ OAuth2TokenService::ScopeSet oauth_scopes;
+ oauth_scopes.insert(kDesktopIOSPromotionOAuthScope);
+ token_request_ = token_service_->StartRequest(
+ signin_manager_->GetAuthenticatedAccountId(), oauth_scopes, this);
+ is_pending_ = true;
+ }
+
+ // content::URLFetcherDelegate interface.
+ void OnURLFetchComplete(const net::URLFetcher* source) override {
+ DCHECK_EQ(source, url_fetcher_.get());
+ response_code_ = url_fetcher_->GetResponseCode();
+
+ UMA_HISTOGRAM_CUSTOM_ENUMERATION(
+ "DesktopIOSPromotion.OAuthTokenResponseCode",
+ net::HttpUtil::MapStatusCodeForHistogram(response_code_),
+ net::HttpUtil::GetStatusCodesForHistogram());
+
+ // If the response code indicates that the token might not be valid,
+ // invalidate the token and try again.
+ if (response_code_ == net::HTTP_UNAUTHORIZED && ++auth_retry_count_ <= 1) {
+ OAuth2TokenService::ScopeSet oauth_scopes;
+ oauth_scopes.insert(kDesktopIOSPromotionOAuthScope);
+ token_service_->InvalidateAccessToken(
+ signin_manager_->GetAuthenticatedAccountId(), oauth_scopes,
+ access_token_);
+
+ access_token_.clear();
+ Start();
+ return;
+ }
+ url_fetcher_->GetResponseAsString(&response_body_);
+ url_fetcher_.reset();
+ is_pending_ = false;
+ callback_.Run(this, response_code_ == net::HTTP_OK);
+ // It is valid for the callback to delete |this|, so do not access any
+ // members below here.
+ }
+
+ // OAuth2TokenService::Consumer interface.
+ void OnGetTokenSuccess(const OAuth2TokenService::Request* request,
+ const std::string& access_token,
+ const base::Time& expiration_time) override {
+ token_request_.reset();
+ DCHECK(!access_token.empty());
+ access_token_ = access_token;
+
+ UMA_HISTOGRAM_BOOLEAN("DesktopIOSPromotion.OAuthTokenCompletion", true);
+
+ // Got an access token -- start the actual API request.
+ url_fetcher_ = CreateUrlFetcher(access_token);
+ url_fetcher_->Start();
+ }
+
+ void OnGetTokenFailure(const OAuth2TokenService::Request* request,
+ const GoogleServiceAuthError& error) override {
+ token_request_.reset();
+ is_pending_ = false;
+
+ UMA_HISTOGRAM_BOOLEAN("DesktopIOSPromotion.OAuthTokenCompletion", false);
+
+ callback_.Run(this, false);
+ // It is valid for the callback to delete |this|, so do not access any
+ // members below here.
+ }
+
+ // Helper for creating a new URLFetcher for the API request.
+ std::unique_ptr<net::URLFetcher> CreateUrlFetcher(
+ const std::string& access_token) {
+ net::URLFetcher::RequestType request_type =
+ post_data_ ? net::URLFetcher::POST : net::URLFetcher::GET;
+ std::unique_ptr<net::URLFetcher> fetcher =
+ net::URLFetcher::Create(url_, request_type, this);
+ fetcher->SetRequestContext(request_context_.get());
+ fetcher->SetMaxRetriesOn5xx(kMaxRetries);
+ fetcher->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SAVE_COOKIES);
+ fetcher->AddExtraRequestHeader("Authorization: Bearer " + access_token);
+ fetcher->AddExtraRequestHeader(
+ "X-Developer-Key: " +
+ GaiaUrls::GetInstance()->oauth2_chrome_client_id());
+
+ if (post_data_)
+ fetcher->SetUploadData(post_data_mime_type_, post_data_.value());
+ return fetcher;
+ }
+
+ void SetPostData(const std::string& post_data) override {
+ SetPostDataAndType(post_data, kPostDataMimeType);
+ }
+
+ void SetPostDataAndType(const std::string& post_data,
+ const std::string& mime_type) override {
+ post_data_ = post_data;
+ post_data_mime_type_ = mime_type;
+ }
+
+ OAuth2TokenService* token_service_;
+ SigninManagerBase* signin_manager_;
+ scoped_refptr<net::URLRequestContextGetter> request_context_;
+
+ // The URL of the API endpoint.
+ GURL url_;
+
+ // POST data to be sent with the request (may be empty).
+ base::Optional<std::string> post_data_;
+
+ // MIME type of the post requests. Defaults to text/plain.
+ std::string post_data_mime_type_;
+
+ // The OAuth2 access token request.
+ std::unique_ptr<OAuth2TokenService::Request> token_request_;
+
+ // The current OAuth2 access token.
+ std::string access_token_;
+
+ // Handles the actual API requests after the OAuth token is acquired.
+ std::unique_ptr<net::URLFetcher> url_fetcher_;
+
+ // Holds the response code received from the server.
+ int response_code_;
+
+ // Holds the response body received from the server.
+ std::string response_body_;
+
+ // The number of times this request has already been retried due to
+ // authorization problems.
+ int auth_retry_count_;
+
+ // The callback to execute when the query is complete.
+ DesktopIOSPromotion::SMSService::CompletionCallback callback_;
+
+ // True if the request was started and has not yet completed, otherwise false.
+ bool is_pending_;
+};
+
+} // namespace
+
+namespace DesktopIOSPromotion {
+
+SMSService::Request::Request() {}
+
+SMSService::Request::~Request() {}
+
+SMSService::SMSService(
+ OAuth2TokenService* token_service,
+ SigninManagerBase* signin_manager,
+ const scoped_refptr<net::URLRequestContextGetter>& request_context)
+ : token_service_(token_service),
+ signin_manager_(signin_manager),
+ request_context_(request_context),
+ weak_ptr_factory_(this) {}
+
+SMSService::~SMSService() {}
+
+void SMSService::AddObserver(SMSServiceObserver* observer) {
+ observer_list_.AddObserver(observer);
+}
+
+void SMSService::RemoveObserver(SMSServiceObserver* observer) {
+ observer_list_.RemoveObserver(observer);
+}
+
+SMSService::Request* SMSService::CreateRequest(
+ const GURL& url,
+ const CompletionCallback& callback) {
+ return new RequestImpl(token_service_, signin_manager_, request_context_, url,
+ callback);
+}
+
+void SMSService::QueryPhoneNumber(const PhoneNumberCallback& callback) {
+ CompletionCallback completion_callback =
+ base::Bind(&SMSService::QueryPhoneNumberCompletionCallback,
+ weak_ptr_factory_.GetWeakPtr(), callback);
+
+ GURL url(kDesktopIOSPromotionQueryPhoneNumber);
+ Request* request = CreateRequest(url, completion_callback);
+ request->SetPostData("{}");
msramek 2017/01/23 21:13:56 Is this a placeholder? In that case please add a T
justincohen 2017/01/25 22:52:38 added a comment.
+ pending_requests_[request] = base::WrapUnique(request);
+ request->Start();
+}
+
+void SMSService::SendSMS(std::string promo_id,
+ const SMSService::PhoneNumberCallback& callback) {
+ CompletionCallback completion_callback = base::Bind(
+ &SMSService::SendSMSCallback, weak_ptr_factory_.GetWeakPtr(), callback);
+ GURL url(kDesktopIOSPromotionSendSMS);
+ Request* request = CreateRequest(url, completion_callback);
+ request->SetPostData(
+ base::StringPrintf(kSendSMSPromoFormat, promo_id.c_str()));
+ pending_requests_[request] = base::WrapUnique(request);
+ request->Start();
+}
+
+void SMSService::QueryPhoneNumberCompletionCallback(
+ const SMSService::PhoneNumberCallback& callback,
+ SMSService::Request* request,
+ bool success) {
+ std::unique_ptr<Request> request_ptr = std::move(pending_requests_[request]);
+ pending_requests_.erase(request);
+
+ std::string phone_number;
+ std::unique_ptr<base::Value> value =
+ base::JSONReader::Read(request->GetResponseBody());
+ if (value.get() && value.get()->IsType(base::Value::Type::DICTIONARY)) {
msramek 2017/01/23 21:13:56 This looks complex enough to deserve a test.
justincohen 2017/01/27 05:12:00 Done.
+ const base::DictionaryValue* dictionary;
+ if (value->GetAsDictionary(&dictionary)) {
+ const base::ListValue* number_list;
+ if (dictionary->GetList("phoneNumber", &number_list)) {
+ const base::DictionaryValue* sub_dictionary;
+ // For now only handle the first number.
+ if (number_list->GetSize() > 0 &&
+ number_list->GetDictionary(0, &sub_dictionary)) {
+ sub_dictionary->GetString("phoneNumber", &phone_number);
+ }
+ }
+ }
+ }
+ callback.Run(request, success, phone_number);
+}
+
+void SMSService::SendSMSCallback(
+ const SMSService::PhoneNumberCallback& callback,
+ SMSService::Request* request,
+ bool success) {
+ std::unique_ptr<Request> request_ptr = std::move(pending_requests_[request]);
+ pending_requests_.erase(request);
+
+ std::string phone_number;
+ std::unique_ptr<base::Value> value =
+ base::JSONReader::Read(request->GetResponseBody());
+ if (value.get() && value.get()->IsType(base::Value::Type::DICTIONARY)) {
+ const base::DictionaryValue* dictionary;
+ if (value->GetAsDictionary(&dictionary)) {
+ dictionary->GetString("phoneNumber", &phone_number);
+ }
+ }
+ callback.Run(request, success, phone_number);
+}
+
+} // namespace DesktopIOSPromotion

Powered by Google App Engine
This is Rietveld 408576698