diff --git a/google/cloud/odbc/bq_client_interface/odbc_authentication.h b/google/cloud/odbc/bq_client_interface/odbc_authentication.h index dff95f2455..c34e78cc5e 100644 --- a/google/cloud/odbc/bq_client_interface/odbc_authentication.h +++ b/google/cloud/odbc/bq_client_interface/odbc_authentication.h @@ -89,6 +89,7 @@ struct Oauth { std::string kms_key_name; std::string psc; GCD gcd; + std::string impersonated_email; }; // Returns true if all required BYOID properties are set. diff --git a/google/cloud/odbc/bq_client_interface/odbc_bq_client.cc b/google/cloud/odbc/bq_client_interface/odbc_bq_client.cc index 5714afff61..6e08e9e577 100644 --- a/google/cloud/odbc/bq_client_interface/odbc_bq_client.cc +++ b/google/cloud/odbc/bq_client_interface/odbc_bq_client.cc @@ -201,6 +201,11 @@ StatusRecordOr> ODBCBQClient::CreateBQClient( return credentials.GetStatusRecord(); } + if (!oauth.impersonated_email.empty()) { + credentials = google::cloud::MakeImpersonateServiceAccountCredentials( + credentials.GetValue(), oauth.impersonated_email, options); + } + options.set(*credentials); if (oauth.gcd.enable_gcd && oauth.gcd.universe_domain != "googleapis.com") { diff --git a/google/cloud/odbc/bq_driver/internal/odbc_conn_handle.cc b/google/cloud/odbc/bq_driver/internal/odbc_conn_handle.cc index 140df21c54..d8e03b3a5d 100644 --- a/google/cloud/odbc/bq_driver/internal/odbc_conn_handle.cc +++ b/google/cloud/odbc/bq_driver/internal/odbc_conn_handle.cc @@ -167,6 +167,8 @@ void ConnectionHandle::SetUp(Section& dsn_section, attribute_str_values_.insert({SQL_ATTR_CURRENT_CATALOG, dsn_.catalog}); } + dsn_.impersonated_email = dsn_section["SERVICEACCOUNTIMPERSONATIONEMAIL"]; + // Populate HTAPI related configurations std::string use_default_large_results_dataset = dsn_section["USEDEFAULTLARGERESULTSDATASET"]; diff --git a/google/cloud/odbc/bq_driver/internal/odbc_conn_handle.h b/google/cloud/odbc/bq_driver/internal/odbc_conn_handle.h index ef782ae8ff..2d7a0511d9 100644 --- a/google/cloud/odbc/bq_driver/internal/odbc_conn_handle.h +++ b/google/cloud/odbc/bq_driver/internal/odbc_conn_handle.h @@ -107,6 +107,7 @@ struct Dsn { std::string psc; bool enable_gcd; std::string universe_domain; + std::string impersonated_email; }; class EnvironmentHandle; diff --git a/google/cloud/odbc/bq_driver/odbc_connection.cc b/google/cloud/odbc/bq_driver/odbc_connection.cc index 96646b9efa..15a16218c4 100644 --- a/google/cloud/odbc/bq_driver/odbc_connection.cc +++ b/google/cloud/odbc/bq_driver/odbc_connection.cc @@ -91,6 +91,7 @@ Authentication CreateAuth(Dsn const& dsn) { auth.oauth.psc = dsn.psc; auth.oauth.gcd.enable_gcd = dsn.enable_gcd; auth.oauth.gcd.universe_domain = dsn.universe_domain; + auth.oauth.impersonated_email = dsn.impersonated_email; return auth; } diff --git a/google/cloud/odbc/integration_tests/odbc_driver_tests/statement_test.cc b/google/cloud/odbc/integration_tests/odbc_driver_tests/statement_test.cc index ba0134cc10..444d8c45d6 100644 --- a/google/cloud/odbc/integration_tests/odbc_driver_tests/statement_test.cc +++ b/google/cloud/odbc/integration_tests/odbc_driver_tests/statement_test.cc @@ -4365,6 +4365,121 @@ TEST(SQLMoreResults, ProcedureWithDescriptorAndQueryParams) { table.Drop(conn); EXPECT_EQ(Disconnect(conn), SQL_SUCCESS); } +TEST(StatementTest, VerifyServiceAccountImpersonationEmail) { + struct TestCase { + std::string impersonation_email; + bool expect_success; + }; + + std::vector const test_cases = { + { + "bq-devtools-simba-drivers-test@bigquery-devtools-drivers." + "iam.gserviceaccount.com", + true, + }, + { + "kirl-test@bigquery-devtools-drivers.iam.gserviceaccount.com", + false, + }, + }; + + for (auto const& test : test_cases) { + SCOPED_TRACE(test.impersonation_email); + + auto conn = std::make_shared(); + + std::string conn_str = + kDefaultConnectionString + + ";ServiceAccountImpersonationEmail=" + test.impersonation_email; + + EXPECT_EQ(Connect(conn_str, conn), SQL_SUCCESS); + + SQLRETURN status = + SQLExecDirect(conn->hstmt, (SQLCHAR*)"SELECT SESSION_USER()", SQL_NTS); + + if (test.expect_success) { + EXPECT_EQ(status, SQL_SUCCESS); + + status = SQLFetch(conn->hstmt); + EXPECT_EQ(status, SQL_SUCCESS); + + SQLWCHAR buf[256] = {}; + SQLLEN indicator = 0; + std::wstring result; + + do { + memset(buf, 0, sizeof(buf)); + + status = SQLGetData(conn->hstmt, 1, SQL_C_WCHAR, buf, sizeof(buf), + &indicator); + + CheckError(status, "SQLGetData", conn); + + if (indicator == SQL_NULL_DATA) { + break; + } + + for (size_t i = 0; i < sizeof(buf) / sizeof(SQLWCHAR) && buf[i] != 0; + ++i) { + result.push_back(buf[i]); + } + + } while (status == SQL_SUCCESS_WITH_INFO); + + EXPECT_FALSE(result.empty()); + + } else { + EXPECT_EQ(status, SQL_ERROR); + + SQLCHAR sql_state[6] = {}; + SQLINTEGER native_error = 0; + SQLCHAR message[512] = {}; + SQLSMALLINT msg_len = 0; + + EXPECT_EQ( + SQLGetDiagRec(SQL_HANDLE_STMT, conn->hstmt, 1, sql_state, + &native_error, message, sizeof(message), &msg_len), + SQL_SUCCESS); + + EXPECT_STREQ(reinterpret_cast(sql_state), "42000"); + + std::string msg(reinterpret_cast(message)); + EXPECT_NE(msg.find("bigquery.jobs.create permission"), std::string::npos); + + // SQLFetch should fail with Function Sequence Error. + status = SQLFetch(conn->hstmt); + EXPECT_EQ(status, SQL_ERROR); + + EXPECT_EQ( + SQLGetDiagRec(SQL_HANDLE_STMT, conn->hstmt, 1, sql_state, + &native_error, message, sizeof(message), &msg_len), + SQL_SUCCESS); + + msg.assign(reinterpret_cast(message)); + EXPECT_NE(msg.find("Function sequence error"), std::string::npos); + + // SQLGetData should also fail with Function Sequence Error. + SQLWCHAR buf[256] = {}; + SQLLEN indicator = 0; + + status = + SQLGetData(conn->hstmt, 1, SQL_C_WCHAR, buf, sizeof(buf), &indicator); + + EXPECT_EQ(status, SQL_ERROR); + + EXPECT_EQ( + SQLGetDiagRec(SQL_HANDLE_STMT, conn->hstmt, 1, sql_state, + &native_error, message, sizeof(message), &msg_len), + SQL_SUCCESS); + + msg.assign(reinterpret_cast(message)); + EXPECT_NE(msg.find("Function sequence error"), std::string::npos); + } + + EXPECT_EQ(Disconnect(conn), SQL_SUCCESS); + } +} + class IgnoreTransactionsTransactionTest : public ::testing::TestWithParam< std::tuplehstmt, (SQLCHAR*)insert_query.c_str(), SQL_NTS), + SQL_SUCCESS); ASSERT_EQ(SQLEndTran(SQL_HANDLE_DBC, conn->hdbc, completion_type),