blob: 2cb4062c0683c42eaba3c19222960892448d6f7e [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.
// Token manager integration tests using dev auth provider.
#include <fuchsia/auth/cpp/fidl.h>
#include <fuchsia/sys/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/sys/cpp/testing/test_with_environment.h>
#include <memory>
#include <string>
#include "gtest/gtest.h"
#include "lib/callback/capture.h"
#include "lib/fidl/cpp/binding.h"
#include "lib/fidl/cpp/synchronous_interface_ptr.h"
#include "src/lib/fxl/command_line.h"
#include "src/lib/fxl/logging.h"
#include "src/lib/fxl/macros.h"
#include "src/lib/fxl/strings/string_view.h"
namespace e2e_dev {
namespace {
struct TestComponentParam {
const std::string auth_provider_type;
const char* auth_provider_url;
};
const std::string kEnvironment = "dev_token_mgr_test_env";
const std::string kTestUserId = "tq_auth_user_1";
const std::string kTestAppUrl = "/pkgfs/packages/test_auth_client/bin/app";
const std::string kDevIdp = "Dev";
const std::string kDevIotIDIdp = "DevIotID";
const bool kForce = true;
const std::string kTokenManagerFactoryUrl = "fuchsia-pkg://fuchsia.com/"
"token_manager_factory#meta/token_manager_factory.cmx";
const TestComponentParam kTestComponentParams[] = {
{kDevIdp,
"fuchsia-pkg://fuchsia.com/dev_auth_provider#"
"meta/dev_auth_provider.cmx"},
{kDevIotIDIdp,
"fuchsia-pkg://fuchsia.com/dev_auth_provider_iotid#"
"meta/dev_auth_provider_iotid.cmx"}
};
fuchsia::auth::AppConfig MakeDevAppConfig(
const std::string& auth_provider_type) {
fuchsia::auth::AppConfig dev_app_config;
dev_app_config.auth_provider_type = auth_provider_type;
dev_app_config.client_id = "test_client_id";
dev_app_config.client_secret = "test_client_secret";
return dev_app_config;
}
using fuchsia::auth::AppConfig;
using fuchsia::auth::FirebaseTokenPtr;
using fuchsia::auth::Status;
using fuchsia::auth::TokenManagerFactory;
using fuchsia::auth::UserProfileInfoPtr;
using fuchsia::sys::LaunchInfo;
using sys::testing::EnclosingEnvironment;
using sys::testing::EnvironmentServices;
class DevTokenManagerAppTest
: public sys::testing::TestWithEnvironment,
public ::testing::WithParamInterface<TestComponentParam>,
fuchsia::auth::AuthenticationContextProvider {
public:
DevTokenManagerAppTest()
: auth_context_provider_binding_(this) {}
~DevTokenManagerAppTest() {}
protected:
// ::testing::Test:
void SetUp() override {
std::unique_ptr<EnvironmentServices> services = CreateServices();
LaunchInfo launch_info;
launch_info.url = kTokenManagerFactoryUrl;
services->AddServiceWithLaunchInfo(std::move(launch_info), TokenManagerFactory::Name_);
environment_ = CreateNewEnclosingEnvironment(kEnvironment, std::move(services));
WaitForEnclosingEnvToStart(environment_.get());
environment_->ConnectToService(token_mgr_factory_.NewRequest());
ASSERT_TRUE(token_mgr_factory_.is_bound());
std::string auth_provider_type = GetParam().auth_provider_type;
dev_app_config_ = MakeDevAppConfig(auth_provider_type);
fuchsia::auth::AuthProviderConfig dev_auth_provider_config;
dev_auth_provider_config.auth_provider_type = auth_provider_type;
dev_auth_provider_config.url = GetParam().auth_provider_url;
std::vector<fuchsia::auth::AuthProviderConfig> auth_provider_configs;
auth_provider_configs.push_back(std::move(dev_auth_provider_config));
token_mgr_factory_->GetTokenManager(
kTestUserId, kTestAppUrl, std::move(auth_provider_configs),
auth_context_provider_binding_.NewBinding(), token_mgr_.NewRequest());
}
// ::testing::Test:
void TearDown() override {
// We attempt to clean up the tokens after each test. The Auth Provider
// uses a different user_profile_id for each test and so any problems with
// deletion should not impact test accuracy.
if (token_mgr_.is_bound() && !user_profile_id_.is_null()) {
bool call_complete = false;
token_mgr_->DeleteAllTokens(dev_app_config_, user_profile_id_, kForce,
[&] (Status status) {call_complete = true;});
RunLoopUntil([&] {return call_complete;});
}
}
// |AuthenticationContextProvider|
void GetAuthenticationUIContext(
fidl::InterfaceRequest<fuchsia::auth::AuthenticationUIContext> request)
override {
// Silently ignore this request. The TokenManager will pass the other end
// of the channel into the dev auth provider, which will never attempt to
// use it since it does not display UI.
}
private:
std::unique_ptr<EnclosingEnvironment> environment_;
protected:
fidl::Binding<fuchsia::auth::AuthenticationContextProvider>
auth_context_provider_binding_;
fuchsia::auth::AppConfig dev_app_config_;
fuchsia::auth::TokenManagerPtr token_mgr_;
fuchsia::auth::TokenManagerFactoryPtr token_mgr_factory_;
fidl::StringPtr user_profile_id_;
void RegisterUser(fuchsia::auth::AppConfig app_config) {
auto scopes = fidl::VectorPtr<std::string>::New(0);
scopes.push_back("test_scope");
bool call_complete = false;
token_mgr_->Authorize(
app_config, nullptr, /* optional AuthenticationUiContext */
std::move(scopes), "", /* new user, no existing user_profile_id */
"", /* empty auth_code */
[&](Status status, UserProfileInfoPtr user_info) {
EXPECT_EQ(Status::OK, status);
EXPECT_NE(nullptr, user_info);
user_profile_id_ = user_info->id;
call_complete = true;
});
RunLoopUntil([&] {return call_complete;});
}
FXL_DISALLOW_COPY_AND_ASSIGN(DevTokenManagerAppTest);
};
TEST_P(DevTokenManagerAppTest, Authorize) {
auto scopes = fidl::VectorPtr<std::string>::New(0);
scopes.push_back("test_scope");
bool call_complete = false;
token_mgr_->Authorize(
dev_app_config_, nullptr, /* optional AuthenticationUiContext */
std::move(scopes), "", /* new user, no existing user_profile_id */
"", /* empty auth_code */
[&](Status status, UserProfileInfoPtr user_info) {
EXPECT_EQ(Status::OK, status);
EXPECT_NE(nullptr, user_info);
EXPECT_FALSE(user_info->id.empty());
EXPECT_FALSE(user_info->display_name.get().empty());
EXPECT_FALSE(user_info->url.get().empty());
EXPECT_FALSE(user_info->image_url.get().empty());
call_complete = true;
});
RunLoopUntil([&] {return call_complete;});
}
TEST_P(DevTokenManagerAppTest, GetAccessToken) {
RegisterUser(dev_app_config_);
auto scopes = fidl::VectorPtr<std::string>::New(0);
bool call_complete = false;
token_mgr_->GetAccessToken(
dev_app_config_, user_profile_id_, std::move(scopes),
[&](Status status, fidl::StringPtr access_token) {
EXPECT_EQ(Status::OK, status);
EXPECT_NE(std::string::npos, access_token.get().find(":at_"));
call_complete = true;
});
RunLoopUntil([&] {return call_complete;});
}
TEST_P(DevTokenManagerAppTest, GetIdToken) {
RegisterUser(dev_app_config_);
bool call_complete = false;
token_mgr_->GetIdToken(
dev_app_config_, user_profile_id_, "",
[&](Status status, fidl::StringPtr id_token) {
if (dev_app_config_.auth_provider_type == kDevIotIDIdp) {
// TODO(ukode): Not yet supported for IotID
EXPECT_EQ(Status::INVALID_REQUEST, status);
} else {
EXPECT_EQ(Status::OK, status);
EXPECT_NE(std::string::npos, id_token.get().find(":idt_"));
}
call_complete = true;
});
RunLoopUntil([&] {return call_complete;});
}
TEST_P(DevTokenManagerAppTest, GetFirebaseToken) {
RegisterUser(dev_app_config_);
bool call_complete = false;
token_mgr_->GetFirebaseToken(
dev_app_config_, user_profile_id_, "firebase_test_api_key", "",
[&](Status status, FirebaseTokenPtr firebase_token) {
if (dev_app_config_.auth_provider_type == kDevIotIDIdp) {
// TODO(ukode): Not yet supported for IotID
EXPECT_EQ(Status::INVALID_REQUEST, status);
} else {
EXPECT_EQ(Status::OK, status);
EXPECT_NE(std::string::npos, firebase_token->id_token.find(":fbt_"));
EXPECT_NE(std::string::npos, firebase_token->email.get().find("@firebase.example.com"));
EXPECT_NE(std::string::npos, firebase_token->local_id.get().find("local_id_"));
}
call_complete = true;
});
RunLoopUntil([&] {return call_complete;});
}
TEST_P(DevTokenManagerAppTest, GetFirebaseTokenFromCache) {
// TODO(ukode): Not yet supported for IotID
if (dev_app_config_.auth_provider_type == kDevIotIDIdp) {
return;
}
FirebaseTokenPtr firebase_token;
FirebaseTokenPtr other_firebase_token;
FirebaseTokenPtr cached_firebase_token;
RegisterUser(dev_app_config_);
bool last_call_complete = false;
token_mgr_->GetFirebaseToken(
dev_app_config_, user_profile_id_, "", "key1",
[&](Status status, FirebaseTokenPtr token) {
EXPECT_EQ(Status::OK, status);
firebase_token = std::move(token);
});
token_mgr_->GetFirebaseToken(
dev_app_config_, user_profile_id_, "", "key2",
[&](Status status, FirebaseTokenPtr token) {
EXPECT_EQ(Status::OK, status);
other_firebase_token = std::move(token);
});
token_mgr_->GetFirebaseToken(
dev_app_config_, user_profile_id_, "", "key1",
[&](Status status, FirebaseTokenPtr token) {
EXPECT_EQ(Status::OK, status);
cached_firebase_token = std::move(token);
last_call_complete = true;
});
RunLoopUntil([&] {return last_call_complete;});
EXPECT_NE(firebase_token->id_token, other_firebase_token->id_token);
EXPECT_EQ(firebase_token->id_token, cached_firebase_token->id_token);
EXPECT_EQ(firebase_token->email, cached_firebase_token->email);
EXPECT_EQ(firebase_token->local_id, cached_firebase_token->local_id);
}
TEST_P(DevTokenManagerAppTest, EraseAllTokens) {
// TODO(ukode): Not yet supported for IotID
if (dev_app_config_.auth_provider_type == kDevIotIDIdp) {
return;
}
RegisterUser(dev_app_config_);
bool last_call_complete = false;
token_mgr_->GetIdToken(
dev_app_config_, user_profile_id_, "",
[&](Status status, fidl::StringPtr id_token) {
EXPECT_EQ(Status::OK, status);
EXPECT_NE(nullptr, id_token);
});
auto scopes = fidl::VectorPtr<std::string>::New(0);
token_mgr_->GetAccessToken(
dev_app_config_, user_profile_id_, std::move(scopes),
[&](Status status, fidl::StringPtr access_token) {
EXPECT_EQ(Status::OK, status);
EXPECT_NE(nullptr, access_token);
});
token_mgr_->GetFirebaseToken(
dev_app_config_, user_profile_id_, "", "",
[&](Status status, FirebaseTokenPtr firebase_token) {
EXPECT_EQ(Status::OK, status);
EXPECT_NE(nullptr, firebase_token);
});
token_mgr_->DeleteAllTokens(
dev_app_config_, user_profile_id_, kForce,
[&](Status status) {
EXPECT_EQ(Status::OK, status);
});
token_mgr_->GetIdToken(
dev_app_config_, user_profile_id_, "",
[&](Status status, fidl::StringPtr id_token) {
EXPECT_EQ(Status::USER_NOT_FOUND, status);
});
scopes = fidl::VectorPtr<std::string>::New(0);
token_mgr_->GetAccessToken(
dev_app_config_, user_profile_id_, std::move(scopes),
[&](Status status, fidl::StringPtr access_token) {
EXPECT_EQ(Status::USER_NOT_FOUND, status);
});
token_mgr_->GetFirebaseToken(
dev_app_config_, user_profile_id_, "", "",
[&](Status status, FirebaseTokenPtr firebase_token) {
EXPECT_EQ(Status::USER_NOT_FOUND, status);
last_call_complete = true;
});
RunLoopUntil([&] {return last_call_complete;});
}
TEST_P(DevTokenManagerAppTest, GetIdTokenFromCache) {
// TODO(ukode): Not yet supported for IotID
if (dev_app_config_.auth_provider_type == kDevIotIDIdp) {
return;
}
fidl::StringPtr id_token;
fidl::StringPtr second_user_id_token;
RegisterUser(dev_app_config_);
bool last_call_complete = false;
token_mgr_->GetIdToken(
dev_app_config_, user_profile_id_, "",
[&](Status status, fidl::StringPtr token) {
EXPECT_EQ(Status::OK, status);
id_token = std::move(token);
});
token_mgr_->GetIdToken(
dev_app_config_, user_profile_id_, "",
[&](Status status, fidl::StringPtr token) {
EXPECT_EQ(Status::OK, status);
EXPECT_EQ(id_token.get(), token.get());
});
// Verify ID tokens are different for different user to prevent a
// degenerate test.
fidl::StringPtr original_user_profile_id = user_profile_id_;
RegisterUser(dev_app_config_);
EXPECT_NE(user_profile_id_, original_user_profile_id);
token_mgr_->GetIdToken(
dev_app_config_, user_profile_id_, "",
[&](Status status, fidl::StringPtr token) {
EXPECT_EQ(Status::OK, status);
EXPECT_NE(id_token.get(), token.get());
last_call_complete = true;
});
RunLoopUntil([&] {return last_call_complete;});
}
TEST_P(DevTokenManagerAppTest, GetAccessTokenFromCache) {
fidl::StringPtr access_token;
RegisterUser(dev_app_config_);
bool last_call_complete = false;
auto scopes = fidl::VectorPtr<std::string>::New(0);
token_mgr_->GetAccessToken(
dev_app_config_, user_profile_id_, std::move(scopes),
[&](Status status, fidl::StringPtr token) {
EXPECT_EQ(Status::OK, status);
EXPECT_NE(std::string::npos, token.get().find(":at_"));
access_token = std::move(token);
});
scopes = fidl::VectorPtr<std::string>::New(0);
token_mgr_->GetAccessToken(
dev_app_config_, user_profile_id_, std::move(scopes),
[&](Status status, fidl::StringPtr token) {
EXPECT_EQ(Status::OK, status);
EXPECT_EQ(access_token.get(), token.get());
last_call_complete = true;
});
RunLoopUntil([&] {return last_call_complete;});
}
// Tests user re-authorization flow that generates fresh long lived credentials
// and verifies that short lived credentials are based on the most recent long
// lived credentials.
TEST_P(DevTokenManagerAppTest, Reauthorize) {
fidl::StringPtr user_profile_id;
std::string credential;
bool authorize_complete = false;
bool last_call_complete = false;
auto scopes = fidl::VectorPtr<std::string>::New(0);
token_mgr_->Authorize(
dev_app_config_, nullptr, std::move(scopes), "", "",
[&](Status status, UserProfileInfoPtr user_info) {
EXPECT_EQ(Status::OK, status);
user_profile_id = user_info->id;
authorize_complete = true;
});
RunLoopUntil([&] {return authorize_complete;});
scopes = fidl::VectorPtr<std::string>::New(0);
token_mgr_->GetAccessToken(
dev_app_config_, user_profile_id, std::move(scopes),
[&](Status status, fidl::StringPtr access_token) {
EXPECT_EQ(Status::OK, status);
// Extract part of the fake access token reflecting the refresh token
credential = access_token->substr(0, access_token->find(":"));
});
token_mgr_->DeleteAllTokens(
dev_app_config_, user_profile_id, kForce,
[&](Status status) {
EXPECT_EQ(Status::OK, status);
});
// Verify that the credential and cache should now be cleared
scopes = fidl::VectorPtr<std::string>::New(0);
token_mgr_->GetAccessToken(
dev_app_config_, user_profile_id, std::move(scopes),
[&](Status status, fidl::StringPtr access_token) {
EXPECT_EQ(Status::USER_NOT_FOUND, status);
EXPECT_EQ(nullptr, access_token);
});
// Re-authorize the same |user_profile_id|
scopes = fidl::VectorPtr<std::string>::New(0);
token_mgr_->Authorize(
dev_app_config_, nullptr, std::move(scopes), user_profile_id, "",
[&](Status status, UserProfileInfoPtr user_info) {
EXPECT_EQ(Status::OK, status);
EXPECT_EQ(user_info->id, user_profile_id);
});
// Verify that new access token is not based on the original credential
scopes = fidl::VectorPtr<std::string>::New(0);
token_mgr_->GetAccessToken(
dev_app_config_, user_profile_id, std::move(scopes),
[&](Status status, fidl::StringPtr access_token) {
EXPECT_EQ(Status::OK, status);
EXPECT_NE(nullptr, access_token);
EXPECT_EQ(std::string::npos, access_token.get().find(credential));
last_call_complete = true;
});
RunLoopUntil([&] {return last_call_complete;});
}
INSTANTIATE_TEST_SUITE_P(Cpp, DevTokenManagerAppTest,
::testing::ValuesIn(kTestComponentParams));
} // namespace
} // namespace e2e_dev