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

Unified Diff: services/authentication/google_authentication_service.cc

Issue 1466733002: Google OAuth Device Flow support for FNL (Closed) Base URL: https://github.com/domokit/mojo.git@master
Patch Set: updated demo namespaces Created 5 years 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: services/authentication/google_authentication_service.cc
diff --git a/services/authentication/google_authentication_service.cc b/services/authentication/google_authentication_service.cc
new file mode 100644
index 0000000000000000000000000000000000000000..0bf698ed85e60c6e95cc58093c808dfdacf9ffc3
--- /dev/null
+++ b/services/authentication/google_authentication_service.cc
@@ -0,0 +1,466 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
jln (very slow on Chromium) 2015/12/08 22:27:29 Did not look yet.
ukode 2015/12/16 19:24:13 Acknowledged.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/authentication/accounts_db_manager.h"
+#include "services/authentication/auth_data.h"
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/strings/string_util.h"
+#include "base/trace_event/trace_event.h"
+#include "base/values.h"
+#include "mojo/common/binding_set.h"
+#include "mojo/data_pipe_utils/data_pipe_drainer.h"
+#include "mojo/data_pipe_utils/data_pipe_utils.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/application_runner.h"
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/services/authentication/interfaces/authentication.mojom.h"
+#include "mojo/services/network/interfaces/network_service.mojom.h"
+#include "mojo/services/network/interfaces/url_loader.mojom.h"
+
+namespace authentication {
+
+// Mojo Shell OAuth2 Client configuration.
+// TODO: These should be retrieved from a secure storage or a configuration file
+// in the future.
+const char* kMojoShellOAuth2ClientId =
+ "962611923869-3avg0b4vlisgjhin0l98dgp6d8sd634r.apps.googleusercontent.com";
+const char* kMojoShellOAuth2ClientSecret = "41IxvPPAt1HyRoYw2hO84dRI";
+
+// Query params used in Google OAuth2 handshake
+const std::string kKeyValSeparator("=");
+const char* kUrlQueryParamSeparator = "&";
+
+const char* kOAuth2ClientIdParamName = "client_id";
+const char* kOAuth2ClientSecretParamName = "client_secret";
+const char* kOAuth2ScopeParamName = "scope";
+const char* kOAuth2GrantTypeParamName = "grant_type";
+const char* kOAuth2CodeParamName = "code";
+const char* kOAuth2RefreshTokenParamName = "refresh_token";
+const char* kOAuth2DeviceFlowGrantType =
+ "http://oauth.net/grant_type/device/1.0";
+const char* kOAuth2RefreshTokenGrantType = "refresh_token";
+
+mojo::String ValueToString(const base::Value& value) {
+ if (value.IsType(base::Value::TYPE_STRING)) {
+ std::string value_string;
+ value.GetAsString(&value_string);
+ return value_string;
+ }
+ if (value.IsType(base::Value::TYPE_INTEGER)) {
+ int value_int;
+ value.GetAsInteger(&value_int);
+ return std::to_string(value_int);
+ }
+ if (value.IsType(base::Value::TYPE_BOOLEAN)) {
+ bool value_bool;
+ value.GetAsBoolean(&value_bool);
+ return value_bool ? "true" : "false";
+ }
+ if (!value.IsType(base::Value::TYPE_NULL)) {
+ LOG(ERROR) << "Unexpected JSON value (requires string or null): " << value;
+ }
+
+ return nullptr;
+}
+
+scoped_ptr<base::DictionaryValue> ParseOAuth2Response(
+ const std::string& response) {
+ if (response.empty()) {
+ return nullptr;
+ }
+
+ scoped_ptr<base::Value> root(base::JSONReader::Read(response));
+ if (!root || !root->IsType(base::Value::TYPE_DICTIONARY)) {
+ LOG(ERROR) << "Unexpected json response:" << std::endl << response;
+ return nullptr;
+ }
+
+ base::DictionaryValue::Iterator it(
+ *static_cast<base::DictionaryValue*>(root.get()));
+ scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
+ std::string val;
+ while (!it.IsAtEnd()) {
+ val = ValueToString(it.value());
+ base::ReplaceChars(val, "\"", "", &val);
+ dict->SetString(it.key(), val);
+ it.Advance();
+ }
+
+ return dict.Pass();
+}
+
+/**
+ * Implementation of AuthenticationService from
+ * services/authentication/authentication.mojom for Google users.
+ */
+class GoogleAuthenticationServiceImpl
+ : public authentication::AuthenticationService {
+ public:
+ GoogleAuthenticationServiceImpl(
+ mojo::InterfaceRequest<AuthenticationService> request,
+ mojo::NetworkServicePtr& network_service,
+ mojo::files::FilesPtr& files)
+ : binding_(this, request.Pass()),
+ network_service_(network_service),
+ weak_ptr_factory_(this) {
+ accounts_db_manager_ = new AccountsDbManager(files.Pass());
+ }
+
+ ~GoogleAuthenticationServiceImpl() override {}
+
+ void onConnectionError() {}
+
+ void GetOAuth2Token(const mojo::String& username,
+ mojo::Array<mojo::String> scopes,
+ const GetOAuth2TokenCallback& callback) override {
+ mojo::String user_data;
+ accounts_db_manager_->GetAccountDataForUser(username, user_data);
+ AuthData* auth_data =
+ authentication::GetAuthDataFromString(user_data.get());
+
+ if ((auth_data == nullptr) || auth_data->persistent_credential.empty()) {
+ callback.Run(nullptr, "User grant not found");
+ }
+
+ // TODO: Scopes are not used with the scoped refresh tokens. When we start
+ // supporting full login scoped tokens, then the scopes here gets used for
+ // Sidescoping.
+ std::string message;
+ message +=
+ kOAuth2ClientIdParamName + kKeyValSeparator + kMojoShellOAuth2ClientId;
+
+ message += kUrlQueryParamSeparator;
+ message += kOAuth2ClientSecretParamName + kKeyValSeparator +
+ kMojoShellOAuth2ClientSecret;
+
+ message += kUrlQueryParamSeparator;
+ message += kOAuth2GrantTypeParamName + kKeyValSeparator +
+ kOAuth2RefreshTokenGrantType;
+
+ message += kUrlQueryParamSeparator;
+ message += kOAuth2RefreshTokenParamName + kKeyValSeparator;
+ message += auth_data->persistent_credential;
+
+ Request("https://www.googleapis.com/oauth2/v3/token", "POST", message,
+ base::Bind(&GoogleAuthenticationServiceImpl::OnGetOAuth2Token,
+ base::Unretained(this), callback));
+ }
+
+ void SelectAccount(bool returnLastSelected,
+ const SelectAccountCallback& callback) override {
+ // Select account will be implemented once we have downscoping enabled. For
+ // now, we are issuing tokens on behalf of Mojo Shell, not the actual app.
+ callback.Run(nullptr, "Not implemented");
+ }
+
+ void ClearOAuth2Token(const mojo::String& token) override {}
+
+ void GetOAuth2DeviceCode(
+ mojo::Array<mojo::String> scopes,
+ const GetOAuth2DeviceCodeCallback& callback) override {
+ std::string message;
+ message +=
+ kOAuth2ClientIdParamName + kKeyValSeparator + kMojoShellOAuth2ClientId;
+
+ std::string scopes_str("email");
+ for (size_t i = 0; i < scopes.size(); i++) {
+ scopes_str += " ";
+ scopes_str += std::string(scopes[i].data());
+ }
+
+ message += kUrlQueryParamSeparator;
+ message += kOAuth2ScopeParamName + kKeyValSeparator;
+ message += scopes_str;
+
+ Request("https://accounts.google.com/o/oauth2/device/code", "POST", message,
+ base::Bind(&GoogleAuthenticationServiceImpl::OnGetOAuth2DeviceCode,
+ base::Unretained(this), callback));
+ }
+
+ void AddAccount(const mojo::String& device_code,
+ const AddAccountCallback& callback) override {
+ std::string message;
+ message +=
+ kOAuth2ClientIdParamName + kKeyValSeparator + kMojoShellOAuth2ClientId;
+
+ message += kUrlQueryParamSeparator;
+ message += kOAuth2ClientSecretParamName + kKeyValSeparator +
+ kMojoShellOAuth2ClientSecret;
+
+ message += kUrlQueryParamSeparator;
+ message += kOAuth2GrantTypeParamName + kKeyValSeparator +
+ kOAuth2DeviceFlowGrantType;
+
+ message += kUrlQueryParamSeparator;
+ message += kOAuth2CodeParamName + kKeyValSeparator;
+ message += device_code;
+
+ Request("https://www.googleapis.com/oauth2/v3/token", "POST", message,
+ base::Bind(&GoogleAuthenticationServiceImpl::OnAddAccount,
+ base::Unretained(this), callback));
+ }
+
+ private:
+ void OnGetOAuth2Token(const GetOAuth2TokenCallback& callback,
+ const std::string& response,
+ const std::string& error) {
+ if (response.empty()) {
+ callback.Run(nullptr, "Error from server:" + error);
+ return;
+ }
+
+ scoped_ptr<base::DictionaryValue> dict =
+ ParseOAuth2Response(response.c_str());
+ if (!dict || dict->HasKey("error")) {
+ callback.Run(nullptr, "Error in parsing response:" + response);
+ return;
+ }
+
+ std::string access_token;
+ dict->GetString("access_token", &access_token);
+
+ callback.Run(access_token, nullptr);
+ }
+
+ void OnGetOAuth2DeviceCode(const GetOAuth2DeviceCodeCallback& callback,
+ const std::string& response,
+ const std::string& error) {
+ if (response.empty()) {
+ callback.Run(nullptr, nullptr, nullptr, "Error from server:" + error);
+ return;
+ }
+
+ scoped_ptr<base::DictionaryValue> dict =
+ ParseOAuth2Response(response.c_str());
+ if (!dict || dict->HasKey("error")) {
+ callback.Run(nullptr, nullptr, nullptr,
+ "Error in parsing response:" + response);
+ return;
+ }
+
+ std::string url;
+ std::string device_code;
+ std::string user_code;
+ dict->GetString("verification_url", &url);
+ dict->GetString("device_code", &device_code);
+ dict->GetString("user_code", &user_code);
+
+ callback.Run(url, device_code, user_code, nullptr);
+ }
+
+ void GetTokenInfo(const std::string& access_token) {
+ std::string url("https://www.googleapis.com/oauth2/v1/tokeninfo");
+ url += "?access_token" + kKeyValSeparator;
+ url += access_token;
+ Request(url, "GET", "",
+ base::Bind(&GoogleAuthenticationServiceImpl::OnGetTokenInfo,
+ base::Unretained(this)));
+ }
+
+ void OnGetTokenInfo(const std::string& response, const std::string& error) {
+ if (response.empty()) {
+ return;
+ }
+
+ scoped_ptr<base::DictionaryValue> dict =
+ ParseOAuth2Response(response.c_str());
+ if (!dict || dict->HasKey("error")) {
+ return;
+ }
+
+ // This field is only present if the profile scope was present in the
+ // request. The value of this field is an immutable identifier for the
+ // logged-in user, and may be used when creating and managing user
+ // sessions in your application.
+ dict->GetString("user_id", &user_id_);
+ dict->GetString("email", &email_);
+ // The space-delimited set of scopes that the user consented to.
+ dict->GetString("scope", &scope_);
+ return;
+ }
+
+ void GetUserInfo(const std::string& id_token) {
+ std::string url("https://www.googleapis.com/oauth2/v1/tokeninfo");
+ url += "?id_token" + kKeyValSeparator;
+ url += id_token;
+ Request(url, "GET", "",
+ base::Bind(&GoogleAuthenticationServiceImpl::OnGetUserInfo,
+ base::Unretained(this)));
+ }
+
+ void OnGetUserInfo(const std::string& response, const std::string& error) {
+ if (response.empty()) {
+ return;
+ }
+
+ scoped_ptr<base::DictionaryValue> dict =
+ ParseOAuth2Response(response.c_str());
+ if (!dict || dict->HasKey("error")) {
+ return;
+ }
+
+ // This field is only present if the email scope was requested
+ dict->GetString("email", &email_);
+
+ return;
+ }
+
+ void OnAddAccount(const AddAccountCallback& callback,
+ const std::string& response,
+ const std::string& error) {
+ if (response.empty()) {
+ callback.Run(nullptr, "Error from server:" + error);
+ return;
+ }
+
+ // Parse response and fetch refresh, access and idtokens
+ scoped_ptr<base::DictionaryValue> dict =
+ ParseOAuth2Response(response.c_str());
+ if (!dict || dict->HasKey("error")) {
+ callback.Run(nullptr, "Error in parsing response:" + response);
+ return;
+ }
+
+ AuthData* auth_data = new AuthData();
+
+ std::string access_token;
+ dict->GetString("access_token", &access_token);
+ GetTokenInfo(access_token); // gets scope, email and user_id
+
+ if (email_.empty()) {
+ std::string id_token;
+ dict->GetString("id_token", &id_token);
+ GetUserInfo(id_token); // gets user's email
+ }
+
+ auth_data->username = email_.empty() ? user_id_ : email_;
+ auth_data->scopes = scope_;
+ auth_data->auth_provider = "Google";
+ auth_data->persistent_credential_type = "RT";
+ dict->GetString("refresh_token", &auth_data->persistent_credential);
+
+ // TODO(ukode): Store access token in cache for the duration set in
+ // response
+ if (!accounts_db_manager_->UpdateAccount(
+ auth_data->username,
+ authentication::GetAuthDataAsString(*auth_data))) {
+ callback.Run(nullptr, "Unable to save refresh grant");
+ return;
+ }
+
+ callback.Run(auth_data->username, nullptr);
+ }
+
+ void Request(const std::string& url,
+ const std::string& method,
+ const std::string& message,
+ const GetOAuth2TokenCallback& callback) {
+ mojo::URLRequestPtr request(mojo::URLRequest::New());
+ request->url = url;
+ request->method = method;
+ request->auto_follow_redirects = true;
+
+ // Add headers
+ auto content_type_header = mojo::HttpHeader::New();
+ content_type_header->name = "Content-Type";
+ content_type_header->value = "application/x-www-form-urlencoded";
+ request->headers.push_back(content_type_header.Pass());
+
+ if (!message.empty()) {
+ request->body.push_back(
+ mojo::common::WriteStringToConsumerHandle(message).Pass());
+ }
+
+ mojo::URLLoaderPtr url_loader;
+ network_service_->CreateURLLoader(GetProxy(&url_loader));
+
+ url_loader->Start(
+ request.Pass(),
+ base::Bind(&GoogleAuthenticationServiceImpl::HandleServerResponse,
+ base::Unretained(this), callback));
+
+ url_loader.WaitForIncomingResponse();
+ }
+
+ void HandleServerResponse(const GetOAuth2TokenCallback& callback,
+ mojo::URLResponsePtr response) {
+ if (response.is_null()) {
+ LOG(WARNING) << "Something went horribly wrong...exiting!!";
+ callback.Run("", "Empty response");
+ return;
+ }
+
+ if (response->error) {
+ LOG(ERROR) << "Got error (" << response->error->code
+ << "), reason: " << response->error->description.get().c_str();
+ callback.Run("", response->error->description.get().c_str());
+ return;
+ }
+
+ std::string response_body;
+ mojo::common::BlockingCopyToString(response->body.Pass(), &response_body);
+
+ callback.Run(response_body, "");
+ }
+
+ std::string user_id_;
+ std::string email_;
+ std::string scope_;
+ mojo::StrongBinding<AuthenticationService> binding_;
+ mojo::NetworkServicePtr& network_service_;
+ AccountsDbManager* accounts_db_manager_;
+ base::WeakPtrFactory<GoogleAuthenticationServiceImpl> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(GoogleAuthenticationServiceImpl);
+};
+
+class GooglerAccountManagerApp
+ : public mojo::ApplicationDelegate,
+ public mojo::InterfaceFactory<AuthenticationService> {
+ public:
+ GooglerAccountManagerApp() {}
+ ~GooglerAccountManagerApp() override {}
+
+ void Initialize(mojo::ApplicationImpl* app) override {
+ app->ConnectToService("mojo:network_service", &network_service_);
+ app->ConnectToService("mojo:files", &files_);
+ }
+
+ bool ConfigureIncomingConnection(
+ mojo::ApplicationConnection* connection) override {
+ connection->AddService<AuthenticationService>(this);
+ return true;
+ }
+
+ void Create(mojo::ApplicationConnection* connection,
+ mojo::InterfaceRequest<AuthenticationService> request) override {
+ new authentication::GoogleAuthenticationServiceImpl(
+ request.Pass(), network_service_, files_);
+ }
+
+ private:
+ mojo::NetworkServicePtr network_service_;
+ mojo::files::FilesPtr files_;
+
+ DISALLOW_COPY_AND_ASSIGN(GooglerAccountManagerApp);
+};
+
+} // namespace authentication
+
+MojoResult MojoMain(MojoHandle application_request) {
+ mojo::ApplicationRunner runner(
+ new authentication::GooglerAccountManagerApp());
+ return runner.Run(application_request);
+}
« services/authentication/auth_data.h ('K') | « services/authentication/dummy_authentication_app.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698