Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b1610f7
feat(bigquery): support authorized views with dataset restrictions
Genesis929 Feb 25, 2026
fe8d967
merge origin/main into allowed_view_fix and resolve conflicts
Genesis929 Feb 25, 2026
0fba781
support authorized views and align util/tests with latest main archit…
Genesis929 Feb 25, 2026
74f4795
resolve linting errors and test failures
Genesis929 Feb 25, 2026
88f3248
Merge branch 'main' into allowed_view_fix
Genesis929 Feb 25, 2026
49d732d
Merge branch 'main' into allowed_view_fix
Genesis929 Feb 25, 2026
8de9da3
update
Genesis929 Feb 25, 2026
36c12ee
update
Genesis929 Feb 25, 2026
ffd1983
Merge branch 'main' into allowed_view_fix
Genesis929 Feb 26, 2026
12f6438
Merge branch 'main' into allowed_view_fix
Genesis929 Feb 26, 2026
bf29315
Merge branch 'main' into allowed_view_fix
Genesis929 Mar 2, 2026
0b44a51
Merge branch 'main' into allowed_view_fix
duwenxin99 Mar 24, 2026
3ae04c9
merge: origin/main into allowed_view_fix and resolve conflicts
Genesis929 Jun 1, 2026
3192c37
improve dataset restriction reporting and increase test timeout
Genesis929 Jun 2, 2026
ff44955
improve bigquery parser and optimize test cleanup
Genesis929 Jun 2, 2026
c1033af
fix cleanup race and parser bugs in bigquery integration
Genesis929 Jun 2, 2026
130b213
align BigQuery dataset restriction with main and improve system funct…
Genesis929 Jun 9, 2026
d6e799d
fix formatting in bigquerycommon utils
Genesis929 Jun 9, 2026
cd330c3
Merge branch 'main' into allowed_view_fix
Genesis929 Jun 9, 2026
c415b74
Merge branch 'main' into allowed_view_fix
Genesis929 Jun 10, 2026
681f966
Merge branch 'main' into allowed_view_fix
Genesis929 Jun 17, 2026
e757340
Merge branch 'origin/main' into allowed_view_fix and resolve conflicts
Genesis929 Jun 25, 2026
017409c
relocate CleanupBigQueryDatasets helper to bigquery integration tests
Genesis929 Jun 25, 2026
1b85448
remove redundant error wrappers from bigquerycommon utility
Genesis929 Jun 25, 2026
791d4a8
centralize and structure query validation errors in bigquery tools
Genesis929 Jun 25, 2026
0be4333
centralize and structure query validation errors in bigquery tools
Genesis929 Jun 25, 2026
cb5b67e
add unit tests for bigquery utility and validation functions
Genesis929 Jun 25, 2026
78f82b1
run gofmt and goimports to fix formatting and imports order
Genesis929 Jun 25, 2026
d4883ad
Merge branch 'main' into allowed_view_fix
Genesis929 Jun 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ layer of security by controlling which datasets can be accessed:
It will reject the query if it attempts to access any table outside the
allowed `datasets` list. To enforce this restriction, the following operations
are also disallowed:

- **Dataset-level operations** (e.g., `CREATE SCHEMA`, `ALTER SCHEMA`).
- **Unanalyzable operations** where the accessed tables cannot be determined
statically (e.g., `EXECUTE IMMEDIATE`, `CREATE PROCEDURE`, `CALL`).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, para
return nil, util.NewAgentError(fmt.Sprintf("unable to cast input_data parameter %s", paramsMap["input_data"]), nil)
}

bqClient, _, err := source.RetrieveClientAndService(accessToken)
bqClient, restService, err := source.RetrieveClientAndService(accessToken)
if err != nil {
return nil, util.NewClientServerError("failed to retrieve BigQuery client", http.StatusInternalServerError, err)
}
Expand Down Expand Up @@ -176,6 +176,26 @@ func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, para
var inputDataSource string
trimmedUpperInputData := strings.TrimSpace(strings.ToUpper(inputData))
if strings.HasPrefix(trimmedUpperInputData, "SELECT") || strings.HasPrefix(trimmedUpperInputData, "WITH") {
if len(source.BigQueryAllowedDatasets()) > 0 {
var connProps []*bigqueryapi.ConnectionProperty
session, err := source.BigQuerySession()(ctx)
if err != nil {
return nil, util.NewClientServerError("failed to get BigQuery session", http.StatusInternalServerError, err)
}
if session != nil {
connProps = []*bigqueryapi.ConnectionProperty{
{Key: "session_id", Value: session.ID},
}
}

dryRunJob, validationErr := bqutil.ValidateQueryAgainstAllowedDatasets(ctx, restService, source.BigQueryClient().Project(), source.BigQueryClient().Location, inputData, nil, connProps, source, source.GetMaximumBytesBilled(), false)
if validationErr != nil {
return nil, validationErr
}
if dryRunJob.Statistics.Query.StatementType != "SELECT" {
return nil, util.NewAgentError(fmt.Sprintf("the 'input_data' parameter only supports a table ID or a SELECT query. The provided query has statement type '%s'", dryRunJob.Statistics.Query.StatementType), nil)
}
}
inputDataSource = fmt.Sprintf("(%s)", inputData)
} else {
if !bqutil.ValidTableID(inputData) {
Expand Down Expand Up @@ -225,27 +245,12 @@ func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, para
// If not in protected mode, create a session for this invocation.
createModelQuery.CreateSession = true
}

if len(source.BigQueryAllowedDatasets()) > 0 {
createModelQuery.DryRun = true
dryRunJob, err := createModelQuery.Run(ctx)
if err != nil {
return nil, util.ProcessGcpError(err)
}
status := dryRunJob.LastStatus()
if status.Statistics != nil {
if qStats, ok := status.Statistics.Details.(*bigqueryapi.QueryStatistics); ok {
for _, tableRef := range qStats.ReferencedTables {
if !source.IsDatasetAllowed(tableRef.ProjectID, tableRef.DatasetID) {
return nil, util.NewAgentError(fmt.Sprintf("query accesses dataset '%s.%s', which is not in the allowed list", tableRef.ProjectID, tableRef.DatasetID), nil)
}
}
} else {
return nil, util.NewAgentError("could not get query statistics details during dry run validation", nil)
}
} else {
return nil, util.NewAgentError("could not dry run model creation query to validate allowed datasets", nil)
_, validationErr := bqutil.ValidateQueryAgainstAllowedDatasets(ctx, restService, source.BigQueryClient().Project(), source.BigQueryClient().Location, createModelSQL, nil, createModelQuery.ConnectionProperties, source, source.GetMaximumBytesBilled(), true)
if validationErr != nil {
return nil, validationErr
}
createModelQuery.DryRun = false
}

createModelJob, err := createModelQuery.Run(ctx)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/googleapis/mcp-toolbox/internal/tools/bigquery/bigqueryanalyzecontribution"
"github.com/googleapis/mcp-toolbox/internal/tools/bigquery/bigquerycommon"
"github.com/googleapis/mcp-toolbox/internal/util/parameters"
bigqueryrestapi "google.golang.org/api/bigquery/v2"
"google.golang.org/api/option"
)

Expand Down Expand Up @@ -319,9 +320,15 @@ func TestInvokeAllowedDatasetsValidation(t *testing.T) {
t.Fatalf("failed to create mocked BigQuery client: %v", err)
}

restService, err := bigqueryrestapi.NewService(ctx, option.WithEndpoint(mockServer.URL), option.WithoutAuthentication())
if err != nil {
t.Fatalf("failed to create mocked BigQuery REST service: %v", err)
}

// 3. Define mock source that returns this client and allowed datasets configuration
testSrc := &bigquerycommon.MockSource{
Client: bqClient,
Service: restService,
AllowedDatasets: []string{"allowed_dataset"},
}

Expand All @@ -348,7 +355,7 @@ func TestInvokeAllowedDatasetsValidation(t *testing.T) {

// 4. Set up parameters
data := map[string]any{
"input_data": "allowed_dataset.my_table",
"input_data": "SELECT * FROM unauthorized_dataset.some_table",
"contribution_metric": "SUM(metric)",
"is_test_col": "is_test",
"dimension_id_cols": []any{"dim1"},
Expand All @@ -370,7 +377,7 @@ func TestInvokeAllowedDatasetsValidation(t *testing.T) {
t.Fatal("expected Invoke to return an error due to out-of-allowlist dataset reference, but got nil")
}

expectedErr := "query accesses dataset 'test-project.unauthorized_dataset', which is not in the allowed list"
expectedErr := "access to dataset 'test-project.unauthorized_dataset' is not allowed"
if !strings.Contains(err.Error(), expectedErr) {
t.Errorf("expected error to contain %q, got: %v", expectedErr, err)
}
Expand Down
Loading
Loading