Skip to content

Commit 58ee72a

Browse files
feat(bigquery): Add revokeTableOrViewAccess feawture and tests
1 parent c87d659 commit 58ee72a

File tree

2 files changed

+232
-0
lines changed

2 files changed

+232
-0
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
const { BigQuery } = require("@google-cloud/bigquery");
16+
17+
/**
18+
* Revokes access to a BigQuery table or view
19+
* @param {Object} params - The parameters object
20+
* @param {string} params.projectId - The ID of the Google Cloud project
21+
* @param {string} params.datasetId - The ID of the dataset containing the table/view
22+
* @param {string} params.resourceId - The ID of the table or view
23+
* @param {string} [params.memberToRevoke] - Optional. Specific member to revoke access from (e.g., 'group:[email protected]')
24+
* @param {string} [params.roleToRevoke='roles/bigquery.dataViewer'] - Optional. Specific role to revoke
25+
* @returns {Promise<void>}
26+
*/
27+
async function revokeTableOrViewAccess({
28+
projectId,
29+
datasetId,
30+
resourceId,
31+
memberToRevoke,
32+
roleToRevoke = "roles/bigquery.dataViewer",
33+
}) {
34+
try {
35+
// Create BigQuery client
36+
const bigquery = new BigQuery({
37+
projectId: projectId,
38+
});
39+
40+
// Get reference to the table or view
41+
const dataset = bigquery.dataset(datasetId);
42+
const table = dataset.table(resourceId);
43+
44+
// Get current IAM policy
45+
const [policy] = await table.iam.getPolicy();
46+
console.log(
47+
"Current IAM Policy:",
48+
JSON.stringify(policy.bindings, null, 2)
49+
);
50+
51+
// Filter bindings based on parameters
52+
let newBindings = policy.bindings;
53+
54+
if (memberToRevoke) {
55+
// Remove specific member from specific role
56+
newBindings = policy.bindings
57+
.map((binding) => ({
58+
...binding,
59+
members:
60+
binding.role === roleToRevoke
61+
? binding.members.filter((member) => member !== memberToRevoke)
62+
: binding.members,
63+
}))
64+
.filter((binding) => binding.members.length > 0);
65+
} else {
66+
// Remove all bindings for the specified role
67+
newBindings = policy.bindings.filter(
68+
(binding) => binding.role !== roleToRevoke
69+
);
70+
}
71+
72+
// Create new policy with updated bindings
73+
const newPolicy = {
74+
bindings: newBindings,
75+
};
76+
77+
// Set the new IAM policy
78+
await table.iam.setPolicy(newPolicy);
79+
console.log(`Access revoked successfully for ${resourceId}`);
80+
81+
// Verify the changes
82+
const [updatedPolicy] = await table.iam.getPolicy();
83+
console.log(
84+
"Updated IAM Policy:",
85+
JSON.stringify(updatedPolicy.bindings, null, 2)
86+
);
87+
} catch (error) {
88+
console.error("Error revoking access:", error);
89+
throw error;
90+
}
91+
}
92+
93+
module.exports = { revokeTableOrViewAccess };
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
const { expect } = require("chai");
2+
const sinon = require("sinon");
3+
const { BigQuery } = require("@google-cloud/bigquery");
4+
const { revokeTableOrViewAccess } = require("../src/revokeTableOrViewAccess");
5+
6+
describe("revokeTableOrViewAccess", () => {
7+
let bigQueryStub;
8+
let tableStub;
9+
let constructorStub;
10+
11+
beforeEach(() => {
12+
// Create stubs for BigQuery client and its methods
13+
tableStub = {
14+
iam: {
15+
getPolicy: sinon.stub(),
16+
setPolicy: sinon.stub(),
17+
},
18+
};
19+
20+
const datasetStub = {
21+
table: sinon.stub().returns(tableStub),
22+
};
23+
24+
bigQueryStub = {
25+
dataset: sinon.stub().returns(datasetStub),
26+
};
27+
28+
// Stub the BigQuery constructor correctly
29+
constructorStub = sinon.stub(BigQuery.prototype, "constructor");
30+
constructorStub.returns(bigQueryStub);
31+
32+
// Stub the BigQuery class itself
33+
sinon.stub(BigQuery.prototype, "dataset").returns(datasetStub);
34+
});
35+
36+
afterEach(() => {
37+
sinon.restore();
38+
});
39+
40+
it("should revoke access for a specific member and role", async () => {
41+
// Setup test data
42+
const testPolicy = {
43+
bindings: [
44+
{
45+
role: "roles/bigquery.dataViewer",
46+
members: ["group:[email protected]", "group:[email protected]"],
47+
},
48+
],
49+
};
50+
51+
const expectedNewPolicy = {
52+
bindings: [
53+
{
54+
role: "roles/bigquery.dataViewer",
55+
members: ["group:[email protected]"],
56+
},
57+
],
58+
};
59+
60+
// Configure stubs
61+
tableStub.iam.getPolicy.resolves([testPolicy]);
62+
tableStub.iam.setPolicy.resolves([]);
63+
64+
// Test the function
65+
await revokeTableOrViewAccess({
66+
projectId: "test-project",
67+
datasetId: "test-dataset",
68+
resourceId: "test-table",
69+
memberToRevoke: "group:[email protected]",
70+
roleToRevoke: "roles/bigquery.dataViewer",
71+
});
72+
73+
// Verify the new policy was set correctly
74+
sinon.assert.calledWith(
75+
tableStub.iam.setPolicy,
76+
sinon.match(expectedNewPolicy)
77+
);
78+
});
79+
80+
it("should revoke all access for a specific role", async () => {
81+
// Setup test data
82+
const testPolicy = {
83+
bindings: [
84+
{
85+
role: "roles/bigquery.dataViewer",
86+
members: ["group:[email protected]"],
87+
},
88+
{
89+
role: "roles/bigquery.dataEditor",
90+
members: ["group:[email protected]"],
91+
},
92+
],
93+
};
94+
95+
const expectedNewPolicy = {
96+
bindings: [
97+
{
98+
role: "roles/bigquery.dataEditor",
99+
members: ["group:[email protected]"],
100+
},
101+
],
102+
};
103+
104+
// Configure stubs
105+
tableStub.iam.getPolicy.resolves([testPolicy]);
106+
tableStub.iam.setPolicy.resolves([]);
107+
108+
// Test the function
109+
await revokeTableOrViewAccess({
110+
projectId: "test-project",
111+
datasetId: "test-dataset",
112+
resourceId: "test-table",
113+
roleToRevoke: "roles/bigquery.dataViewer",
114+
});
115+
116+
// Verify the new policy was set correctly
117+
sinon.assert.calledWith(
118+
tableStub.iam.setPolicy,
119+
sinon.match(expectedNewPolicy)
120+
);
121+
});
122+
123+
it("should handle errors appropriately", async () => {
124+
// Configure stub to throw an error
125+
tableStub.iam.getPolicy.rejects(new Error("Test error"));
126+
127+
// Test the function
128+
try {
129+
await revokeTableOrViewAccess({
130+
projectId: "test-project",
131+
datasetId: "test-dataset",
132+
resourceId: "test-table",
133+
});
134+
expect.fail("Should have thrown an error");
135+
} catch (error) {
136+
expect(error.message).to.equal("Test error");
137+
}
138+
});
139+
});

0 commit comments

Comments
 (0)