-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(bigquery): support IAM conditions in datasets (#11123)
This PR adds support for IAM conditions via the existing dataset access mechanism. To do so, the following changes are necessary: * add the `Expr` type for expressing conditions, and wire it into the existing DatasetAccessEntry. * add an option pattern to the Dataset-related RPC methods * Add a new WithAccessPolicyVersion option for setting access policies To expose the new functionality, this PR adds CreateWithOptions, UpdateWithOptions, MetadataWithOptions methods on Dataset that accept the new option.
- Loading branch information
Showing
3 changed files
with
260 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,7 @@ import ( | |
"time" | ||
|
||
"cloud.google.com/go/internal/testutil" | ||
"github.com/google/go-cmp/cmp" | ||
"github.com/google/go-cmp/cmp/cmpopts" | ||
) | ||
|
||
|
@@ -435,6 +436,102 @@ func TestIntegration_DatasetUpdateAccess(t *testing.T) { | |
} | ||
} | ||
|
||
// This test validates behaviors related to IAM conditions in | ||
// dataset access control. | ||
func TestIntegration_DatasetConditions(t *testing.T) { | ||
if client == nil { | ||
t.Skip("Integration tests skipped") | ||
} | ||
ctx := context.Background() | ||
// Use our test dataset for a base access policy. | ||
md, err := dataset.Metadata(ctx) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
wantEntry := &AccessEntry{ | ||
Role: ReaderRole, | ||
Entity: "[email protected]", | ||
EntityType: UserEmailEntity, | ||
Condition: &Expr{ | ||
Expression: "request.time < timestamp('2030-01-01T00:00:00Z')", | ||
Description: "requests before the year 2030", | ||
Title: "test condition", | ||
}, | ||
} | ||
origAccess := append(md.Access, wantEntry) | ||
|
||
ds := client.Dataset(datasetIDs.New()) | ||
wantMeta := &DatasetMetadata{ | ||
Access: origAccess, | ||
Description: "test dataset", | ||
} | ||
|
||
// First, attempt to create the dataset without specifying a policy access version. | ||
err = ds.Create(ctx, wantMeta) | ||
if err == nil { | ||
t.Fatalf("expected Create failure, but succeeded") | ||
} | ||
|
||
err = ds.CreateWithOptions(ctx, wantMeta, WithAccessPolicyVersion(3)) | ||
if err != nil { | ||
t.Fatalf("expected Create to succeed, but failed: %v", err) | ||
} | ||
defer func() { | ||
if err := ds.Delete(ctx); err != nil { | ||
t.Logf("defer deletion failed: %v", err) | ||
} | ||
}() | ||
|
||
// Now, get the dataset without specifying policy version | ||
md, err = ds.Metadata(ctx) | ||
if err != nil { | ||
t.Fatalf("Metadata: %v", err) | ||
} | ||
for _, entry := range md.Access { | ||
if entry.Entity == wantEntry.Entity && | ||
entry.Condition != nil { | ||
t.Fatalf("got policy with condition without specifying access policy version") | ||
} | ||
} | ||
|
||
// Re-fetch metadata with access policy specified. | ||
md, err = ds.MetadataWithOptions(ctx, WithAccessPolicyVersion(3)) | ||
if err != nil { | ||
t.Fatalf("Metadata (WithAccessPolicy): %v", err) | ||
} | ||
var foundEntry bool | ||
for _, entry := range md.Access { | ||
if entry.Entity == wantEntry.Entity { | ||
if cmp.Equal(entry.Condition, wantEntry.Condition) { | ||
foundEntry = true | ||
break | ||
} | ||
} | ||
} | ||
if !foundEntry { | ||
t.Fatalf("failed to find wanted entry in access list") | ||
} | ||
|
||
newAccess := append(origAccess, &AccessEntry{ | ||
Role: ReaderRole, | ||
Entity: "allUsers", | ||
EntityType: IAMMemberEntity, | ||
}) | ||
|
||
// append another entry. Should fail without sending access policy version since we have conditions present. | ||
md, err = ds.Update(ctx, DatasetMetadataToUpdate{Access: newAccess}, "") | ||
if err == nil { | ||
t.Fatalf("Update succeeded where failure expected: %v", err) | ||
} | ||
|
||
md, err = ds.UpdateWithOptions(ctx, DatasetMetadataToUpdate{Access: newAccess}, "", WithAccessPolicyVersion(3)) | ||
if err != nil { | ||
t.Fatalf("Update failed: %v", err) | ||
} | ||
|
||
} | ||
|
||
// Comparison function for AccessEntries to enable order insensitive equality checking. | ||
func lessAccessEntries(x, y *AccessEntry) bool { | ||
if x.Entity < y.Entity { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -420,6 +420,15 @@ func TestBQToDatasetMetadata(t *testing.T) { | |
Labels: map[string]string{"x": "y"}, | ||
Access: []*bq.DatasetAccess{ | ||
{Role: "READER", UserByEmail: "[email protected]"}, | ||
{Role: "READER", | ||
UserByEmail: "[email protected]", | ||
Condition: &bq.Expr{ | ||
Description: "desc", | ||
Expression: "expr", | ||
Location: "loc", | ||
Title: "title", | ||
}, | ||
}, | ||
{Role: "WRITER", GroupByEmail: "[email protected]"}, | ||
{ | ||
Dataset: &bq.DatasetAccessEntry{ | ||
|
@@ -456,7 +465,20 @@ func TestBQToDatasetMetadata(t *testing.T) { | |
Location: "EU", | ||
Labels: map[string]string{"x": "y"}, | ||
Access: []*AccessEntry{ | ||
{Role: ReaderRole, Entity: "[email protected]", EntityType: UserEmailEntity}, | ||
{Role: ReaderRole, | ||
Entity: "[email protected]", | ||
EntityType: UserEmailEntity, | ||
}, | ||
{Role: ReaderRole, | ||
Entity: "[email protected]", | ||
EntityType: UserEmailEntity, | ||
Condition: &Expr{ | ||
Title: "title", | ||
Expression: "expr", | ||
Location: "loc", | ||
Description: "desc", | ||
}, | ||
}, | ||
{Role: WriterRole, Entity: "[email protected]", EntityType: GroupEmailEntity}, | ||
{ | ||
EntityType: DatasetEntity, | ||
|
@@ -536,6 +558,12 @@ func TestConvertAccessEntry(t *testing.T) { | |
{Role: OwnerRole, Entity: "e", EntityType: UserEmailEntity}, | ||
{Role: ReaderRole, Entity: "e", EntityType: SpecialGroupEntity}, | ||
{Role: ReaderRole, Entity: "e", EntityType: IAMMemberEntity}, | ||
{Role: WriterRole, Entity: "e", EntityType: IAMMemberEntity, | ||
Condition: &Expr{Expression: "expr", | ||
Title: "title", | ||
Location: "loc", | ||
Description: "desc", | ||
}}, | ||
{Role: ReaderRole, EntityType: ViewEntity, | ||
View: &Table{ProjectID: "p", DatasetID: "d", TableID: "t", c: c}}, | ||
{Role: ReaderRole, EntityType: RoutineEntity, | ||
|