[auth] Remove oauth_token_manager prototype.
oauth_token_manager is replaced by fuchsia::auth::TokenManager for all
auth/token operations.
TEST = Manual user login, CQ.
Change-Id: I704c0ad8870e957b19e80515f6d1de484ddf27c2
diff --git a/examples/oauth_token_manager/BUILD.gn b/examples/oauth_token_manager/BUILD.gn
deleted file mode 100644
index df81c82..0000000
--- a/examples/oauth_token_manager/BUILD.gn
+++ /dev/null
@@ -1,55 +0,0 @@
-# Copyright 2017 The Fuchsia Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//build/package.gni")
-import("//third_party/flatbuffers/flatbuffer.gni")
-
-flatbuffer("credentials") {
- sources = [
- "credentials.fbs",
- ]
-}
-
-executable("bin") {
- output_name = "oauth_token_manager"
-
- sources = [
- "oauth_token_manager.cc",
- ]
-
- deps = [
- ":credentials",
- "//garnet/public/lib/component/cpp",
- "//garnet/public/lib/fidl/cpp",
- "//garnet/public/lib/fsl",
- "//garnet/public/lib/fxl",
- "//garnet/public/fidl/fuchsia.net.oldhttp",
- "//garnet/public/fidl/fuchsia.ui.viewsv1",
- "//peridot/public/fidl/fuchsia.modular.auth",
- "//peridot/public/lib/async/cpp:operation",
- "//third_party/rapidjson",
- "//topaz/runtime/web_runner/services",
- "//zircon/public/lib/async-loop-cpp",
- "//zircon/public/lib/trace-provider",
- ]
-}
-
-package("oauth_token_manager") {
- deps = [
- ":bin",
- ]
-
- binaries = [
- {
- name = "oauth_token_manager"
- },
- ]
-
- meta = [
- {
- path = rebase_path("meta/oauth_token_manager.cmx")
- dest = "oauth_token_manager.cmx"
- },
- ]
-}
diff --git a/examples/oauth_token_manager/credentials.fbs b/examples/oauth_token_manager/credentials.fbs
deleted file mode 100644
index 3fbb623..0000000
--- a/examples/oauth_token_manager/credentials.fbs
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2017 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Schema to store user's long lived credentials from different Identity
-// Providers.
-
-namespace modular;
-namespace auth;
-
-// List of supported identity providers.
-enum IdentityProvider : byte { GOOGLE }
-
-// IdpCredential is used to store refresh token from each identity provider
-// after user's consent.
-table IdpCredential {
- identity_provider: IdentityProvider;
- refresh_token: string;
-}
-
-// UserCredential stores a list of authentication credentials from various
-// identity providers for each unique user.
-table UserCredential {
- account_id: string;
- tokens: [IdpCredential];
-}
-
-// Stores authentication credentials for all users.
-table CredentialStore {
- creds: [UserCredential];
-}
-
-root_type CredentialStore;
diff --git a/examples/oauth_token_manager/meta/oauth_token_manager.cmx b/examples/oauth_token_manager/meta/oauth_token_manager.cmx
deleted file mode 100644
index 318922c..0000000
--- a/examples/oauth_token_manager/meta/oauth_token_manager.cmx
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "program": {
- "binary": "bin/oauth_token_manager"
- },
- "sandbox": {
- "features": [ "persistent-storage" ],
- "services": [
- "fuchsia.tracelink.Registry",
- "fuchsia.sys.Launcher",
- "fuchsia.net.oldhttp.HttpService",
- "fuchsia.net.oldhttp.UrlLoader",
- "fuchsia.ui.viewsv1.ViewProvider",
- "fuchsia.webview.WebView"
- ]
- }
-}
diff --git a/examples/oauth_token_manager/oauth_token_manager.cc b/examples/oauth_token_manager/oauth_token_manager.cc
deleted file mode 100644
index f034ed7..0000000
--- a/examples/oauth_token_manager/oauth_token_manager.cc
+++ /dev/null
@@ -1,1604 +0,0 @@
-// Copyright 2017 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// OAuthTokenManagerApp is a simple auth service hack for fetching user OAuth
-// tokens to talk programmatically to backend apis. These apis are hosted or
-// integrated with Identity providers such as Google, Twitter, Spotify etc.
-
-#include <iomanip>
-#include <iostream>
-#include <map>
-#include <memory>
-#include <utility>
-
-#include <fuchsia/modular/auth/cpp/fidl.h>
-#include <fuchsia/net/oldhttp/cpp/fidl.h>
-#include <fuchsia/ui/viewsv1/cpp/fidl.h>
-#include <fuchsia/webview/cpp/fidl.h>
-#include <lib/async-loop/cpp/loop.h>
-#include <trace-provider/provider.h>
-
-#include "lib/async/cpp/operation.h"
-#include "lib/component/cpp/connect.h"
-#include "lib/component/cpp/startup_context.h"
-#include "lib/fidl/cpp/interface_request.h"
-#include "lib/fidl/cpp/optional.h"
-#include "lib/fidl/cpp/string.h"
-#include "lib/fsl/socket/strings.h"
-#include "lib/fsl/vmo/strings.h"
-#include "lib/fxl/command_line.h"
-#include "lib/fxl/files/directory.h"
-#include "lib/fxl/files/file.h"
-#include "lib/fxl/files/path.h"
-#include "lib/fxl/functional/make_copyable.h"
-#include "lib/fxl/log_settings_command_line.h"
-#include "lib/fxl/macros.h"
-#include "lib/fxl/strings/join_strings.h"
-#include "lib/fxl/strings/string_number_conversions.h"
-#include "lib/fxl/time/time_point.h"
-#include "lib/svc/cpp/services.h"
-#include "rapidjson/document.h"
-#include "rapidjson/error/en.h"
-#include "rapidjson/pointer.h"
-#include "rapidjson/prettywriter.h"
-#include "rapidjson/stringbuffer.h"
-#include "rapidjson/writer.h"
-#include "topaz/examples/oauth_token_manager/credentials_generated.h"
-
-namespace {
-
-namespace http = ::fuchsia::net::oldhttp;
-
-using ShortLivedTokenCallback =
- fit::function<void(std::string, fuchsia::modular::auth::AuthErr)>;
-
-using FirebaseTokenCallback = fit::function<void(
- fuchsia::modular::auth::FirebaseTokenPtr, fuchsia::modular::auth::AuthErr)>;
-
-namespace {
-
-// TODO(alhaad/ukode): Move the following to a configuration file.
-// NOTE: We are currently using a single client-id in Fuchsia. This is temporary
-// and will change in the future.
-constexpr char kClientId[] =
- "934259141868-rejmm4ollj1bs7th1vg2ur6antpbug79.apps.googleusercontent.com";
-constexpr char kGoogleOAuthAuthEndpoint[] =
- "https://accounts.google.com/o/oauth2/v2/auth";
-constexpr char kGoogleOAuthGlifParam[] = "false";
-constexpr char kGoogleOAuthTokenEndpoint[] =
- "https://www.googleapis.com/oauth2/v4/token";
-constexpr char kGoogleRevokeTokenEndpoint[] =
- "https://accounts.google.com/o/oauth2/revoke";
-constexpr char kGooglePeopleGetEndpoint[] =
- "https://www.googleapis.com/plus/v1/people/me";
-constexpr char kFirebaseAuthEndpoint[] =
- "https://www.googleapis.com/identitytoolkit/v3/relyingparty/"
- "verifyAssertion";
-constexpr char kRedirectUri[] = "com.google.fuchsia.auth:/oauth2redirect";
-constexpr char kCredentialsFile[] = "/data/v2/creds.db";
-constexpr char kWebViewUrl[] = "web_view";
-
-constexpr auto kScopes = {
- "openid",
- "email",
- "https://www.googleapis.com/auth/admin.directory.user.readonly",
- "https://www.googleapis.com/auth/assistant",
- "https://www.googleapis.com/auth/userinfo.email",
- "https://www.googleapis.com/auth/userinfo.profile",
- "https://www.googleapis.com/auth/youtube.readonly",
- "https://www.googleapis.com/auth/contacts",
- "https://www.googleapis.com/auth/drive.file",
- "https://www.googleapis.com/auth/plus.login",
- "https://www.googleapis.com/auth/calendar.readonly"};
-
-// Type of token requested.
-enum TokenType {
- ACCESS_TOKEN = 0,
- ID_TOKEN = 1,
- FIREBASE_JWT_TOKEN = 2,
-};
-
-// Adjusts the token expiration window by a small amount to proactively refresh
-// tokens before the expiry time limit has reached.
-const uint64_t kPaddingForTokenExpiryInS = 600;
-
-template <typename T>
-inline std::string JsonValueToPrettyString(const T& v) {
- rapidjson::StringBuffer buffer;
- rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
- v.Accept(writer);
- return buffer.GetString();
-}
-
-// TODO(alhaad/ukode): Don't use a hand-rolled version of this.
-std::string UrlEncode(const std::string& value) {
- std::ostringstream escaped;
- escaped.fill('0');
- escaped << std::hex;
-
- for (char c : value) {
- // Keep alphanumeric and other accepted characters intact
- if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '=' ||
- c == '&' || c == '+') {
- escaped << c;
- continue;
- }
-
- // Any other characters are percent-encoded
- escaped << std::uppercase;
- escaped << '%' << std::setw(2) << int(static_cast<unsigned char>(c));
- escaped << std::nouppercase;
- }
-
- return escaped.str();
-}
-
-// Checks the supplied Google authentication URL. If the URL indicated the user
-// has aborted the flow or an error occured these are reported as error
-// statuses, otherwise a status of OK is returned. If the URL contains an auth
-// code query parameter, this will be returned in |auth_code|.
-fuchsia::modular::auth::Status ParseAuthCodeFromUrl(const std::string& url,
- std::string& auth_code) {
- static const std::string success_prefix =
- std::string{kRedirectUri} + "?code=";
- static const std::string cancel_prefix =
- std::string{kRedirectUri} + "?error=access_denied";
-
- if (url.find(cancel_prefix) == 0) {
- return fuchsia::modular::auth::Status::USER_CANCELLED;
- }
- if (url.find(success_prefix) != 0) {
- // The authentication process is still ongoing.
- return fuchsia::modular::auth::Status::OK;
- }
-
- // Take everything up to the next query parameter or hash fragment.
- auto end_char = url.find_first_of("#&", success_prefix.size());
- auto length = end_char == std::string::npos
- ? std::string::npos
- : end_char - success_prefix.size();
- auto code = url.substr(success_prefix.size(), length);
-
- // Note: The full auth stack normalizes the code here since the GLIF endpoint
- // URL-encodes the slash prefix. We omit this normalization since our endpoint
- // doesn't have that behavior.
-
- if (code.empty()) {
- return fuchsia::modular::auth::Status::OAUTH_SERVER_ERROR;
- } else {
- auth_code = code;
- return fuchsia::modular::auth::Status::OK;
- }
-}
-
-// Read the contents of |kCredentialsFile| into the supplied buffer, validates
-// that these contents are a valid credentials file, and then returns a
-// |::auth::CredentialStore| pointer to the contents.
-const ::auth::CredentialStore* ParseCredsFile(std::string* buffer) {
- if (!files::IsFile(kCredentialsFile)) {
- return nullptr;
- }
-
- if (!files::ReadFileToString(kCredentialsFile, buffer)) {
- FXL_LOG(WARNING) << "Unable to read user credential file at: "
- << kCredentialsFile;
- return nullptr;
- }
-
- flatbuffers::Verifier verifier(
- reinterpret_cast<const unsigned char*>(buffer->data()), buffer->size());
- if (!::auth::VerifyCredentialStoreBuffer(verifier)) {
- FXL_LOG(WARNING) << "Unable to verify credentials buffer";
- return nullptr;
- }
-
- return ::auth::GetCredentialStore(buffer->data());
-}
-
-// Serializes |::auth::CredentialStore| to the |kCredentialsFIle| on disk.
-bool WriteCredsFile(const std::string& serialized_creds) {
- // verify file before saving
- flatbuffers::Verifier verifier(
- reinterpret_cast<const unsigned char*>(serialized_creds.data()),
- serialized_creds.size());
- if (!::auth::VerifyCredentialStoreBuffer(verifier)) {
- FXL_LOG(ERROR) << "Unable to verify credentials buffer:"
- << serialized_creds.data();
- return false;
- }
-
- if (!files::CreateDirectory(files::GetDirectoryName(kCredentialsFile))) {
- FXL_LOG(ERROR) << "Unable to create directory for " << kCredentialsFile;
- return false;
- }
-
- if (!files::WriteFile(kCredentialsFile, serialized_creds.data(),
- serialized_creds.size())) {
- FXL_LOG(ERROR) << "Unable to write file " << kCredentialsFile;
- return false;
- }
-
- return true;
-}
-
-// Fetch user's refresh token from local credential store. In case of errors
-// or account not found, an empty token is returned.
-std::string GetRefreshTokenFromCredsFile(const std::string& account_id) {
- if (account_id.empty()) {
- FXL_LOG(ERROR) << "Account id is empty.";
- return "";
- }
-
- std::string file_buffer;
- const ::auth::CredentialStore* credentials_storage =
- ParseCredsFile(&file_buffer);
- if (credentials_storage == nullptr) {
- FXL_LOG(ERROR) << "Failed to parse credentials.";
- return "";
- }
-
- for (const auto* credential : *credentials_storage->creds()) {
- if (credential->account_id()->str() == account_id) {
- for (const auto* token : *credential->tokens()) {
- switch (token->identity_provider()) {
- case ::auth::IdentityProvider_GOOGLE:
- return token->refresh_token()->str();
- default:
- FXL_LOG(WARNING) << "Unrecognized IdentityProvider"
- << token->identity_provider();
- }
- }
- }
- }
- return "";
-}
-
-// Exactly one of success_callback and failure_callback is ever invoked.
-void Post(const std::string& request_body, http::URLLoader* const url_loader,
- const std::string& url, const std::function<void()>& success_callback,
- const std::function<void(fuchsia::modular::auth::Status,
- std::string)>& failure_callback,
- const std::function<bool(rapidjson::Document)>& set_token_callback) {
- std::string encoded_request_body(request_body);
- if (url.find(kFirebaseAuthEndpoint) == std::string::npos) {
- encoded_request_body = UrlEncode(request_body);
- }
-
- fsl::SizedVmo data;
- auto result = fsl::VmoFromString(encoded_request_body, &data);
- FXL_VLOG(1) << "Post Data:" << encoded_request_body;
- FXL_DCHECK(result);
-
- http::URLRequest request;
- request.url = url;
- request.method = "POST";
- request.auto_follow_redirects = true;
-
- // Content-length header.
- http::HttpHeader content_length_header;
- content_length_header.name = "Content-length";
- uint64_t data_size = encoded_request_body.length();
- content_length_header.value = fxl::NumberToString(data_size);
- request.headers.push_back(std::move(content_length_header));
-
- // content-type header.
- http::HttpHeader content_type_header;
- content_type_header.name = "content-type";
- if (url.find("identitytoolkit") != std::string::npos) {
- // set accept header
- http::HttpHeader accept_header;
- accept_header.name = "accept";
- accept_header.value = "application/json";
- request.headers.push_back(std::move(accept_header));
-
- // set content_type header
- content_type_header.value = "application/json";
- } else {
- content_type_header.value = "application/x-www-form-urlencoded";
- }
- request.headers.push_back(std::move(content_type_header));
-
- request.body = http::URLBody::New();
- request.body->set_buffer(std::move(data).ToTransport());
-
- url_loader->Start(std::move(request), [success_callback, failure_callback,
- set_token_callback](
- http::URLResponse response) {
- FXL_VLOG(1) << "URL Loader response:"
- << std::to_string(response.status_code);
-
- if (response.error) {
- failure_callback(
- fuchsia::modular::auth::Status::NETWORK_ERROR,
- "POST error: " + std::to_string(response.error->code) +
- " , with description: " + response.error->description->data());
- return;
- }
-
- std::string response_body;
- if (response.body) {
- FXL_DCHECK(response.body->is_stream());
- // TODO(alhaad/ukode): Use non-blocking variant.
- if (!fsl::BlockingCopyToString(std::move(response.body->stream()),
- &response_body)) {
- failure_callback(fuchsia::modular::auth::Status::NETWORK_ERROR,
- "Failed to read response from socket with status:" +
- std::to_string(response.status_code));
- return;
- }
- }
-
- if (response.status_code != 200) {
- failure_callback(
- fuchsia::modular::auth::Status::OAUTH_SERVER_ERROR,
- "Received status code:" + std::to_string(response.status_code) +
- ", and response body:" + response_body);
- return;
- }
-
- rapidjson::Document doc;
- rapidjson::ParseResult ok = doc.Parse(response_body);
- if (!ok) {
- std::string error_msg = GetParseError_En(ok.Code());
- failure_callback(fuchsia::modular::auth::Status::BAD_RESPONSE,
- "JSON parse error: " + error_msg);
- return;
- };
- auto result = set_token_callback(std::move(doc));
- if (result) {
- success_callback();
- } else {
- failure_callback(fuchsia::modular::auth::Status::BAD_RESPONSE,
- "Invalid response: " + JsonValueToPrettyString(doc));
- }
- return;
- });
-}
-
-// Exactly one of success_callback and failure_callback is ever invoked.
-void Get(http::URLLoader* const url_loader, const std::string& url,
- const std::string& access_token,
- const std::function<void()>& success_callback,
- const std::function<void(fuchsia::modular::auth::Status status,
- std::string)>& failure_callback,
- const std::function<bool(rapidjson::Document)>& set_token_callback) {
- http::URLRequest request;
- request.url = url;
- request.method = "GET";
- request.auto_follow_redirects = true;
-
- // Set Authorization header.
- http::HttpHeader auth_header;
- auth_header.name = "Authorization";
- auth_header.value = "Bearer " + access_token;
- request.headers.push_back(std::move(auth_header));
-
- // set content-type header to json.
- http::HttpHeader content_type_header;
- content_type_header.name = "content-type";
- content_type_header.value = "application/json";
-
- // set accept header to json
- http::HttpHeader accept_header;
- accept_header.name = "accept";
- accept_header.value = "application/json";
- request.headers.push_back(std::move(accept_header));
-
- url_loader->Start(std::move(request), [success_callback, failure_callback,
- set_token_callback](
- http::URLResponse response) {
- if (response.error) {
- failure_callback(
- fuchsia::modular::auth::Status::NETWORK_ERROR,
- "GET error: " + std::to_string(response.error->code) +
- " ,with description: " + response.error->description->data());
- return;
- }
-
- std::string response_body;
- if (response.body) {
- FXL_DCHECK(response.body->is_stream());
- // TODO(alhaad/ukode): Use non-blocking variant.
- if (!fsl::BlockingCopyToString(std::move(response.body->stream()),
- &response_body)) {
- failure_callback(fuchsia::modular::auth::Status::NETWORK_ERROR,
- "Failed to read response from socket with status:" +
- std::to_string(response.status_code));
- return;
- }
- }
-
- if (response.status_code != 200) {
- failure_callback(
- fuchsia::modular::auth::Status::OAUTH_SERVER_ERROR,
- "Status code: " + std::to_string(response.status_code) +
- " while fetching tokens with error description:" + response_body);
- return;
- }
-
- rapidjson::Document doc;
- rapidjson::ParseResult ok = doc.Parse(response_body);
- if (!ok) {
- std::string error_msg = GetParseError_En(ok.Code());
- failure_callback(fuchsia::modular::auth::Status::BAD_RESPONSE,
- "JSON parse error: " + error_msg);
- return;
- };
- auto result = set_token_callback(std::move(doc));
- if (result) {
- success_callback();
- } else {
- failure_callback(fuchsia::modular::auth::Status::BAD_RESPONSE,
- "Invalid response: " + JsonValueToPrettyString(doc));
- }
- });
-}
-
-} // namespace
-
-// Implementation of the OAuth Token Manager app.
-class OAuthTokenManagerApp : fuchsia::modular::auth::AccountProvider {
- public:
- OAuthTokenManagerApp(async::Loop* loop);
-
- private:
- // |AccountProvider|
- void Initialize(
- fidl::InterfaceHandle<fuchsia::modular::auth::AccountProviderContext>
- provider) override;
-
- // |AccountProvider|
- void Terminate() override;
-
- // |AccountProvider|
- void AddAccount(fuchsia::modular::auth::IdentityProvider identity_provider,
- AddAccountCallback callback) override;
-
- // |AccountProvider|
- void RemoveAccount(fuchsia::modular::auth::Account account, bool revoke_all,
- RemoveAccountCallback callback) override;
-
- // |AccountProvider|
- void GetTokenProviderFactory(
- fidl::StringPtr account_id,
- fidl::InterfaceRequest<fuchsia::modular::auth::TokenProviderFactory>
- request) override;
-
- // Generate a random account id.
- std::string GenerateAccountId();
-
- // Refresh access and id tokens.
- void RefreshToken(const std::string& account_id, const TokenType& token_type,
- ShortLivedTokenCallback callback);
-
- // Refresh firebase tokens.
- void RefreshFirebaseToken(const std::string& account_id,
- const std::string& firebase_api_key,
- const std::string& id_token,
- FirebaseTokenCallback callback);
- async::Loop* const loop_;
-
- std::shared_ptr<component::StartupContext> startup_context_;
-
- fuchsia::modular::auth::AccountProviderContextPtr account_provider_context_;
-
- fidl::Binding<fuchsia::modular::auth::AccountProvider> binding_;
-
- class TokenProviderFactoryImpl;
- // account_id -> TokenProviderFactoryImpl
- std::unordered_map<std::string, std::unique_ptr<TokenProviderFactoryImpl>>
- token_provider_factory_impls_;
-
- // In-memory cache for short lived firebase auth id tokens. These tokens get
- // reset on system reboots. Tokens are cached based on the expiration time
- // set by the Firebase servers. Cache is indexed by firebase api keys.
- struct FirebaseAuthToken {
- uint64_t creation_ts;
- uint64_t expires_in;
- std::string id_token;
- std::string local_id;
- std::string email;
- };
-
- // In-memory cache for short lived oauth tokens that resets on system reboots.
- // Tokens are cached based on the expiration time set by the Identity
- // provider. Cache is indexed by unique account_ids.
- struct ShortLivedToken {
- uint64_t creation_ts;
- uint64_t expires_in;
- std::string access_token;
- std::string id_token;
- std::map<std::string, FirebaseAuthToken> fb_tokens_;
- };
- std::map<std::string, ShortLivedToken> oauth_tokens_;
-
- // We are using operations here not to guard state across asynchronous calls
- // but rather to clean up state after an 'operation' is done.
- // TODO(ukode): All operations are running in a queue now which is
- // inefficient because we block on operations that could be done in parallel.
- // Instead we may want to create an operation for what
- // TokenProviderFactoryImpl::GetFirebaseAuthToken() is doing in an sub
- // operation queue.
- modular::OperationQueue operation_queue_;
-
- class GoogleFirebaseTokensCall;
- class GoogleOAuthTokensCall;
- class GoogleUserCredsCall;
- class GoogleRevokeTokensCall;
- class GoogleProfileAttributesCall;
-
- FXL_DISALLOW_COPY_AND_ASSIGN(OAuthTokenManagerApp);
-};
-
-class OAuthTokenManagerApp::TokenProviderFactoryImpl
- : fuchsia::modular::auth::TokenProviderFactory,
- fuchsia::modular::auth::TokenProvider {
- public:
- TokenProviderFactoryImpl(
- const fidl::StringPtr& account_id, OAuthTokenManagerApp* const app,
- fidl::InterfaceRequest<fuchsia::modular::auth::TokenProviderFactory>
- request)
- : account_id_(account_id), binding_(this, std::move(request)), app_(app) {
- binding_.set_error_handler([this](zx_status_t status) {
- app_->token_provider_factory_impls_.erase(account_id_);
- });
- }
-
- private:
- // |TokenProviderFactory|
- void GetTokenProvider(
- fidl::StringPtr /*application_url*/,
- fidl::InterfaceRequest<fuchsia::modular::auth::TokenProvider> request)
- override {
- // TODO(alhaad/ukode): Current implementation is agnostic about which
- // agent is requesting what token. Fix this.
- token_provider_bindings_.AddBinding(this, std::move(request));
- }
-
- // |TokenProvider|
- void GetAccessToken(GetAccessTokenCallback callback) override {
- FXL_DCHECK(app_);
- app_->RefreshToken(account_id_, ACCESS_TOKEN, std::move(callback));
- }
-
- // |TokenProvider|
- void GetIdToken(GetIdTokenCallback callback) override {
- FXL_DCHECK(app_);
- app_->RefreshToken(account_id_, ID_TOKEN, std::move(callback));
- }
-
- // |TokenProvider|
- void GetFirebaseAuthToken(fidl::StringPtr firebase_api_key,
- GetFirebaseAuthTokenCallback callback) override {
- FXL_DCHECK(app_);
-
- // Oauth id token is used as input to fetch firebase auth token.
- GetIdToken([this, firebase_api_key = firebase_api_key,
- callback = std::move(callback)](
- const std::string id_token,
- const fuchsia::modular::auth::AuthErr auth_err) mutable {
- if (auth_err.status != fuchsia::modular::auth::Status::OK) {
- FXL_LOG(ERROR) << "Error in refreshing Idtoken.";
- callback(nullptr, std::move(auth_err));
- return;
- }
-
- app_->RefreshFirebaseToken(account_id_, firebase_api_key, id_token,
- std::move(callback));
- });
- }
-
- // |TokenProvider|
- void GetClientId(GetClientIdCallback callback) override {
- callback(kClientId);
- }
-
- std::string account_id_;
- fidl::Binding<fuchsia::modular::auth::TokenProviderFactory> binding_;
- fidl::BindingSet<fuchsia::modular::auth::TokenProvider>
- token_provider_bindings_;
-
- OAuthTokenManagerApp* const app_;
-
- FXL_DISALLOW_COPY_AND_ASSIGN(TokenProviderFactoryImpl);
-};
-
-class OAuthTokenManagerApp::GoogleFirebaseTokensCall
- : public modular::Operation<fuchsia::modular::auth::FirebaseTokenPtr,
- fuchsia::modular::auth::AuthErr> {
- public:
- GoogleFirebaseTokensCall(std::string account_id, std::string firebase_api_key,
- std::string id_token,
- OAuthTokenManagerApp* const app,
- FirebaseTokenCallback callback)
- : Operation("OAuthTokenManagerApp::GoogleFirebaseTokensCall",
- fxl::MakeCopyable(std::move(callback))),
- account_id_(std::move(account_id)),
- firebase_api_key_(std::move(firebase_api_key)),
- id_token_(std::move(id_token)),
- app_(app) {}
-
- private:
- void Run() override {
- FlowToken flow{this, &firebase_token_, &auth_err_};
-
- if (account_id_.empty()) {
- Failure(flow, fuchsia::modular::auth::Status::BAD_REQUEST,
- "Account id is empty");
- return;
- }
-
- if (firebase_api_key_.empty()) {
- Failure(flow, fuchsia::modular::auth::Status::BAD_REQUEST,
- "Firebase Api key is empty");
- return;
- }
-
- if (id_token_.empty()) {
- // TODO(ukode): Need to differentiate between deleted users, users that
- // are not provisioned and Guest mode users. For now, return empty
- // response in such cases as there is no clear way to differentiate
- // between regular users and guest users.
- Success(flow);
- return;
- }
-
- // check cache for existing firebase tokens.
- bool cacheValid = IsCacheValid();
- if (!cacheValid) {
- FetchFirebaseToken(flow);
- } else {
- Success(flow);
- }
- }
-
- // Fetch fresh firebase auth token by exchanging idToken from Google.
- void FetchFirebaseToken(FlowToken flow) {
- FXL_DCHECK(!id_token_.empty());
- FXL_DCHECK(!firebase_api_key_.empty());
-
- // JSON post request body
- const std::string json_request_body =
- R"({ "postBody": "id_token=)" + id_token_ +
- "&providerId=google.com\"," + " \"returnIdpCredential\": true," +
- " \"returnSecureToken\": true," +
- R"( "requestUri": "http://localhost")" + "}";
-
- app_->startup_context_->ConnectToEnvironmentService(
- http_service_.NewRequest());
- http_service_->CreateURLLoader(url_loader_.NewRequest());
-
- std::string url(kFirebaseAuthEndpoint);
- url += "?key=" + UrlEncode(firebase_api_key_);
-
- // This flow branches below, so we need to put it in a shared
- // container from which it can be removed once for all branches.
- FlowTokenHolder branch{flow};
-
- Post(json_request_body, url_loader_.get(), url,
- [this, branch] {
- std::unique_ptr<FlowToken> flow = branch.Continue();
- FXL_CHECK(flow);
- Success(*flow);
- },
- [this, branch](const fuchsia::modular::auth::Status status,
- const std::string error_message) {
- std::unique_ptr<FlowToken> flow = branch.Continue();
- FXL_CHECK(flow);
- Failure(*flow, status, error_message);
- },
- [this](rapidjson::Document doc) {
- return GetFirebaseToken(std::move(doc));
- });
- }
-
- // Parses firebase jwt auth token from firebase auth endpoint response and
- // saves it to local token in-memory cache.
- bool GetFirebaseToken(rapidjson::Document jwt_token) {
- FXL_VLOG(1) << "Firebase Token: " << JsonValueToPrettyString(jwt_token);
-
- if (!jwt_token.HasMember("idToken") || !jwt_token.HasMember("localId") ||
- !jwt_token.HasMember("email") || !jwt_token.HasMember("expiresIn")) {
- FXL_LOG(ERROR)
- << "Firebase Token returned from server is missing "
- << "either idToken or email or localId fields. Returned token: "
- << JsonValueToPrettyString(jwt_token);
- return false;
- }
-
- uint64_t expiresIn;
- std::istringstream(jwt_token["expiresIn"].GetString()) >> expiresIn;
-
- app_->oauth_tokens_[account_id_].fb_tokens_[firebase_api_key_] = {
- static_cast<uint64_t>(fxl::TimePoint::Now().ToEpochDelta().ToSeconds()),
- expiresIn,
- jwt_token["idToken"].GetString(),
- jwt_token["localId"].GetString(),
- jwt_token["email"].GetString(),
- };
- return true;
- }
-
- // Returns true if the firebase tokens stored in cache are still valid and
- // not expired.
- bool IsCacheValid() {
- FXL_DCHECK(app_);
- FXL_DCHECK(!account_id_.empty());
- FXL_DCHECK(!firebase_api_key_.empty());
-
- if (app_->oauth_tokens_[account_id_].fb_tokens_.find(firebase_api_key_) ==
- app_->oauth_tokens_[account_id_].fb_tokens_.end()) {
- FXL_VLOG(1) << "Firebase api key: [" << firebase_api_key_
- << "] not found in cache.";
- return false;
- }
-
- uint64_t current_ts = fxl::TimePoint::Now().ToEpochDelta().ToSeconds();
- auto fb_token =
- app_->oauth_tokens_[account_id_].fb_tokens_[firebase_api_key_];
- uint64_t creation_ts = fb_token.creation_ts;
- uint64_t token_expiry = fb_token.expires_in;
- if ((current_ts - creation_ts) <
- (token_expiry - kPaddingForTokenExpiryInS)) {
- FXL_VLOG(1) << "Returning firebase token for api key ["
- << firebase_api_key_ << "] from cache. ";
- return true;
- }
-
- return false;
- }
-
- void Success(FlowToken /*flow*/) {
- // Set firebase token
- firebase_token_ = fuchsia::modular::auth::FirebaseToken::New();
- if (id_token_.empty()) {
- firebase_token_->id_token = "";
- firebase_token_->local_id = "";
- firebase_token_->email = "";
- } else {
- auto fb_token =
- app_->oauth_tokens_[account_id_].fb_tokens_[firebase_api_key_];
- firebase_token_->id_token = fb_token.id_token;
- firebase_token_->local_id = fb_token.local_id;
- firebase_token_->email = fb_token.email;
- }
-
- // Set status to success
- auth_err_.status = fuchsia::modular::auth::Status::OK;
- auth_err_.message = "";
- }
-
- void Failure(FlowToken /*flow*/, const fuchsia::modular::auth::Status& status,
- const std::string& error_message) {
- FXL_LOG(ERROR) << "Failed with error status:" << fidl::ToUnderlying(status)
- << " ,and message:" << error_message;
- auth_err_.status = status;
- auth_err_.message = error_message;
- }
-
- const std::string account_id_;
- const std::string firebase_api_key_;
- const std::string id_token_;
- OAuthTokenManagerApp* const app_;
-
- fuchsia::modular::auth::FirebaseTokenPtr firebase_token_;
- fuchsia::modular::auth::AuthErr auth_err_;
-
- http::HttpServicePtr http_service_;
- http::URLLoaderPtr url_loader_;
-
- FXL_DISALLOW_COPY_AND_ASSIGN(GoogleFirebaseTokensCall);
-};
-
-class OAuthTokenManagerApp::GoogleOAuthTokensCall
- : public modular::Operation<fidl::StringPtr,
- fuchsia::modular::auth::AuthErr> {
- public:
- GoogleOAuthTokensCall(std::string account_id, const TokenType& token_type,
- OAuthTokenManagerApp* const app,
- ShortLivedTokenCallback callback)
- : Operation("OAuthTokenManagerApp::GoogleOAuthTokensCall",
- fxl::MakeCopyable(std::move(callback))),
- account_id_(std::move(account_id)),
- token_type_(token_type),
- app_(app) {}
-
- private:
- void Run() override {
- FlowToken flow{this, &result_, &auth_err_};
-
- if (account_id_.empty()) {
- Failure(flow, fuchsia::modular::auth::Status::BAD_REQUEST,
- "Account id is empty.");
- return;
- }
-
- FXL_VLOG(1) << "Fetching access/id tokens for Account_ID:" << account_id_;
-
- // Use an entry from the cache if one exists
- bool cacheValid = IsCacheValid();
- if (cacheValid) {
- Success(flow); // fetching tokens from local cache.
- return;
- }
-
- // Check if the user has a stored refesh token
- const std::string refresh_token = GetRefreshTokenFromCredsFile(account_id_);
- if (refresh_token.empty()) {
- // TODO(ukode): Need to differentiate between deleted users, users that
- // are not provisioned and Guest mode users. For now, return empty
- // response in such cases as there is no clear way to differentiate
- // between regular users and guest users.
- Success(flow);
- } else {
- // Use this refresh token to generate and cache new short lived tokens.
- FetchAccessAndIdToken(refresh_token, flow);
- }
- }
-
- // Fetch fresh access and id tokens by exchanging refresh token from Google
- // token endpoint.
- void FetchAccessAndIdToken(const std::string& refresh_token, FlowToken flow) {
- FXL_CHECK(!refresh_token.empty());
-
- const std::string request_body = "refresh_token=" + refresh_token +
- "&client_id=" + kClientId +
- "&grant_type=refresh_token";
-
- app_->startup_context_->ConnectToEnvironmentService(
- http_service_.NewRequest());
- http_service_->CreateURLLoader(url_loader_.NewRequest());
-
- // This flow exlusively branches below, so we need to put it in a shared
- // container from which it can be removed once for all branches.
- FlowTokenHolder branch{flow};
-
- Post(request_body, url_loader_.get(), kGoogleOAuthTokenEndpoint,
- [this, branch] {
- std::unique_ptr<FlowToken> flow = branch.Continue();
- FXL_CHECK(flow);
- Success(*flow);
- },
- [this, branch](const fuchsia::modular::auth::Status status,
- const std::string error_message) {
- std::unique_ptr<FlowToken> flow = branch.Continue();
- FXL_CHECK(flow);
- Failure(*flow, status, error_message);
- },
- [this](rapidjson::Document doc) {
- return GetShortLivedTokens(std::move(doc));
- });
- }
-
- // Parse access and id tokens from OAUth endpoints into local token in-memory
- // cache.
- bool GetShortLivedTokens(rapidjson::Document tokens) {
- if (!tokens.HasMember("access_token")) {
- FXL_LOG(ERROR) << "Tokens returned from server does not contain "
- << "access_token. Returned token: "
- << JsonValueToPrettyString(tokens);
- return false;
- };
-
- if ((token_type_ == ID_TOKEN) && !tokens.HasMember("id_token")) {
- FXL_LOG(ERROR) << "Tokens returned from server does not contain "
- << "id_token. Returned token: "
- << JsonValueToPrettyString(tokens);
- return false;
- }
-
- // Add the token generation timestamp to |tokens| for caching.
- uint64_t creation_ts = fxl::TimePoint::Now().ToEpochDelta().ToSeconds();
- app_->oauth_tokens_[account_id_] = {
- creation_ts,
- tokens["expires_in"].GetUint64(),
- tokens["access_token"].GetString(),
- tokens["id_token"].GetString(),
- std::map<std::string, FirebaseAuthToken>(),
- };
-
- return true;
- }
-
- // Returns true if the access and idtokens stored in cache are still valid and
- // not expired.
- bool IsCacheValid() {
- FXL_DCHECK(app_);
- FXL_DCHECK(!account_id_.empty());
-
- if (app_->oauth_tokens_.find(account_id_) == app_->oauth_tokens_.end()) {
- FXL_VLOG(1) << "Account: [" << account_id_ << "] not found in cache.";
- return false;
- }
-
- uint64_t current_ts = fxl::TimePoint::Now().ToEpochDelta().ToSeconds();
- uint64_t creation_ts = app_->oauth_tokens_[account_id_].creation_ts;
- uint64_t token_expiry = app_->oauth_tokens_[account_id_].expires_in;
- if ((current_ts - creation_ts) <
- (token_expiry - kPaddingForTokenExpiryInS)) {
- FXL_VLOG(1) << "Returning access/id tokens for account [" << account_id_
- << "] from cache. ";
- return true;
- }
-
- return false;
- }
-
- void Success(FlowToken flow) {
- if (app_->oauth_tokens_.find(account_id_) == app_->oauth_tokens_.end()) {
- // In guest mode, return empty tokens.
- result_ = "";
- } else {
- switch (token_type_) {
- case ACCESS_TOKEN:
- result_ = app_->oauth_tokens_[account_id_].access_token;
- break;
- case ID_TOKEN:
- result_ = app_->oauth_tokens_[account_id_].id_token;
- break;
- case FIREBASE_JWT_TOKEN:
- Failure(flow, fuchsia::modular::auth::Status::INTERNAL_ERROR,
- "invalid token type");
- }
- }
-
- // Set status to success
- auth_err_.status = fuchsia::modular::auth::Status::OK;
- auth_err_.message = "";
- }
-
- void Failure(FlowToken /*flow*/, const fuchsia::modular::auth::Status& status,
- const std::string& error_message) {
- FXL_LOG(ERROR) << "Failed with error status:" << fidl::ToUnderlying(status)
- << " ,and message:" << error_message;
- auth_err_.status = status;
- auth_err_.message = error_message;
- }
-
- const std::string account_id_;
- const std::string firebase_api_key_;
- TokenType token_type_;
- OAuthTokenManagerApp* const app_;
-
- http::HttpServicePtr http_service_;
- http::URLLoaderPtr url_loader_;
-
- fidl::StringPtr result_;
- fuchsia::modular::auth::AuthErr auth_err_;
-
- FXL_DISALLOW_COPY_AND_ASSIGN(GoogleOAuthTokensCall);
-};
-
-// TODO(alhaad): Use variadic template in |Operation|. That way, parameters to
-// |callback| can be returned as parameters to |Done()|.
-class OAuthTokenManagerApp::GoogleUserCredsCall
- : public modular::Operation<>,
- fuchsia::webview::WebRequestDelegate {
- public:
- GoogleUserCredsCall(fuchsia::modular::auth::AccountPtr account,
- OAuthTokenManagerApp* const app,
- AddAccountCallback callback)
- : Operation("OAuthTokenManagerApp::GoogleUserCredsCall", [] {}),
- account_(std::move(account)),
- app_(app),
- callback_(std::move(callback)) {}
-
- private:
- // |Operation|
- void Run() override {
- // No FlowToken used here; calling Done() directly is more suitable,
- // because of the flow of control through
- // fuchsia::webview::WebRequestDelegate.
-
- auto view_owner = SetupWebView();
-
- // Set a delegate which will parse incoming URLs for authorization code.
- // TODO(alhaad/ukode): We need to set a timout here in-case we do not get
- // the code.
- fuchsia::webview::WebRequestDelegatePtr web_request_delegate;
- web_request_delegate_bindings_.AddBinding(
- this, web_request_delegate.NewRequest());
- web_view_->SetWebRequestDelegate(std::move(web_request_delegate));
-
- web_view_->ClearCookies();
-
- const std::vector<std::string> scopes(kScopes.begin(), kScopes.end());
- std::string joined_scopes = fxl::JoinStrings(scopes, "+");
-
- std::string url = kGoogleOAuthAuthEndpoint;
- url += "?scope=" + joined_scopes;
- url += "&response_type=code&redirect_uri=";
- url += kRedirectUri;
- url += "&glif=";
- url += kGoogleOAuthGlifParam;
- url += "&client_id=";
- url += kClientId;
-
- web_view_->SetUrl(url);
-
- app_->account_provider_context_->GetAuthenticationContext(
- account_->id, auth_context_.NewRequest());
-
- auth_context_.set_error_handler([this](zx_status_t status) {
- callback_(nullptr, "Overlay cancelled by base shell.");
- Done();
- });
- auth_context_->StartOverlay(std::move(view_owner));
- }
-
- // |fuchsia::webview::WebRequestDelegate|
- void WillSendRequest(fidl::StringPtr incoming_url) override {
- std::string auth_code;
- fuchsia::modular::auth::Status status =
- ParseAuthCodeFromUrl(incoming_url.get(), auth_code);
-
- if (status != fuchsia::modular::auth::Status::OK) {
- Failure(status, "User cancelled OAuth flow");
- } else if (auth_code.empty()) {
- // Authentication is ongoing.
- return;
- }
-
- // User accepted OAuth permissions - close the webview and exchange auth
- // code to long lived credential.
- // Also, de-register previously registered error callbacks since calling
- // StopOverlay() might cause this connection to be closed.
- auth_context_.set_error_handler([](zx_status_t status) {});
- auth_context_->StopOverlay();
-
- const std::string request_body =
- "code=" + auth_code + "&redirect_uri=" + kRedirectUri +
- "&client_id=" + kClientId + "&grant_type=authorization_code";
-
- app_->startup_context_->ConnectToEnvironmentService(
- http_service_.NewRequest());
- http_service_->CreateURLLoader(url_loader_.NewRequest());
-
- Post(request_body, url_loader_.get(), kGoogleOAuthTokenEndpoint,
- [this] { Success(); },
- [this](const fuchsia::modular::auth::Status status,
- const std::string error_message) {
- Failure(status, error_message);
- },
- [this](rapidjson::Document doc) {
- return ProcessCredentials(std::move(doc));
- });
- }
-
- // Parses refresh tokens from auth endpoint response and persists it in
- // |kCredentialsFile|.
- bool ProcessCredentials(rapidjson::Document tokens) {
- if (!tokens.HasMember("refresh_token") ||
- !tokens.HasMember("access_token")) {
- FXL_LOG(ERROR) << "Tokens returned from server does not contain "
- << "refresh_token or access_token. Returned token: "
- << JsonValueToPrettyString(tokens);
- return false;
- };
-
- if (!SaveCredentials(tokens["refresh_token"].GetString())) {
- return false;
- }
-
- // Store short lived tokens local in-memory cache.
- uint64_t creation_ts = fxl::TimePoint::Now().ToEpochDelta().ToSeconds();
- app_->oauth_tokens_[account_->id] = {
- creation_ts,
- tokens["expires_in"].GetUint64(),
- tokens["access_token"].GetString(),
- tokens["id_token"].GetString(),
- std::map<std::string, FirebaseAuthToken>(),
- };
- return true;
- }
-
- // Saves new credentials to the persistent creds storage file.
- bool SaveCredentials(const std::string& refresh_token) {
- flatbuffers::FlatBufferBuilder builder;
- std::vector<flatbuffers::Offset<::auth::UserCredential>> creds;
-
- std::string file_buffer;
- const ::auth::CredentialStore* file_creds = ParseCredsFile(&file_buffer);
- if (file_creds != nullptr) {
- // Reserialize existing users.
- for (const auto* cred : *file_creds->creds()) {
- if (cred->account_id()->str() == account_->id) {
- // Update existing credentials
- continue;
- }
-
- std::vector<flatbuffers::Offset<::auth::IdpCredential>> idp_creds;
- for (const auto* idp_cred : *cred->tokens()) {
- idp_creds.push_back(::auth::CreateIdpCredential(
- builder, idp_cred->identity_provider(),
- builder.CreateString(idp_cred->refresh_token())));
- }
-
- creds.push_back(::auth::CreateUserCredential(
- builder, builder.CreateString(cred->account_id()),
- builder.CreateVector<flatbuffers::Offset<::auth::IdpCredential>>(
- idp_creds)));
- }
- }
-
- // add the new credential for |account_->id|.
- std::vector<flatbuffers::Offset<::auth::IdpCredential>> new_idp_creds;
- new_idp_creds.push_back(
- ::auth::CreateIdpCredential(builder, ::auth::IdentityProvider_GOOGLE,
- builder.CreateString(refresh_token)));
-
- creds.push_back(::auth::CreateUserCredential(
- builder, builder.CreateString(account_->id),
- builder.CreateVector<flatbuffers::Offset<::auth::IdpCredential>>(
- new_idp_creds)));
-
- builder.Finish(
- ::auth::CreateCredentialStore(builder, builder.CreateVector(creds)));
-
- std::string new_serialized_creds = std::string(
- reinterpret_cast<const char*>(builder.GetCurrentBufferPointer()),
- builder.GetSize());
-
- return WriteCredsFile(new_serialized_creds);
- }
-
- void Success() {
- callback_(std::move(account_), nullptr);
- Done();
- }
-
- void Failure(const fuchsia::modular::auth::Status& status,
- const std::string& error_message) {
- FXL_LOG(ERROR) << "Failed with error status:" << fidl::ToUnderlying(status)
- << " ,and message:" << error_message;
- callback_(nullptr, error_message);
- auth_context_.set_error_handler([](zx_status_t status) {});
- auth_context_->StopOverlay();
- Done();
- }
-
- fuchsia::ui::viewsv1token::ViewOwnerPtr SetupWebView() {
- component::Services web_view_services;
- fuchsia::sys::LaunchInfo web_view_launch_info;
- web_view_launch_info.url = kWebViewUrl;
- web_view_launch_info.directory_request = web_view_services.NewRequest();
- app_->startup_context_->launcher()->CreateComponent(
- std::move(web_view_launch_info), web_view_controller_.NewRequest());
- web_view_controller_.set_error_handler([this](zx_status_t status) {
- FXL_CHECK(false) << "web_view not found at " << kWebViewUrl << ".";
- });
-
- fuchsia::ui::viewsv1token::ViewOwnerPtr view_owner;
- fuchsia::ui::viewsv1::ViewProviderPtr view_provider;
- web_view_services.ConnectToService(view_provider.NewRequest());
- fuchsia::sys::ServiceProviderPtr web_view_moz_services;
- view_provider->CreateView(view_owner.NewRequest(),
- web_view_moz_services.NewRequest());
-
- component::ConnectToService(web_view_moz_services.get(),
- web_view_.NewRequest());
-
- return view_owner;
- }
-
- fuchsia::modular::auth::AccountPtr account_;
- OAuthTokenManagerApp* const app_;
- const AddAccountCallback callback_;
-
- fuchsia::modular::auth::AuthenticationContextPtr auth_context_;
-
- fuchsia::webview::WebViewPtr web_view_;
- fuchsia::sys::ComponentControllerPtr web_view_controller_;
-
- http::HttpServicePtr http_service_;
- http::URLLoaderPtr url_loader_;
-
- fidl::BindingSet<fuchsia::webview::WebRequestDelegate>
- web_request_delegate_bindings_;
-
- FXL_DISALLOW_COPY_AND_ASSIGN(GoogleUserCredsCall);
-};
-
-class OAuthTokenManagerApp::GoogleRevokeTokensCall
- : public modular::Operation<fuchsia::modular::auth::AuthErr> {
- public:
- GoogleRevokeTokensCall(fuchsia::modular::auth::AccountPtr account,
- bool revoke_all, OAuthTokenManagerApp* const app,
- RemoveAccountCallback callback)
- : Operation("OAuthTokenManagerApp::GoogleRevokeTokensCall",
- fxl::MakeCopyable(callback.share())),
- account_(std::move(account)),
- revoke_all_(revoke_all),
- app_(app),
- callback_(callback.share()) {}
-
- private:
- // |Operation|
- void Run() override {
- FlowToken flow{this, &auth_err_};
-
- if (!account_) {
- Failure(flow, fuchsia::modular::auth::Status::BAD_REQUEST,
- "Account is null.");
- return;
- }
-
- switch (account_->identity_provider) {
- case fuchsia::modular::auth::IdentityProvider::DEV:
- Success(flow); // guest mode
- return;
- case fuchsia::modular::auth::IdentityProvider::GOOGLE:
- break;
- default:
- Failure(flow, fuchsia::modular::auth::Status::BAD_REQUEST,
- "Unsupported IDP.");
- return;
- }
-
- // If there is a cache entry always ensure its deleted
- app_->oauth_tokens_.erase(account_->id);
-
- // If no credentials exist in the database we are now done.
- const std::string refresh_token =
- GetRefreshTokenFromCredsFile(account_->id);
- if (refresh_token.empty()) {
- FXL_LOG(ERROR) << "Account: " << account_->id << " not found.";
- Success(flow); // Maybe a guest account.
- return;
- }
-
- // Delete user credentials from local persistent storage.
- if (!DeleteCredentials()) {
- Failure(flow, fuchsia::modular::auth::Status::INTERNAL_ERROR,
- "Unable to delete persistent credentials for account:" +
- std::string(account_->id));
- return;
- }
-
- if (!revoke_all_) {
- Success(flow);
- return;
- }
-
- // Revoke persistent tokens on backend IDP server.
- app_->startup_context_->ConnectToEnvironmentService(
- http_service_.NewRequest());
- http_service_->CreateURLLoader(url_loader_.NewRequest());
-
- std::string url = kGoogleRevokeTokenEndpoint + std::string("?token=");
- url += refresh_token;
-
- std::string request_body;
-
- // This flow branches below, so we need to put it in a shared container
- // from which it can be removed once for all branches.
- FlowTokenHolder branch{flow};
-
- Post(request_body, url_loader_.get(), url,
- [this, branch] {
- std::unique_ptr<FlowToken> flow = branch.Continue();
- FXL_CHECK(flow);
- Success(*flow);
- },
- [this, branch](const fuchsia::modular::auth::Status status,
- const std::string error_message) {
- std::unique_ptr<FlowToken> flow = branch.Continue();
- FXL_CHECK(flow);
- Failure(*flow, status, error_message);
- },
- [this](rapidjson::Document doc) {
- return RevokeAllTokens(std::move(doc));
- });
- }
-
- // Deletes existing user credentials for |account_->id|.
- bool DeleteCredentials() {
- std::string file_buffer;
- const ::auth::CredentialStore* credentials_storage =
- ParseCredsFile(&file_buffer);
- if (credentials_storage == nullptr) {
- FXL_LOG(ERROR) << "Failed to parse credentials.";
- return false;
- }
-
- // Delete |account_->id| credentials and reserialize existing users.
- flatbuffers::FlatBufferBuilder builder;
- std::vector<flatbuffers::Offset<::auth::UserCredential>> creds;
-
- for (const auto* cred : *credentials_storage->creds()) {
- if (cred->account_id()->str() == account_->id) {
- // Delete existing credentials
- continue;
- }
-
- std::vector<flatbuffers::Offset<::auth::IdpCredential>> idp_creds;
- for (const auto* idp_cred : *cred->tokens()) {
- idp_creds.push_back(::auth::CreateIdpCredential(
- builder, idp_cred->identity_provider(),
- builder.CreateString(idp_cred->refresh_token())));
- }
-
- creds.push_back(::auth::CreateUserCredential(
- builder, builder.CreateString(cred->account_id()),
- builder.CreateVector<flatbuffers::Offset<::auth::IdpCredential>>(
- idp_creds)));
- }
-
- builder.Finish(
- ::auth::CreateCredentialStore(builder, builder.CreateVector(creds)));
-
- std::string new_serialized_creds = std::string(
- reinterpret_cast<const char*>(builder.GetCurrentBufferPointer()),
- builder.GetSize());
-
- return WriteCredsFile(new_serialized_creds);
- }
-
- // Invalidate both refresh and access tokens on backend IDP server.
- // If the revocation is successfully processed, then the status code of the
- // response is 200. For error conditions, a status code 400 is returned along
- // with an error code in the response body.
- bool RevokeAllTokens(rapidjson::Document status) {
- FXL_VLOG(1) << "Revoke token api response: "
- << JsonValueToPrettyString(status);
-
- return true;
- }
-
- void Success(FlowToken /*flow*/) {
- // Set status to success
- auth_err_.status = fuchsia::modular::auth::Status::OK;
- auth_err_.message = "";
- }
-
- void Failure(FlowToken /*flow*/, const fuchsia::modular::auth::Status& status,
- const std::string& error_message) {
- FXL_LOG(ERROR) << "Failed with error status:" << fidl::ToUnderlying(status)
- << " ,and message:" << error_message;
- auth_err_.status = status;
- auth_err_.message = error_message;
- }
-
- fuchsia::modular::auth::AccountPtr account_;
- // By default, RemoveAccount deletes account only from the device where the
- // user performed the operation.
- bool revoke_all_ = false;
- OAuthTokenManagerApp* const app_;
- const RemoveAccountCallback callback_;
-
- http::HttpServicePtr http_service_;
- http::URLLoaderPtr url_loader_;
-
- fuchsia::modular::auth::AuthErr auth_err_;
-
- FXL_DISALLOW_COPY_AND_ASSIGN(GoogleRevokeTokensCall);
-};
-
-class OAuthTokenManagerApp::GoogleProfileAttributesCall
- : public modular::Operation<> {
- public:
- GoogleProfileAttributesCall(fuchsia::modular::auth::AccountPtr account,
- OAuthTokenManagerApp* const app,
- AddAccountCallback callback)
- : Operation("OAuthTokenManagerApp::GoogleProfileAttributesCall", [] {}),
- account_(std::move(account)),
- app_(app),
- callback_(std::move(callback)) {}
-
- private:
- // |Operation|
- void Run() override {
- if (!account_) {
- Failure(fuchsia::modular::auth::Status::BAD_REQUEST, "Account is null.");
- return;
- }
-
- if (app_->oauth_tokens_.find(account_->id) == app_->oauth_tokens_.end()) {
- FXL_LOG(ERROR) << "Account: " << account_->id << " not found.";
- Success(); // Maybe a guest account.
- return;
- }
-
- const std::string access_token =
- app_->oauth_tokens_[account_->id].access_token;
- app_->startup_context_->ConnectToEnvironmentService(
- http_service_.NewRequest());
- http_service_->CreateURLLoader(url_loader_.NewRequest());
-
- // Fetch profile atrributes for the provisioned user using
- // https://developers.google.com/+/web/api/rest/latest/people/get api.
- Get(url_loader_.get(), kGooglePeopleGetEndpoint, access_token,
- [this] { Success(); },
- [this](const fuchsia::modular::auth::Status status,
- const std::string error_message) {
- Failure(status, error_message);
- },
- [this](rapidjson::Document doc) {
- return SetAccountAttributes(std::move(doc));
- });
- }
-
- // Populate profile urls and display name for the account.
- bool SetAccountAttributes(rapidjson::Document attributes) {
- FXL_VLOG(1) << "People:get api response: "
- << JsonValueToPrettyString(attributes);
-
- if (!account_) {
- return false;
- }
-
- if (attributes.HasMember("id")) {
- account_->profile_id = attributes["id"].GetString();
- }
- if (account_->profile_id.is_null()) {
- account_->profile_id = "";
- }
-
- if (attributes.HasMember("displayName")) {
- account_->display_name = attributes["displayName"].GetString();
- }
- if (account_->display_name.is_null()) {
- account_->display_name = "";
- }
-
- if (attributes.HasMember("url")) {
- account_->url = attributes["url"].GetString();
- } else {
- account_->url = "";
- }
-
- if (attributes.HasMember("image")) {
- account_->image_url = attributes["image"]["url"].GetString();
- } else {
- account_->image_url = "";
- }
-
- return true;
- }
-
- void Success() {
- callback_(std::move(account_), nullptr);
- Done();
- }
-
- void Failure(const fuchsia::modular::auth::Status& status,
- const std::string& error_message) {
- FXL_LOG(ERROR) << "Failed with error status:" << fidl::ToUnderlying(status)
- << " ,and message:" << error_message;
-
- // Account is missing profile attributes, but still valid.
- callback_(std::move(account_), error_message);
- Done();
- }
-
- fuchsia::modular::auth::AccountPtr account_;
- OAuthTokenManagerApp* const app_;
- const AddAccountCallback callback_;
-
- http::HttpServicePtr http_service_;
- http::URLLoaderPtr url_loader_;
-
- FXL_DISALLOW_COPY_AND_ASSIGN(GoogleProfileAttributesCall);
-};
-
-OAuthTokenManagerApp::OAuthTokenManagerApp(async::Loop* loop)
- : loop_(loop),
- startup_context_(component::StartupContext::CreateFromStartupInfo()),
- binding_(this) {
- startup_context_->outgoing().AddPublicService<AccountProvider>(
- [this](fidl::InterfaceRequest<AccountProvider> request) {
- binding_.Bind(std::move(request));
- });
- // Log an error if the existing credential file is invalid.
- if (files::IsFile(kCredentialsFile)) {
- std::string file_buffer;
- if (ParseCredsFile(&file_buffer) == nullptr) {
- FXL_LOG(WARNING) << "Error in parsing existing credentials from: "
- << kCredentialsFile;
- }
- }
-}
-
-void OAuthTokenManagerApp::Initialize(
- fidl::InterfaceHandle<fuchsia::modular::auth::AccountProviderContext>
- provider) {
- FXL_VLOG(1) << "OAuthTokenManagerApp::Initialize()";
- account_provider_context_.Bind(std::move(provider));
-}
-
-void OAuthTokenManagerApp::Terminate() {
- FXL_LOG(INFO) << "OAuthTokenManagerApp::Terminate()";
- loop_->Quit();
-}
-
-// TODO(alhaad): Check if account id already exists.
-std::string OAuthTokenManagerApp::GenerateAccountId() {
- uint32_t random_number = 0;
- zx_cprng_draw(&random_number, sizeof(random_number));
- return std::to_string(random_number);
-}
-
-void OAuthTokenManagerApp::AddAccount(
- fuchsia::modular::auth::IdentityProvider identity_provider,
- AddAccountCallback callback) {
- FXL_VLOG(1) << "OAuthTokenManagerApp::AddAccount()";
- auto account = fuchsia::modular::auth::Account::New();
- account->id = GenerateAccountId();
- account->identity_provider = identity_provider;
- account->profile_id = "";
- account->display_name = "";
- account->url = "";
- account->image_url = "";
-
- switch (identity_provider) {
- case fuchsia::modular::auth::IdentityProvider::DEV:
- callback(std::move(account), nullptr);
- return;
- case fuchsia::modular::auth::IdentityProvider::GOOGLE:
- operation_queue_.Add(new GoogleUserCredsCall(
- std::move(account), this,
- [this, callback = std::move(callback)](
- fuchsia::modular::auth::AccountPtr account,
- const fidl::StringPtr error_msg) mutable {
- if (error_msg) {
- callback(nullptr, error_msg);
- return;
- }
-
- operation_queue_.Add(new GoogleProfileAttributesCall(
- std::move(account), this, std::move(callback)));
- }));
- return;
- default:
- callback(nullptr, "Unrecognized Identity Provider");
- }
-}
-
-void OAuthTokenManagerApp::RemoveAccount(
- fuchsia::modular::auth::Account account, bool revoke_all,
- RemoveAccountCallback callback) {
- FXL_VLOG(1) << "OAuthTokenManagerApp::RemoveAccount()";
- operation_queue_.Add(
- new GoogleRevokeTokensCall(fidl::MakeOptional(std::move(account)),
- revoke_all, this, std::move(callback)));
-}
-
-void OAuthTokenManagerApp::GetTokenProviderFactory(
- fidl::StringPtr account_id,
- fidl::InterfaceRequest<fuchsia::modular::auth::TokenProviderFactory>
- request) {
- new TokenProviderFactoryImpl(account_id, this, std::move(request));
-}
-
-void OAuthTokenManagerApp::RefreshToken(const std::string& account_id,
- const TokenType& token_type,
- ShortLivedTokenCallback callback) {
- FXL_VLOG(1) << "OAuthTokenManagerApp::RefreshToken()";
- operation_queue_.Add(new GoogleOAuthTokensCall(account_id, token_type, this,
- std::move(callback)));
-}
-
-void OAuthTokenManagerApp::RefreshFirebaseToken(
- const std::string& account_id, const std::string& firebase_api_key,
- const std::string& id_token, FirebaseTokenCallback callback) {
- FXL_VLOG(1) << "OAuthTokenManagerApp::RefreshFirebaseToken()";
- operation_queue_.Add(new GoogleFirebaseTokensCall(
- account_id, firebase_api_key, id_token, this, std::move(callback)));
-}
-
-} // namespace
-
-int main(int argc, const char** argv) {
- auto command_line = fxl::CommandLineFromArgcArgv(argc, argv);
- if (!fxl::SetLogSettingsFromCommandLine(command_line)) {
- return 1;
- }
-
- async::Loop loop(&kAsyncLoopConfigAttachToThread);
- trace::TraceProvider trace_provider(loop.dispatcher());
-
- OAuthTokenManagerApp app(&loop);
- loop.Run();
- return 0;
-}
diff --git a/packages/prod/all b/packages/prod/all
index 085daa6..7b23308 100644
--- a/packages/prod/all
+++ b/packages/prod/all
@@ -25,7 +25,6 @@
"topaz/packages/prod/maxwell",
"topaz/packages/prod/modules_index",
"topaz/packages/prod/mondrian",
- "topaz/packages/prod/oauth_token_manager",
"topaz/packages/prod/perspective",
"topaz/packages/prod/skottie_viewer",
"topaz/packages/prod/spotify_auth_provider",
diff --git a/packages/prod/oauth_token_manager b/packages/prod/oauth_token_manager
deleted file mode 100644
index aa38316..0000000
--- a/packages/prod/oauth_token_manager
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "packages": [
- "//topaz/examples/oauth_token_manager"
- ]
-}
diff --git a/packages/products/ermine b/packages/products/ermine
index ac0ffa1..d828258 100644
--- a/packages/products/ermine
+++ b/packages/products/ermine
@@ -10,7 +10,6 @@
"topaz/packages/config/ermine",
"topaz/packages/examples/noodles",
"topaz/packages/prod/dashboard",
- "topaz/packages/prod/oauth_token_manager",
"topaz/packages/prod/sysui",
"topaz/packages/prod/term",
"topaz/packages/prod/web_runner_prototype"