blob: 8bc9ee4c477f119516658d2d6f04785c053e7881 [file] [log] [blame]
// 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.
#include "peridot/bin/basemgr/user_provider_impl.h"
#include <utility>
#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/strings/string_printf.h>
#include "peridot/bin/basemgr/users_generated.h"
#include "peridot/lib/common/xdr.h"
#include "peridot/lib/fidl/clone.h"
#include "peridot/lib/fidl/json_xdr.h"
namespace modular {
namespace {
constexpr char kUsersConfigurationFile[] = "/data/modular/users-v5.db";
// Url of the application launching token manager
constexpr char kUserProviderAppUrl[] = "user_provider_url";
// Dev auth provider configuration
constexpr char kDevAuthProviderType[] = "dev";
constexpr char kDevAuthProviderUrl[] = "dev_auth_provider";
// Google auth provider configuration
constexpr char kGoogleAuthProviderType[] = "google";
constexpr char kGoogleAuthProviderUrl[] = "google_auth_provider";
fuchsia::modular::auth::AccountPtr Convert(
const fuchsia::modular::UserStorage* const user) {
FXL_DCHECK(user);
auto account = fuchsia::modular::auth::Account::New();
account->id = user->id()->str();
switch (user->identity_provider()) {
case fuchsia::modular::IdentityProvider_DEV:
account->identity_provider =
fuchsia::modular::auth::IdentityProvider::DEV;
break;
case fuchsia::modular::IdentityProvider_GOOGLE:
account->identity_provider =
fuchsia::modular::auth::IdentityProvider::GOOGLE;
break;
default:
FXL_DCHECK(false) << "Unrecognized IdentityProvider"
<< user->identity_provider();
}
account->display_name = user->display_name()->str();
account->url = user->profile_url()->str();
account->image_url = user->image_url()->str();
if (flatbuffers::IsFieldPresent(
user, fuchsia::modular::UserStorage::VT_PROFILE_ID)) {
account->profile_id = user->profile_id()->str();
} else {
account->profile_id = "";
}
return account;
}
std::string GetRandomId() {
uint32_t random_number = 0;
zx_cprng_draw(&random_number, sizeof random_number);
return std::to_string(random_number);
}
// Returns the corresponding |auth_provider_type| string that maps to
// |fuchsia::modular::auth::IdentityProvider| value.
// TODO(ukode): Convert enum |fuchsia::modular::auth::IdentityProvider| to
// fidl::String datatype to make it consistent in the future.
std::string MapIdentityProviderToAuthProviderType(
const fuchsia::modular::auth::IdentityProvider idp) {
switch (idp) {
case fuchsia::modular::auth::IdentityProvider::DEV:
return kDevAuthProviderType;
case fuchsia::modular::auth::IdentityProvider::GOOGLE:
return kGoogleAuthProviderType;
}
FXL_DCHECK(false) << "Unrecognized IDP.";
}
// Returns a list of supported auth provider configurations that includes the
// type, startup parameters and the url of the auth provider component.
// TODO(ukode): This list will be derived from a config package in the future.
fidl::VectorPtr<fuchsia::auth::AuthProviderConfig> GetAuthProviderConfigs() {
fuchsia::auth::AuthProviderConfig dev_auth_provider_config;
dev_auth_provider_config.auth_provider_type = kDevAuthProviderType;
dev_auth_provider_config.url = kDevAuthProviderUrl;
fuchsia::auth::AuthProviderConfig google_auth_provider_config;
google_auth_provider_config.auth_provider_type = kGoogleAuthProviderType;
google_auth_provider_config.url = kGoogleAuthProviderUrl;
fidl::VectorPtr<fuchsia::auth::AuthProviderConfig> auth_provider_configs;
auth_provider_configs.push_back(std::move(google_auth_provider_config));
auth_provider_configs.push_back(std::move(dev_auth_provider_config));
return auth_provider_configs;
}
} // namespace
UserProviderImpl::UserProviderImpl(
fuchsia::sys::Launcher* const launcher,
const fuchsia::modular::AppConfig& sessionmgr,
const fuchsia::modular::AppConfig& session_shell,
const fuchsia::modular::AppConfig& story_shell,
fuchsia::auth::TokenManagerFactory* token_manager_factory,
fuchsia::auth::AuthenticationContextProviderPtr
authentication_context_provider,
Delegate* const delegate)
: launcher_(launcher),
sessionmgr_(sessionmgr),
session_shell_(session_shell),
story_shell_(story_shell),
token_manager_factory_(token_manager_factory),
authentication_context_provider_(
std::move(authentication_context_provider)),
delegate_(delegate),
authentication_context_provider_binding_(this) {
FXL_DCHECK(delegate);
FXL_DCHECK(authentication_context_provider_);
authentication_context_provider_binding_.set_error_handler(
[this](zx_status_t status) {
FXL_LOG(WARNING) << "AuthenticationContextProvider disconnected.";
authentication_context_provider_binding_.Unbind();
});
// There might not be a file of users persisted. If config file doesn't
// exist, move forward with no previous users.
// TODO(alhaad): Use JSON instead of flatbuffers for better inspectablity.
if (files::IsFile(kUsersConfigurationFile)) {
std::string serialized_users;
if (!files::ReadFileToString(kUsersConfigurationFile, &serialized_users)) {
// Unable to read file. Bailing out.
FXL_LOG(ERROR) << "Unable to read user configuration file at: "
<< kUsersConfigurationFile;
return;
}
if (!Parse(serialized_users)) {
return;
}
}
}
void UserProviderImpl::Connect(
fidl::InterfaceRequest<fuchsia::modular::UserProvider> request) {
bindings_.AddBinding(this, std::move(request));
}
void UserProviderImpl::Teardown(const std::function<void()>& callback) {
if (user_controllers_.empty()) {
callback();
return;
}
for (auto& it : user_controllers_) {
auto cont = [this, ptr = it.first, callback] {
// This is okay because during teardown, |cont| is never invoked
// asynchronously.
user_controllers_.erase(ptr);
if (!user_controllers_.empty()) {
// Not the last callback.
return;
}
callback();
};
it.second->Logout(cont);
}
}
void UserProviderImpl::Login(fuchsia::modular::UserLoginParams params) {
// If requested, run in incognito mode.
if (params.account_id.is_null() || params.account_id == "") {
FXL_LOG(INFO) << "fuchsia::modular::UserProvider::Login() Incognito mode";
LoginInternal(nullptr /* account */, std::move(params));
return;
}
// If not running in incognito mode, a corresponding entry must be present
// in the users database.
const fuchsia::modular::UserStorage* found_user = nullptr;
if (users_storage_) {
for (const auto* user : *users_storage_->users()) {
if (user->id()->str() == params.account_id) {
found_user = user;
break;
}
}
}
// If an entry is not found, we drop the incoming requests on the floor.
if (!found_user) {
FXL_LOG(INFO) << "The requested user was not found in the users database"
<< "It needs to be added first via "
"fuchsia::modular::UserProvider::AddUser().";
return;
}
LoginInternal(Convert(found_user), std::move(params));
}
void UserProviderImpl::PreviousUsers(PreviousUsersCallback callback) {
fidl::VectorPtr<fuchsia::modular::auth::Account> accounts;
accounts.resize(0);
if (users_storage_) {
for (const auto* user : *users_storage_->users()) {
accounts.push_back(*Convert(user));
}
}
callback(std::move(accounts));
}
void UserProviderImpl::AddUser(
fuchsia::modular::auth::IdentityProvider identity_provider,
AddUserCallback callback) {
FXL_DCHECK(token_manager_factory_);
// Creating a new user, the initial bootstrapping will be done by
// AccountManager in the future. For now, create an account_id that
// uniquely maps to a token manager instance at runtime.
const std::string& account_id = GetRandomId();
fuchsia::auth::TokenManagerPtr token_manager;
token_manager = CreateTokenManager(account_id);
// TODO(ukode): Fuchsia mod configuration that is requesting OAuth tokens.
// This includes OAuth client specific details such as client id, secret,
// list of scopes etc. These could be supplied by a config package in the
// future.
fuchsia::auth::AppConfig fuchsia_app_config;
fuchsia_app_config.auth_provider_type =
MapIdentityProviderToAuthProviderType(identity_provider);
auto scopes = fidl::VectorPtr<fidl::StringPtr>::New(0);
token_manager->Authorize(
std::move(fuchsia_app_config), nullptr, std::move(scopes), "", "",
[this, identity_provider, account_id,
token_manager = std::move(token_manager),
callback](fuchsia::auth::Status status,
fuchsia::auth::UserProfileInfoPtr user_profile_info) {
if (status != fuchsia::auth::Status::OK) {
FXL_LOG(ERROR) << "Authorize() call returned error for user: "
<< account_id;
callback(nullptr, "Failed to authorize user");
return;
}
if (!user_profile_info) {
FXL_LOG(ERROR) << "Authorize() call returned empty user profile";
callback(nullptr,
"Empty user profile info returned by auth_provider");
return;
}
auto account = fuchsia::modular::auth::Account::New();
account->id = account_id;
account->identity_provider = identity_provider;
account->profile_id = user_profile_info->id;
account->display_name = user_profile_info->display_name.is_null()
? ""
: user_profile_info->display_name;
account->url =
user_profile_info->url.is_null() ? "" : user_profile_info->url;
account->image_url = user_profile_info->image_url.is_null()
? ""
: user_profile_info->image_url;
std::string error;
if (!AddUserToAccountsDB(account.get(), &error)) {
FXL_LOG(ERROR) << "Failed to add user: " << account_id
<< ", to the accounts database:" << error;
callback(nullptr, error);
return;
}
FXL_DLOG(INFO) << "Successfully added user: " << account_id;
callback(std::move(account), "");
});
}
void UserProviderImpl::RemoveUser(fidl::StringPtr account_id,
RemoveUserCallback callback) {
fuchsia::modular::auth::AccountPtr account;
if (users_storage_) {
for (const auto* user : *users_storage_->users()) {
if (user->id()->str() == account_id) {
account = Convert(user);
}
}
}
if (!account) {
callback("User not found.");
return;
}
RemoveUserInternal(std::move(account), std::move(callback));
}
bool UserProviderImpl::AddUserToAccountsDB(
const fuchsia::modular::auth::Account* account, std::string* error) {
FXL_DCHECK(account);
flatbuffers::FlatBufferBuilder builder;
std::vector<flatbuffers::Offset<fuchsia::modular::UserStorage>> users;
// Reserialize existing users.
if (users_storage_) {
for (const auto* user : *(users_storage_->users())) {
users.push_back(fuchsia::modular::CreateUserStorage(
builder, builder.CreateString(user->id()), user->identity_provider(),
builder.CreateString(user->display_name()),
builder.CreateString(user->profile_url()),
builder.CreateString(user->image_url()),
builder.CreateString(user->profile_id())));
}
}
auto account_identity_provider = account->identity_provider;
auto flatbuffer_identity_provider = [account_identity_provider]() {
switch (account_identity_provider) {
case fuchsia::modular::auth::IdentityProvider::DEV:
return fuchsia::modular::IdentityProvider::IdentityProvider_DEV;
case fuchsia::modular::auth::IdentityProvider::GOOGLE:
return fuchsia::modular::IdentityProvider::IdentityProvider_GOOGLE;
}
FXL_DCHECK(false) << "Unrecognized IDP.";
// TODO(ukode): Move |UserStorage::identity_provider| to string
// datatype. Use DEV identity provider as default in the interim.
return fuchsia::modular::IdentityProvider::IdentityProvider_DEV;
}();
// Add new user
users.push_back(fuchsia::modular::CreateUserStorage(
builder, builder.CreateString(account->id), flatbuffer_identity_provider,
builder.CreateString(account->display_name),
builder.CreateString(account->url),
builder.CreateString(account->image_url),
builder.CreateString(account->id)));
// Write user info to disk
builder.Finish(fuchsia::modular::CreateUsersStorage(
builder, builder.CreateVector(users)));
std::string new_serialized_users = std::string(
reinterpret_cast<const char*>(builder.GetCurrentBufferPointer()),
builder.GetSize());
return WriteUsersDb(new_serialized_users, error);
}
void UserProviderImpl::RemoveUserInternal(
fuchsia::modular::auth::AccountPtr account, RemoveUserCallback callback) {
FXL_DCHECK(account);
auto account_id = account->id;
FXL_DLOG(INFO) << "Invoking DeleteAllTokens() for user:" << account_id;
auto token_manager = CreateTokenManager(account_id);
// TODO(ukode): Delete tokens for all the supported auth provider configs just
// not Google. This will be replaced by AccountManager::RemoveUser api in the
// future.
fuchsia::auth::AppConfig fuchsia_app_config;
fuchsia_app_config.auth_provider_type = kGoogleAuthProviderType;
token_manager->DeleteAllTokens(
fuchsia_app_config, account->profile_id,
[this, account_id, token_manager = std::move(token_manager),
callback](fuchsia::auth::Status status) {
if (status != fuchsia::auth::Status::OK) {
FXL_LOG(ERROR) << "Token Manager Authorize() call returned error";
callback("Unable to remove user");
return;
}
std::string error;
if (!RemoveUserFromAccountsDB(account_id, &error)) {
FXL_LOG(ERROR) << "Error in updating user database: " << error;
callback(error);
return;
}
callback(""); // success
});
}
// Update user storage after deleting user credentials.
bool UserProviderImpl::RemoveUserFromAccountsDB(fidl::StringPtr account_id,
std::string* error) {
FXL_DCHECK(account_id);
FXL_DCHECK(error);
flatbuffers::FlatBufferBuilder builder;
std::vector<flatbuffers::Offset<fuchsia::modular::UserStorage>> users;
for (const auto* user : *(users_storage_->users())) {
if (user->id()->str() == account_id) {
// TODO(alhaad): We need to delete the local ledger data for a user
// who has been removed. Re-visit this when sandboxing the user
// runner.
continue;
}
users.push_back(fuchsia::modular::CreateUserStorage(
builder, builder.CreateString(user->id()), user->identity_provider(),
builder.CreateString(user->display_name()),
builder.CreateString(user->profile_url()),
builder.CreateString(user->image_url()),
builder.CreateString(user->profile_id())));
}
builder.Finish(fuchsia::modular::CreateUsersStorage(
builder, builder.CreateVector(users)));
std::string new_serialized_users = std::string(
reinterpret_cast<const char*>(builder.GetCurrentBufferPointer()),
builder.GetSize());
return WriteUsersDb(new_serialized_users, error);
}
void UserProviderImpl::GetAuthenticationUIContext(
fidl::InterfaceRequest<fuchsia::auth::AuthenticationUIContext> request) {
authentication_context_provider_->GetAuthenticationUIContext(
std::move(request));
}
fuchsia::auth::TokenManagerPtr UserProviderImpl::CreateTokenManager(
fidl::StringPtr account_id) {
FXL_DCHECK(account_id);
FXL_DCHECK(token_manager_factory_);
fuchsia::auth::TokenManagerPtr token_mgr;
token_manager_factory_->GetTokenManager(
account_id, kUserProviderAppUrl, GetAuthProviderConfigs(),
authentication_context_provider_binding_.NewBinding(),
token_mgr.NewRequest());
token_mgr.set_error_handler([this, account_id](zx_status_t status) {
FXL_LOG(INFO) << "Token Manager for account:" << account_id
<< " disconnected";
});
return token_mgr;
}
bool UserProviderImpl::WriteUsersDb(const std::string& serialized_users,
std::string* const error) {
if (!Parse(serialized_users)) {
*error = "The user database seems corrupted.";
return false;
}
// Save users to disk.
if (!files::CreateDirectory(
files::GetDirectoryName(kUsersConfigurationFile))) {
*error = "Unable to create directory.";
return false;
}
if (!files::WriteFile(kUsersConfigurationFile, serialized_users.data(),
serialized_users.size())) {
*error = "Unable to write file.";
return false;
}
return true;
}
bool UserProviderImpl::Parse(const std::string& serialized_users) {
flatbuffers::Verifier verifier(
reinterpret_cast<const unsigned char*>(serialized_users.data()),
serialized_users.size());
if (!fuchsia::modular::VerifyUsersStorageBuffer(verifier)) {
FXL_LOG(ERROR) << "Unable to verify storage buffer.";
return false;
}
serialized_users_ = serialized_users;
users_storage_ = fuchsia::modular::GetUsersStorage(serialized_users_.data());
return true;
}
void UserProviderImpl::LoginInternal(fuchsia::modular::auth::AccountPtr account,
fuchsia::modular::UserLoginParams params) {
auto account_id = account ? account->id.get() : GetRandomId();
FXL_DLOG(INFO) << "Login() User:" << account_id;
// Instead of passing token_manager_factory all the way to agents and
// runners with all auth provider configurations, send two
// |fuchsia::auth::TokenManager| handles, one for ledger and one for agents
// for the given user account |account_id|.
fuchsia::auth::TokenManagerPtr ledger_token_manager =
CreateTokenManager(account_id);
fuchsia::auth::TokenManagerPtr agent_token_manager =
CreateTokenManager(account_id);
zx::eventpair default_view_token =
params.view_token
? std::move(params.view_token)
: zx::eventpair(params.view_owner.TakeChannel().release());
auto view_token =
delegate_->GetSessionShellViewToken(std::move(default_view_token));
auto service_provider =
delegate_->GetSessionShellServiceProvider(std::move(params.services));
auto controller = std::make_unique<UserControllerImpl>(
launcher_, CloneStruct(sessionmgr_), CloneStruct(session_shell_),
CloneStruct(story_shell_), std::move(ledger_token_manager),
std::move(agent_token_manager), std::move(account), std::move(view_token),
std::move(service_provider), std::move(params.user_controller),
[this](UserControllerImpl* c) {
user_controllers_.erase(c);
delegate_->DidLogout();
});
auto controller_ptr = controller.get();
user_controllers_[controller_ptr] = std::move(controller);
delegate_->DidLogin();
}
FuturePtr<> UserProviderImpl::SwapSessionShell(
fuchsia::modular::AppConfig session_shell_config) {
if (user_controllers_.size() == 0)
return Future<>::CreateCompleted("SwapSessionShell(Completed)");
FXL_CHECK(user_controllers_.size() == 1)
<< user_controllers_.size()
<< " user controllers exist, which should be impossible.";
auto user_controller = user_controllers_.begin()->first;
return user_controller->SwapSessionShell(std::move(session_shell_config));
}
void UserProviderImpl::RestartSession() {
// Callback to log the user back in if login is not automatic
auto login = [this] {
if (user_controllers_.size() < 1 && users_storage_) {
auto account = Convert(users_storage_->users()->Get(0));
fuchsia::modular::UserLoginParams params;
params.account_id = account->id;
Login(std::move(params));
}
};
// Log the user out to shut down sessionmgr
user_controllers_.begin()->first->Logout(login);
}
} // namespace modular