1# Copyright 2019 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# http://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"""Configure HMAC keys that can be used to authenticate requests to Google Cloud Storage.
16
17See [HMAC keys documentation](https://cloud.google.com/storage/docs/authentication/hmackeys)
18"""
19
20from google.cloud.exceptions import NotFound
21from google.cloud._helpers import _rfc3339_nanos_to_datetime
22
23from google.cloud.storage._opentelemetry_tracing import create_trace_span
24from google.cloud.storage.constants import _DEFAULT_TIMEOUT
25from google.cloud.storage.retry import DEFAULT_RETRY
26from google.cloud.storage.retry import DEFAULT_RETRY_IF_ETAG_IN_JSON
27
28
29class HMACKeyMetadata(object):
30 """Metadata about an HMAC service account key withn Cloud Storage.
31
32 :type client: :class:`~google.cloud.stoage.client.Client`
33 :param client: client associated with the key metadata.
34
35 :type access_id: str
36 :param access_id: (Optional) Unique ID of an existing key.
37
38 :type project_id: str
39 :param project_id: (Optional) Project ID of an existing key.
40 Defaults to client's project.
41
42 :type user_project: str
43 :param user_project: (Optional) This parameter is currently ignored.
44 """
45
46 ACTIVE_STATE = "ACTIVE"
47 """Key is active, and may be used to sign requests."""
48 INACTIVE_STATE = "INACTIVE"
49 """Key is inactive, and may not be used to sign requests.
50
51 It can be re-activated via :meth:`update`.
52 """
53 DELETED_STATE = "DELETED"
54 """Key is deleted. It cannot be re-activated."""
55
56 _SETTABLE_STATES = (ACTIVE_STATE, INACTIVE_STATE)
57
58 def __init__(self, client, access_id=None, project_id=None, user_project=None):
59 self._client = client
60 self._properties = {}
61
62 if access_id is not None:
63 self._properties["accessId"] = access_id
64
65 if project_id is not None:
66 self._properties["projectId"] = project_id
67
68 self._user_project = user_project
69
70 def __eq__(self, other):
71 if not isinstance(other, self.__class__):
72 return NotImplemented
73
74 return self._client == other._client and self.access_id == other.access_id
75
76 def __hash__(self):
77 return hash(self._client) + hash(self.access_id)
78
79 @property
80 def access_id(self):
81 """Access ID of the key.
82
83 :rtype: str or None
84 :returns: unique identifier of the key within a project.
85 """
86 return self._properties.get("accessId")
87
88 @property
89 def etag(self):
90 """ETag identifying the version of the key metadata.
91
92 :rtype: str or None
93 :returns: ETag for the version of the key's metadata.
94 """
95 return self._properties.get("etag")
96
97 @property
98 def id(self):
99 """ID of the key, including the Project ID and the Access ID.
100
101 :rtype: str or None
102 :returns: ID of the key.
103 """
104 return self._properties.get("id")
105
106 @property
107 def project(self):
108 """Project ID associated with the key.
109
110 :rtype: str or None
111 :returns: project identfier for the key.
112 """
113 return self._properties.get("projectId")
114
115 @property
116 def service_account_email(self):
117 """Service account e-mail address associated with the key.
118
119 :rtype: str or None
120 :returns: e-mail address for the service account which created the key.
121 """
122 return self._properties.get("serviceAccountEmail")
123
124 @property
125 def state(self):
126 """Get / set key's state.
127
128 One of:
129 - ``ACTIVE``
130 - ``INACTIVE``
131 - ``DELETED``
132
133 :rtype: str or None
134 :returns: key's current state.
135 """
136 return self._properties.get("state")
137
138 @state.setter
139 def state(self, value):
140 self._properties["state"] = value
141
142 @property
143 def time_created(self):
144 """Retrieve the timestamp at which the HMAC key was created.
145
146 :rtype: :class:`datetime.datetime` or ``NoneType``
147 :returns: Datetime object parsed from RFC3339 valid timestamp, or
148 ``None`` if the bucket's resource has not been loaded
149 from the server.
150 """
151 value = self._properties.get("timeCreated")
152 if value is not None:
153 return _rfc3339_nanos_to_datetime(value)
154
155 @property
156 def updated(self):
157 """Retrieve the timestamp at which the HMAC key was created.
158
159 :rtype: :class:`datetime.datetime` or ``NoneType``
160 :returns: Datetime object parsed from RFC3339 valid timestamp, or
161 ``None`` if the bucket's resource has not been loaded
162 from the server.
163 """
164 value = self._properties.get("updated")
165 if value is not None:
166 return _rfc3339_nanos_to_datetime(value)
167
168 @property
169 def path(self):
170 """Resource path for the metadata's key."""
171
172 if self.access_id is None:
173 raise ValueError("No 'access_id' set.")
174
175 project = self.project
176 if project is None:
177 project = self._client.project
178
179 return f"/projects/{project}/hmacKeys/{self.access_id}"
180
181 @property
182 def user_project(self):
183 """Project ID to be billed for API requests made via this bucket.
184
185 This property is currently ignored by the server.
186
187 :rtype: str
188 """
189 return self._user_project
190
191 def exists(self, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY):
192 """Determine whether or not the key for this metadata exists.
193
194 :type timeout: float or tuple
195 :param timeout:
196 (Optional) The amount of time, in seconds, to wait
197 for the server response. See: :ref:`configuring_timeouts`
198
199 :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
200 :param retry:
201 (Optional) How to retry the RPC. See: :ref:`configuring_retries`
202
203 :rtype: bool
204 :returns: True if the key exists in Cloud Storage.
205 """
206 with create_trace_span(name="Storage.HmacKey.exists"):
207 try:
208 qs_params = {}
209
210 if self.user_project is not None:
211 qs_params["userProject"] = self.user_project
212
213 self._client._get_resource(
214 self.path,
215 query_params=qs_params,
216 timeout=timeout,
217 retry=retry,
218 )
219 except NotFound:
220 return False
221 else:
222 return True
223
224 def reload(self, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY):
225 """Reload properties from Cloud Storage.
226
227 :type timeout: float or tuple
228 :param timeout:
229 (Optional) The amount of time, in seconds, to wait
230 for the server response. See: :ref:`configuring_timeouts`
231
232 :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
233 :param retry:
234 (Optional) How to retry the RPC. See: :ref:`configuring_retries`
235
236 :raises :class:`~google.api_core.exceptions.NotFound`:
237 if the key does not exist on the back-end.
238 """
239 with create_trace_span(name="Storage.HmacKey.reload"):
240 qs_params = {}
241
242 if self.user_project is not None:
243 qs_params["userProject"] = self.user_project
244
245 self._properties = self._client._get_resource(
246 self.path,
247 query_params=qs_params,
248 timeout=timeout,
249 retry=retry,
250 )
251
252 def update(self, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY_IF_ETAG_IN_JSON):
253 """Save writable properties to Cloud Storage.
254
255 :type timeout: float or tuple
256 :param timeout:
257 (Optional) The amount of time, in seconds, to wait
258 for the server response. See: :ref:`configuring_timeouts`
259
260 :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
261 :param retry:
262 (Optional) How to retry the RPC. See: :ref:`configuring_retries`
263
264 :raises :class:`~google.api_core.exceptions.NotFound`:
265 if the key does not exist on the back-end.
266 """
267 with create_trace_span(name="Storage.HmacKey.update"):
268 qs_params = {}
269 if self.user_project is not None:
270 qs_params["userProject"] = self.user_project
271
272 payload = {"state": self.state}
273 self._properties = self._client._put_resource(
274 self.path,
275 payload,
276 query_params=qs_params,
277 timeout=timeout,
278 retry=retry,
279 )
280
281 def delete(self, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY):
282 """Delete the key from Cloud Storage.
283
284 :type timeout: float or tuple
285 :param timeout:
286 (Optional) The amount of time, in seconds, to wait
287 for the server response. See: :ref:`configuring_timeouts`
288
289 :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
290 :param retry:
291 (Optional) How to retry the RPC. See: :ref:`configuring_retries`
292
293 :raises :class:`~google.api_core.exceptions.NotFound`:
294 if the key does not exist on the back-end.
295 """
296 with create_trace_span(name="Storage.HmacKey.delete"):
297 qs_params = {}
298 if self.user_project is not None:
299 qs_params["userProject"] = self.user_project
300
301 self._client._delete_resource(
302 self.path,
303 query_params=qs_params,
304 timeout=timeout,
305 retry=retry,
306 )