From f42a8e28722fa1f7e0db9e6b581864c6ad8f0030 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 20 May 2019 14:51:00 -0400 Subject: [PATCH 1/2] Add 'google.cloud.storage.hmac_keys.HMACKeyMetadata' resource class. Toward #7851. --- storage/google/cloud/storage/hmac_key.py | 126 +++++++++++++++++++++++ storage/tests/unit/test_hmac_key.py | 118 +++++++++++++++++++++ 2 files changed, 244 insertions(+) create mode 100644 storage/google/cloud/storage/hmac_key.py create mode 100644 storage/tests/unit/test_hmac_key.py diff --git a/storage/google/cloud/storage/hmac_key.py b/storage/google/cloud/storage/hmac_key.py new file mode 100644 index 000000000000..e66d1da60703 --- /dev/null +++ b/storage/google/cloud/storage/hmac_key.py @@ -0,0 +1,126 @@ +# Copyright 2019 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. + +from google.cloud._helpers import _rfc3339_to_datetime + + +class HMACKeyMetadata(object): + """Metadata about an HMAC service account key withn Cloud Storage. + + :type client: :class:`~google.cloud.stoage.client.Client` + :param client: client associated with the key metadata. + """ + + ACTIVE_STATE = "ACTIVE" + """Key is active, and may be used to sign requests.""" + INACTIVE_STATE = "INACTIVE" + """Key is inactive, and may not be used to sign requests. + + It can be re-activated via :meth:`update`. + """ + DELETED_STATE = "DELETED" + """Key is deleted. It cannot be re-activated.""" + + _SETTABLE_STATES = (ACTIVE_STATE, INACTIVE_STATE) + + def __init__(self, client): + self._client = client + self._properties = {} + + @property + def access_id(self): + """ID of the key. + + :rtype: str or None + :returns: unique identifier of the key within a project. + """ + return self._properties.get("accessId") + + @property + def etag(self): + """ETag identifying the version of the key metadata. + + :rtype: str or None + :returns: ETag for the version of the key's metadata. + """ + return self._properties.get("etag") + + @property + def project(self): + """Project ID associated with the key. + + :rtype: str or None + :returns: project identfier for the key. + """ + return self._properties.get("projectId") + + @property + def service_account_email(self): + """Service account e-mail address associated with the key. + + :rtype: str or None + :returns: e-mail address for the service account which created the key. + """ + return self._properties.get("serviceAccountEmail") + + @property + def state(self): + """Get / set key's state. + + One of: + - ``ACTIVE`` + - ``INACTIVE`` + - ``DELETED`` + + :rtype: str or None + :returns: key's current state. + """ + return self._properties.get("state") + + @state.setter + def state(self, value): + if value not in self._SETTABLE_STATES: + raise ValueError( + "State may only be set to one of: {}".format( + ", ".join(self._SETTABLE_STATES) + ) + ) + + self._properties["state"] = value + + @property + def time_created(self): + """Retrieve the timestamp at which the bucket was created. + + :rtype: :class:`datetime.datetime` or ``NoneType`` + :returns: Datetime object parsed from RFC3339 valid timestamp, or + ``None`` if the bucket's resource has not been loaded + from the server. + """ + value = self._properties.get("timeCreated") + if value is not None: + return _rfc3339_to_datetime(value) + + @property + def updated(self): + """Retrieve the timestamp at which the bucket was created. + + :rtype: :class:`datetime.datetime` or ``NoneType`` + :returns: Datetime object parsed from RFC3339 valid timestamp, or + ``None`` if the bucket's resource has not been loaded + from the server. + """ + value = self._properties.get("updated") + if value is not None: + return _rfc3339_to_datetime(value) diff --git a/storage/tests/unit/test_hmac_key.py b/storage/tests/unit/test_hmac_key.py new file mode 100644 index 000000000000..f2bfccb4f9f7 --- /dev/null +++ b/storage/tests/unit/test_hmac_key.py @@ -0,0 +1,118 @@ +# Copyright 2019 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. + +import unittest + +import mock + + +class TestHMACKeyMetadata(unittest.TestCase): + @staticmethod + def _get_target_class(): + from google.cloud.storage.hmac_key import HMACKeyMetadata + + return HMACKeyMetadata + + def _make_one(self, client=None, *args, **kw): + if client is None: + client = object() + return self._get_target_class()(client, *args, **kw) + + def test_ctor_defaults(self): + client = object() + metadata = self._make_one(client) + self.assertIs(metadata._client, client) + self.assertEqual(metadata._properties, {}) + self.assertIsNone(metadata.access_id) + self.assertIsNone(metadata.etag) + self.assertIsNone(metadata.project) + self.assertIsNone(metadata.service_account_email) + self.assertIsNone(metadata.state) + self.assertIsNone(metadata.time_created) + self.assertIsNone(metadata.updated) + + def test_access_id_getter(self): + metadata = self._make_one() + expected = "ACCESS-ID" + metadata._properties["accessId"] = expected + self.assertEqual(metadata.access_id, expected) + + def test_etag_getter(self): + metadata = self._make_one() + expected = "ETAG" + metadata._properties["etag"] = expected + self.assertEqual(metadata.etag, expected) + + def test_project_getter(self): + metadata = self._make_one() + expected = "PROJECT-ID" + metadata._properties["projectId"] = expected + self.assertEqual(metadata.project, expected) + + def test_service_account_email_getter(self): + metadata = self._make_one() + expected = "service_account@example.com" + metadata._properties["serviceAccountEmail"] = expected + self.assertEqual(metadata.service_account_email, expected) + + def test_state_getter(self): + metadata = self._make_one() + expected = "STATE" + metadata._properties["state"] = expected + self.assertEqual(metadata.state, expected) + + def test_state_setter_invalid_state(self): + metadata = self._make_one() + expected = "INVALID" + + with self.assertRaises(ValueError): + metadata.state = expected + + self.assertIsNone(metadata.state) + + def test_state_setter_inactive(self): + metadata = self._make_one() + metadata._properties["state"] = "ACTIVE" + expected = "INACTIVE" + metadata.state = expected + self.assertEqual(metadata.state, expected) + self.assertEqual(metadata._properties["state"], expected) + + def test_state_setter_active(self): + metadata = self._make_one() + metadata._properties["state"] = "INACTIVE" + expected = "ACTIVE" + metadata.state = expected + self.assertEqual(metadata.state, expected) + self.assertEqual(metadata._properties["state"], expected) + + def test_time_created_getter(self): + import datetime + from pytz import UTC + + metadata = self._make_one() + now = datetime.datetime.utcnow() + now_stamp = "{}Z".format(now.isoformat()) + metadata._properties["timeCreated"] = now_stamp + self.assertEqual(metadata.time_created, now.replace(tzinfo=UTC)) + + def test_updated_getter(self): + import datetime + from pytz import UTC + + metadata = self._make_one() + now = datetime.datetime.utcnow() + now_stamp = "{}Z".format(now.isoformat()) + metadata._properties["updated"] = now_stamp + self.assertEqual(metadata.updated, now.replace(tzinfo=UTC)) From 5d0a8623febae611902b4eaa5c5742d9fd14e739 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 20 May 2019 15:26:24 -0400 Subject: [PATCH 2/2] Lint. --- storage/tests/unit/test_hmac_key.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/storage/tests/unit/test_hmac_key.py b/storage/tests/unit/test_hmac_key.py index f2bfccb4f9f7..b6925ee25f24 100644 --- a/storage/tests/unit/test_hmac_key.py +++ b/storage/tests/unit/test_hmac_key.py @@ -14,8 +14,6 @@ import unittest -import mock - class TestHMACKeyMetadata(unittest.TestCase): @staticmethod