From b694963f66dd10471f08f07265cfce4aa062cef7 Mon Sep 17 00:00:00 2001 From: steffnay Date: Mon, 22 Jun 2020 20:33:27 -0700 Subject: [PATCH 1/8] feat(iam): adds get_iam_policy --- google/cloud/bigquery/client.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/google/cloud/bigquery/client.py b/google/cloud/bigquery/client.py index 8e265d971..975878b3b 100644 --- a/google/cloud/bigquery/client.py +++ b/google/cloud/bigquery/client.py @@ -46,6 +46,7 @@ import google.api_core.client_options import google.api_core.exceptions +from google.api_core.iam import Policy from google.api_core import page_iterator import google.cloud._helpers from google.cloud import exceptions @@ -597,6 +598,30 @@ def get_dataset(self, dataset_ref, retry=DEFAULT_RETRY, timeout=None): ) return Dataset.from_api_repr(api_response) + def get_iam_policy( + self, table, requested_policy_version=1, retry=DEFAULT_RETRY, timeout=None, + ): + if not isinstance(table, (Table, TableReference)): + raise TypeError("table must be a Table or TableReference") + + if requested_policy_version != 1: + raise ValueError("only IAM policy version 1 is supported") + + options = {} + body = {} + options["requestedPolicyVersion"] = requested_policy_version + body["options"] = options + + path = "%s:getIamPolicy" % (table.path) + + response = self._call_api( + retry, method="POST", path=path, data=body, timeout=timeout, + ) + + iam_policy = Policy.from_api_repr(response) + + return iam_policy + def get_model(self, model_ref, retry=DEFAULT_RETRY, timeout=None): """[Beta] Fetch the model referenced by ``model_ref``. From 5f9eead55d25929f4e4084ed20bb06b16aa84b04 Mon Sep 17 00:00:00 2001 From: steffnay Date: Mon, 13 Jul 2020 10:37:24 -0700 Subject: [PATCH 2/8] feat: add ability to get and set table/view IAM policy --- google/cloud/bigquery/client.py | 26 ++++++- google/cloud/bigquery/iam.py | 38 ++++++++++ tests/system.py | 29 ++++++++ tests/unit/test_client.py | 118 ++++++++++++++++++++++++++++++++ 4 files changed, 209 insertions(+), 2 deletions(-) create mode 100644 google/cloud/bigquery/iam.py diff --git a/google/cloud/bigquery/client.py b/google/cloud/bigquery/client.py index 975878b3b..30ff2c4f6 100644 --- a/google/cloud/bigquery/client.py +++ b/google/cloud/bigquery/client.py @@ -618,9 +618,31 @@ def get_iam_policy( retry, method="POST", path=path, data=body, timeout=timeout, ) - iam_policy = Policy.from_api_repr(response) + return Policy.from_api_repr(response) - return iam_policy + def set_iam_policy( + self, table, policy, updateMask=None, retry=DEFAULT_RETRY, timeout=None, + ): + if not isinstance(table, (Table, TableReference)): + raise TypeError("table must be a Table or TableReference") + + if not isinstance(policy, (Policy)): + raise TypeError("policy must be a Policy") + + body = {} + + if updateMask is not None: + body["updateMask"] = updateMask + + body["policy"] = policy.to_api_repr() + + path = "%s:setIamPolicy" % (table.path) + + response = self._call_api( + retry, method="POST", path=path, data=body, timeout=timeout, + ) + + return Policy.from_api_repr(response) def get_model(self, model_ref, retry=DEFAULT_RETRY, timeout=None): """[Beta] Fetch the model referenced by ``model_ref``. diff --git a/google/cloud/bigquery/iam.py b/google/cloud/bigquery/iam.py new file mode 100644 index 000000000..1f12028c3 --- /dev/null +++ b/google/cloud/bigquery/iam.py @@ -0,0 +1,38 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""BigQuery API IAM policy definitions + +For all allowed roles and permissions, see: + +https://cloud.google.com/bigquery/docs/access-control +""" + +# BigQuery-specific IAM roles available for tables and views + +BIGQUERY_DATA_EDITOR_ROLE = "roles/bigquery.dataEditor" +"""When applied to a table or view, this role provides permissions to +read and update data and metadata for the table or view.""" + +BIGQUERY_DATA_OWNER_ROLE = "roles/bigquery.dataOwner" +"""When applied to a table or view, this role provides permissions to +read and update data and metadata for the table or view, share the +table/view, and delete the table/view.""" + +BIGQUERY_DATA_VIEWER_ROLE = "roles/bigquery.dataViewer" +"""When applied to a table or view, this role provides permissions to +read data and metadata from the table or view.""" + +BIGQUERY_METADATA_VIEWER_ROLE = "roles/bigquery.metadataViewer" +"""When papplied to a table or view, this role provides persmissions to +read metadata from the table or view.""" diff --git a/tests/system.py b/tests/system.py index 965c34331..72bad3b65 100644 --- a/tests/system.py +++ b/tests/system.py @@ -71,6 +71,7 @@ from google.api_core.exceptions import InternalServerError from google.api_core.exceptions import ServiceUnavailable from google.api_core.exceptions import TooManyRequests +from google.api_core.iam import Policy from google.cloud import bigquery from google.cloud import bigquery_v2 from google.cloud.bigquery.dataset import Dataset @@ -1407,6 +1408,34 @@ def test_copy_table(self): got_rows = self._fetch_single_page(dest_table) self.assertTrue(len(got_rows) > 0) + def test_iam_policy(self): + from google.cloud.bigquery.iam import BIGQUERY_DATA_VIEWER_ROLE + + dataset = self.temp_dataset(_make_dataset_id("create_table")) + table_id = "test_table" + table_arg = Table(dataset.table(table_id), schema=SCHEMA) + self.assertFalse(_table_exists(table_arg)) + + table = retry_403(Config.CLIENT.create_table)(table_arg) + self.to_delete.insert(0, table) + + self.assertTrue(_table_exists(table)) + + member = "serviceAccount:{}".format(Config.CLIENT.get_service_account_email()) + BINDING = { + "role": BIGQUERY_DATA_VIEWER_ROLE, + "members": {member}, + } + + policy = Config.CLIENT.get_iam_policy(table) + self.assertIsInstance(policy, Policy) + self.assertEqual(policy.bindings, []) + + policy.bindings.append(BINDING) + returned_policy = Config.CLIENT.set_iam_policy(table, policy) + + self.assertEqual(returned_policy.bindings, policy.bindings) + def test_job_cancel(self): DATASET_ID = _make_dataset_id("job_cancel") JOB_ID_PREFIX = "fetch_" + DATASET_ID diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 0e083d43f..64f2e1482 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -1748,6 +1748,124 @@ def test_get_table_sets_user_agent(self): ) self.assertIn("my-application/1.2.3", expected_user_agent) + def test_get_iam_policy(self): + from google.cloud.bigquery.iam import BIGQUERY_DATA_EDITOR_ROLE + from google.cloud.bigquery.iam import BIGQUERY_DATA_OWNER_ROLE + from google.cloud.bigquery.iam import BIGQUERY_DATA_VIEWER_ROLE + from google.api_core.iam import Policy + + PATH = "/projects/%s/datasets/%s/tables/%s:getIamPolicy" % ( + self.PROJECT, + self.DS_ID, + self.TABLE_ID, + ) + BODY = {"options": {"requestedPolicyVersion": 1}} + ETAG = "CARDI" + VERSION = 1 + OWNER1 = "user:phred@example.com" + OWNER2 = "group:cloud-logs@google.com" + EDITOR1 = "domain:google.com" + EDITOR2 = "user:phred@example.com" + VIEWER1 = "serviceAccount:1234-abcdef@service.example.com" + VIEWER2 = "user:phred@example.com" + RETURNED = { + "resourceId": PATH, + "etag": ETAG, + "version": VERSION, + "bindings": [ + {"role": BIGQUERY_DATA_EDITOR_ROLE, "members": [OWNER1, OWNER2]}, + {"role": BIGQUERY_DATA_OWNER_ROLE, "members": [EDITOR1, EDITOR2]}, + {"role": BIGQUERY_DATA_VIEWER_ROLE, "members": [VIEWER1, VIEWER2]}, + ], + } + EXPECTED = { + binding["role"]: set(binding["members"]) for binding in RETURNED["bindings"] + } + + creds = _make_credentials() + http = object() + client = self._make_one(project=self.PROJECT, credentials=creds, _http=http) + conn = client._connection = make_connection(RETURNED) + + policy = client.get_iam_policy(self.TABLE_REF, timeout=7.5) + + conn.api_request.assert_called_once_with( + method="POST", path=PATH, data=BODY, timeout=7.5 + ) + + self.assertIsInstance(policy, Policy) + self.assertEqual(policy.etag, RETURNED["etag"]) + self.assertEqual(policy.version, RETURNED["version"]) + self.assertEqual(dict(policy), EXPECTED) + + def test_get_iam_policy_w_invalid_table(self): + creds = _make_credentials() + http = object() + client = self._make_one(project=self.PROJECT, credentials=creds, _http=http) + + table_resource_string = "projects/%s/datasets/%s/tables/%s" % ( + self.PROJECT, + self.DS_ID, + self.TABLE_ID, + ) + + with self.assertRaises(TypeError): + client.get_iam_policy(table_resource_string) + + def test_get_iam_policy_w_invalid_version(self): + creds = _make_credentials() + http = object() + client = self._make_one(project=self.PROJECT, credentials=creds, _http=http) + + with self.assertRaises(ValueError): + client.get_iam_policy(self.TABLE_REF, requested_policy_version=2) + + def test_set_iam_policy(self): + from google.cloud.bigquery.iam import BIGQUERY_DATA_EDITOR_ROLE + from google.cloud.bigquery.iam import BIGQUERY_DATA_OWNER_ROLE + from google.cloud.bigquery.iam import BIGQUERY_DATA_VIEWER_ROLE + from google.api_core.iam import Policy + + PATH = "/projects/%s/datasets/%s/tables/%s:setIamPolicy" % ( + self.PROJECT, + self.DS_ID, + self.TABLE_ID, + ) + ETAG = "CARDI" + VERSION = 1 + OWNER1 = "user:phred@example.com" + OWNER2 = "group:cloud-logs@google.com" + EDITOR1 = "domain:google.com" + EDITOR2 = "user:phred@example.com" + VIEWER1 = "serviceAccount:1234-abcdef@service.example.com" + VIEWER2 = "user:phred@example.com" + BINDINGS = [ + {"role": BIGQUERY_DATA_EDITOR_ROLE, "members": [OWNER1, OWNER2]}, + {"role": BIGQUERY_DATA_OWNER_ROLE, "members": [EDITOR1, EDITOR2]}, + {"role": BIGQUERY_DATA_VIEWER_ROLE, "members": [VIEWER1, VIEWER2]}, + ] + RETURNED = {"etag": ETAG, "version": VERSION, "bindings": BINDINGS} + + policy = Policy() + for binding in BINDINGS: + policy[binding["role"]] = binding["members"] + + BODY = {"policy": policy.to_api_repr()} + + creds = _make_credentials() + http = object() + client = self._make_one(project=self.PROJECT, credentials=creds, _http=http) + conn = client._connection = make_connection(RETURNED) + + returned_policy = client.set_iam_policy(self.TABLE_REF, policy, timeout=7.5) + + conn.api_request.assert_called_once_with( + method="POST", path=PATH, data=BODY, timeout=7.5 + ) + self.assertEqual(returned_policy.etag, ETAG) + self.assertEqual(returned_policy.version, VERSION) + self.assertEqual(dict(returned_policy), dict(policy)) + def test_update_dataset_w_invalid_field(self): from google.cloud.bigquery.dataset import Dataset From 13e1be10d1e6df592d24b9bdf4bdd742c88a94c0 Mon Sep 17 00:00:00 2001 From: steffnay Date: Mon, 13 Jul 2020 12:28:59 -0700 Subject: [PATCH 3/8] test: add updateMask to test_set_iam_policy --- tests/unit/test_client.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 64f2e1482..3d4db9f7a 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -1844,20 +1844,23 @@ def test_set_iam_policy(self): {"role": BIGQUERY_DATA_OWNER_ROLE, "members": [EDITOR1, EDITOR2]}, {"role": BIGQUERY_DATA_VIEWER_ROLE, "members": [VIEWER1, VIEWER2]}, ] + MASK = "bindings,etag" RETURNED = {"etag": ETAG, "version": VERSION, "bindings": BINDINGS} policy = Policy() for binding in BINDINGS: policy[binding["role"]] = binding["members"] - BODY = {"policy": policy.to_api_repr()} + BODY = {"policy": policy.to_api_repr(), "updateMask": MASK} creds = _make_credentials() http = object() client = self._make_one(project=self.PROJECT, credentials=creds, _http=http) conn = client._connection = make_connection(RETURNED) - returned_policy = client.set_iam_policy(self.TABLE_REF, policy, timeout=7.5) + returned_policy = client.set_iam_policy( + self.TABLE_REF, policy, updateMask=MASK, timeout=7.5 + ) conn.api_request.assert_called_once_with( method="POST", path=PATH, data=BODY, timeout=7.5 @@ -1866,6 +1869,19 @@ def test_set_iam_policy(self): self.assertEqual(returned_policy.version, VERSION) self.assertEqual(dict(returned_policy), dict(policy)) + def test_set_iam_policy_invalid_policy(self): + from google.api_core.iam import Policy + + policy = Policy() + invalid_policy_repr = policy.to_api_repr() + + creds = _make_credentials() + http = object() + client = self._make_one(project=self.PROJECT, credentials=creds, _http=http) + + with self.assertRaises(TypeError): + client.set_iam_policy(self.TABLE_REF, invalid_policy_repr) + def test_update_dataset_w_invalid_field(self): from google.cloud.bigquery.dataset import Dataset From a769276d253af45c8737e4425191b264fb974607 Mon Sep 17 00:00:00 2001 From: steffnay Date: Mon, 13 Jul 2020 16:33:20 -0700 Subject: [PATCH 4/8] test: add test for invalid table --- tests/unit/test_client.py | 56 ++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 3d4db9f7a..f060bc9fa 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -1749,8 +1749,8 @@ def test_get_table_sets_user_agent(self): self.assertIn("my-application/1.2.3", expected_user_agent) def test_get_iam_policy(self): - from google.cloud.bigquery.iam import BIGQUERY_DATA_EDITOR_ROLE from google.cloud.bigquery.iam import BIGQUERY_DATA_OWNER_ROLE + from google.cloud.bigquery.iam import BIGQUERY_DATA_EDITOR_ROLE from google.cloud.bigquery.iam import BIGQUERY_DATA_VIEWER_ROLE from google.api_core.iam import Policy @@ -1773,8 +1773,8 @@ def test_get_iam_policy(self): "etag": ETAG, "version": VERSION, "bindings": [ - {"role": BIGQUERY_DATA_EDITOR_ROLE, "members": [OWNER1, OWNER2]}, - {"role": BIGQUERY_DATA_OWNER_ROLE, "members": [EDITOR1, EDITOR2]}, + {"role": BIGQUERY_DATA_OWNER_ROLE, "members": [OWNER1, OWNER2]}, + {"role": BIGQUERY_DATA_EDITOR_ROLE, "members": [EDITOR1, EDITOR2]}, {"role": BIGQUERY_DATA_VIEWER_ROLE, "members": [VIEWER1, VIEWER2]}, ], } @@ -1821,8 +1821,8 @@ def test_get_iam_policy_w_invalid_version(self): client.get_iam_policy(self.TABLE_REF, requested_policy_version=2) def test_set_iam_policy(self): - from google.cloud.bigquery.iam import BIGQUERY_DATA_EDITOR_ROLE from google.cloud.bigquery.iam import BIGQUERY_DATA_OWNER_ROLE + from google.cloud.bigquery.iam import BIGQUERY_DATA_EDITOR_ROLE from google.cloud.bigquery.iam import BIGQUERY_DATA_VIEWER_ROLE from google.api_core.iam import Policy @@ -1831,7 +1831,7 @@ def test_set_iam_policy(self): self.DS_ID, self.TABLE_ID, ) - ETAG = "CARDI" + ETAG = "foo" VERSION = 1 OWNER1 = "user:phred@example.com" OWNER2 = "group:cloud-logs@google.com" @@ -1840,8 +1840,8 @@ def test_set_iam_policy(self): VIEWER1 = "serviceAccount:1234-abcdef@service.example.com" VIEWER2 = "user:phred@example.com" BINDINGS = [ - {"role": BIGQUERY_DATA_EDITOR_ROLE, "members": [OWNER1, OWNER2]}, - {"role": BIGQUERY_DATA_OWNER_ROLE, "members": [EDITOR1, EDITOR2]}, + {"role": BIGQUERY_DATA_OWNER_ROLE, "members": [OWNER1, OWNER2]}, + {"role": BIGQUERY_DATA_EDITOR_ROLE, "members": [EDITOR1, EDITOR2]}, {"role": BIGQUERY_DATA_VIEWER_ROLE, "members": [VIEWER1, VIEWER2]}, ] MASK = "bindings,etag" @@ -1869,6 +1869,30 @@ def test_set_iam_policy(self): self.assertEqual(returned_policy.version, VERSION) self.assertEqual(dict(returned_policy), dict(policy)) + def test_set_iam_policy_no_mask(self): + from google.api_core.iam import Policy + + PATH = "/projects/%s/datasets/%s/tables/%s:setIamPolicy" % ( + self.PROJECT, + self.DS_ID, + self.TABLE_ID, + ) + RETURNED = {"etag": "foo", "version": 1, "bindings": []} + + policy = Policy() + BODY = {"policy": policy.to_api_repr()} + + creds = _make_credentials() + http = object() + client = self._make_one(project=self.PROJECT, credentials=creds, _http=http) + conn = client._connection = make_connection(RETURNED) + + client.set_iam_policy(self.TABLE_REF, policy, timeout=7.5) + + conn.api_request.assert_called_once_with( + method="POST", path=PATH, data=BODY, timeout=7.5 + ) + def test_set_iam_policy_invalid_policy(self): from google.api_core.iam import Policy @@ -1882,6 +1906,24 @@ def test_set_iam_policy_invalid_policy(self): with self.assertRaises(TypeError): client.set_iam_policy(self.TABLE_REF, invalid_policy_repr) + def test_set_iam_policy_w_invalid_table(self): + from google.api_core.iam import Policy + + policy = Policy() + + creds = _make_credentials() + http = object() + client = self._make_one(project=self.PROJECT, credentials=creds, _http=http) + + table_resource_string = "projects/%s/datasets/%s/tables/%s" % ( + self.PROJECT, + self.DS_ID, + self.TABLE_ID, + ) + + with self.assertRaises(TypeError): + client.set_iam_policy(table_resource_string, policy) + def test_update_dataset_w_invalid_field(self): from google.cloud.bigquery.dataset import Dataset From ff65fec4a43222b532fcf85adc44ba76a78740ff Mon Sep 17 00:00:00 2001 From: steffnay Date: Fri, 24 Jul 2020 12:22:56 -0700 Subject: [PATCH 5/8] feat: adds test_iam_permissions --- google/cloud/bigquery/client.py | 18 ++++++++++++++++++ tests/system.py | 28 ++++++++++++++++++++++++---- tests/unit/test_client.py | 22 ++++++++++++++++++++++ 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/google/cloud/bigquery/client.py b/google/cloud/bigquery/client.py index 408f2568e..a07eaef94 100644 --- a/google/cloud/bigquery/client.py +++ b/google/cloud/bigquery/client.py @@ -640,6 +640,24 @@ def set_iam_policy( return Policy.from_api_repr(response) + def test_iam_permissions( + self, table, permissions, retry=DEFAULT_RETRY, timeout=None, + ): + if not isinstance(table, (Table, TableReference)): + raise TypeError("table must be a Table or TableReference") + + body = {} + + body["permissions"] = permissions + + path = "%s:testIamPermissions" % (table.path) + + response = self._call_api( + retry, method="POST", path=path, data=body, timeout=timeout, + ) + + return response + def get_model(self, model_ref, retry=DEFAULT_RETRY, timeout=None): """[Beta] Fetch the model referenced by ``model_ref``. diff --git a/tests/system.py b/tests/system.py index ddd76afc5..e7e5df944 100644 --- a/tests/system.py +++ b/tests/system.py @@ -1413,10 +1413,10 @@ def test_iam_policy(self): dataset = self.temp_dataset(_make_dataset_id("create_table")) table_id = "test_table" - table_arg = Table(dataset.table(table_id), schema=SCHEMA) - self.assertFalse(_table_exists(table_arg)) + table_ref = Table(dataset.table(table_id)) + self.assertFalse(_table_exists(table_ref)) - table = retry_403(Config.CLIENT.create_table)(table_arg) + table = retry_403(Config.CLIENT.create_table)(table_ref) self.to_delete.insert(0, table) self.assertTrue(_table_exists(table)) @@ -1433,9 +1433,29 @@ def test_iam_policy(self): policy.bindings.append(BINDING) returned_policy = Config.CLIENT.set_iam_policy(table, policy) - self.assertEqual(returned_policy.bindings, policy.bindings) + def test_test_iam_permissions(self): + dataset = self.temp_dataset(_make_dataset_id("create_table")) + table_id = "test_table" + table_ref = Table(dataset.table(table_id)) + self.assertFalse(_table_exists(table_ref)) + + table = retry_403(Config.CLIENT.create_table)(table_ref) + self.to_delete.insert(0, table) + + self.assertTrue(_table_exists(table)) + + # Test some default permissions. + permissions = [ + "bigquery.tables.get", + "bigquery.tables.getData", + "bigquery.tables.update", + ] + + response = Config.CLIENT.test_iam_permissions(table, [permissions]) + self.assertCountEqual(response["permissions"], permissions) + def test_job_cancel(self): DATASET_ID = _make_dataset_id("job_cancel") JOB_ID_PREFIX = "fetch_" + DATASET_ID diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index f060bc9fa..df8e5e711 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -1924,6 +1924,28 @@ def test_set_iam_policy_w_invalid_table(self): with self.assertRaises(TypeError): client.set_iam_policy(table_resource_string, policy) + def test_test_iam_permissions(self): + PATH = "/projects/%s/datasets/%s/tables/%s:testIamPermissions" % ( + self.PROJECT, + self.DS_ID, + self.TABLE_ID, + ) + + PERMISSIONS = ["bigquery.tables.get", "bigquery.tables.update"] + BODY = {"permissions": PERMISSIONS} + RETURNED = {"permissions": PERMISSIONS} + + creds = _make_credentials() + http = object() + client = self._make_one(project=self.PROJECT, credentials=creds, _http=http) + conn = client._connection = make_connection(RETURNED) + + client.test_iam_permissions(self.TABLE_REF, PERMISSIONS, timeout=7.5) + + conn.api_request.assert_called_once_with( + method="POST", path=PATH, data=BODY, timeout=7.5 + ) + def test_update_dataset_w_invalid_field(self): from google.cloud.bigquery.dataset import Dataset From 2d1a5efd495d8fccf85dc90958ef4b377e17e7bb Mon Sep 17 00:00:00 2001 From: steffnay Date: Fri, 24 Jul 2020 13:55:13 -0700 Subject: [PATCH 6/8] test: adds test_iam_permissions test --- google/cloud/bigquery/iam.py | 2 +- tests/unit/test_client.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/google/cloud/bigquery/iam.py b/google/cloud/bigquery/iam.py index 1f12028c3..df9db36b7 100644 --- a/google/cloud/bigquery/iam.py +++ b/google/cloud/bigquery/iam.py @@ -34,5 +34,5 @@ read data and metadata from the table or view.""" BIGQUERY_METADATA_VIEWER_ROLE = "roles/bigquery.metadataViewer" -"""When papplied to a table or view, this role provides persmissions to +"""When applied to a table or view, this role provides persmissions to read metadata from the table or view.""" diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 02a67ba8b..6e9da3479 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -1946,6 +1946,22 @@ def test_test_iam_permissions(self): method="POST", path=PATH, data=BODY, timeout=7.5 ) + def test_test_iam_permissions_w_invalid_table(self): + creds = _make_credentials() + http = object() + client = self._make_one(project=self.PROJECT, credentials=creds, _http=http) + + table_resource_string = "projects/%s/datasets/%s/tables/%s" % ( + self.PROJECT, + self.DS_ID, + self.TABLE_ID, + ) + + PERMISSIONS = ["bigquery.tables.get", "bigquery.tables.update"] + + with self.assertRaises(TypeError): + client.test_iam_permissions(table_resource_string, PERMISSIONS) + def test_update_dataset_w_invalid_field(self): from google.cloud.bigquery.dataset import Dataset From 8dd8763f20ab8bcfaa3965e8e0ad0386564941d6 Mon Sep 17 00:00:00 2001 From: steffnay Date: Tue, 28 Jul 2020 14:49:05 -0700 Subject: [PATCH 7/8] test: updates assertion --- tests/system.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/system.py b/tests/system.py index fa5224be9..b59e4311b 100644 --- a/tests/system.py +++ b/tests/system.py @@ -1454,7 +1454,7 @@ def test_test_iam_permissions(self): ] response = Config.CLIENT.test_iam_permissions(table, [permissions]) - self.assertCountEqual(response["permissions"], permissions) + self.assertEqual(set(response["permissions"]), set(permissions)) def test_job_cancel(self): DATASET_ID = _make_dataset_id("job_cancel") From bf6964ecaecf74d3d0c47f11d7b9bf6e695dd729 Mon Sep 17 00:00:00 2001 From: steffnay Date: Wed, 29 Jul 2020 16:08:08 -0700 Subject: [PATCH 8/8] refactor --- google/cloud/bigquery/client.py | 19 ++++++------------- tests/system.py | 2 +- tests/unit/test_client.py | 12 ++++-------- 3 files changed, 11 insertions(+), 22 deletions(-) diff --git a/google/cloud/bigquery/client.py b/google/cloud/bigquery/client.py index 9ae6595b1..651f0263e 100644 --- a/google/cloud/bigquery/client.py +++ b/google/cloud/bigquery/client.py @@ -615,12 +615,9 @@ def get_iam_policy( if requested_policy_version != 1: raise ValueError("only IAM policy version 1 is supported") - options = {} - body = {} - options["requestedPolicyVersion"] = requested_policy_version - body["options"] = options + body = {"options": {"requestedPolicyVersion": 1}} - path = "%s:getIamPolicy" % (table.path) + path = "{}:getIamPolicy".format(table.path) response = self._call_api( retry, method="POST", path=path, data=body, timeout=timeout, @@ -637,14 +634,12 @@ def set_iam_policy( if not isinstance(policy, (Policy)): raise TypeError("policy must be a Policy") - body = {} + body = {"policy": policy.to_api_repr()} if updateMask is not None: body["updateMask"] = updateMask - body["policy"] = policy.to_api_repr() - - path = "%s:setIamPolicy" % (table.path) + path = "{}:setIamPolicy".format(table.path) response = self._call_api( retry, method="POST", path=path, data=body, timeout=timeout, @@ -658,11 +653,9 @@ def test_iam_permissions( if not isinstance(table, (Table, TableReference)): raise TypeError("table must be a Table or TableReference") - body = {} - - body["permissions"] = permissions + body = {"permissions": permissions} - path = "%s:testIamPermissions" % (table.path) + path = "{}:testIamPermissions".format(table.path) response = self._call_api( retry, method="POST", path=path, data=body, timeout=timeout, diff --git a/tests/system.py b/tests/system.py index b59e4311b..50e2dc7de 100644 --- a/tests/system.py +++ b/tests/system.py @@ -1408,7 +1408,7 @@ def test_copy_table(self): got_rows = self._fetch_single_page(dest_table) self.assertTrue(len(got_rows) > 0) - def test_iam_policy(self): + def test_get_set_iam_policy(self): from google.cloud.bigquery.iam import BIGQUERY_DATA_VIEWER_ROLE dataset = self.temp_dataset(_make_dataset_id("create_table")) diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 4e3d21b68..5687a27ec 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -1754,10 +1754,8 @@ def test_get_iam_policy(self): from google.cloud.bigquery.iam import BIGQUERY_DATA_VIEWER_ROLE from google.api_core.iam import Policy - PATH = "/projects/%s/datasets/%s/tables/%s:getIamPolicy" % ( - self.PROJECT, - self.DS_ID, - self.TABLE_ID, + PATH = "/projects/{}/datasets/{}/tables/{}:getIamPolicy".format( + self.PROJECT, self.DS_ID, self.TABLE_ID, ) BODY = {"options": {"requestedPolicyVersion": 1}} ETAG = "CARDI" @@ -1803,10 +1801,8 @@ def test_get_iam_policy_w_invalid_table(self): http = object() client = self._make_one(project=self.PROJECT, credentials=creds, _http=http) - table_resource_string = "projects/%s/datasets/%s/tables/%s" % ( - self.PROJECT, - self.DS_ID, - self.TABLE_ID, + table_resource_string = "projects/{}/datasets/{}/tables/{}".format( + self.PROJECT, self.DS_ID, self.TABLE_ID, ) with self.assertRaises(TypeError):