| // 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 |