keystone  18.0.0
About: OpenStack Keystone (Core Service: Identity) provides an authentication and authorization service for other OpenStack services. Provides a catalog of endpoints for all OpenStack services.
The "Victoria" series (maintained release).
  Fossies Dox: keystone-18.0.0.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

receipt_formatters.py
Go to the documentation of this file.
1 # Copyright 2018 Catalyst Cloud Ltd
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License"); you may
4 # not use this file except in compliance with the License. You may obtain
5 # 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, WITHOUT
11 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 # License for the specific language governing permissions and limitations
13 # under the License.
14 
15 import base64
16 import datetime
17 import struct
18 import uuid
19 
20 from cryptography import fernet
21 import msgpack
22 from oslo_log import log
23 from oslo_utils import timeutils
24 
25 from keystone.auth import plugins as auth_plugins
26 from keystone.common import fernet_utils as utils
27 from keystone.common import utils as ks_utils
28 import keystone.conf
29 from keystone import exception
30 from keystone.i18n import _
31 
32 
33 CONF = keystone.conf.CONF
34 LOG = log.getLogger(__name__)
35 
36 # Fernet byte indexes as computed by pypi/keyless_fernet and defined in
37 # https://github.com/fernet/spec
38 TIMESTAMP_START = 1
39 TIMESTAMP_END = 9
40 
41 
42 class ReceiptFormatter(object):
43  """Packs and unpacks payloads into receipts for transport."""
44 
45  @property
46  def crypto(self):
47  """Return a cryptography instance.
48 
49  You can extend this class with a custom crypto @property to provide
50  your own receipt encoding / decoding. For example, using a different
51  cryptography library (e.g. ``python-keyczar``) or to meet arbitrary
52  security requirements.
53 
54  This @property just needs to return an object that implements
55  ``encrypt(plaintext)`` and ``decrypt(ciphertext)``.
56 
57  """
58  fernet_utils = utils.FernetUtils(
59  CONF.fernet_receipts.key_repository,
60  CONF.fernet_receipts.max_active_keys,
61  'fernet_receipts'
62  )
63  keys = fernet_utils.load_keys()
64 
65  if not keys:
67 
68  fernet_instances = [fernet.Fernet(key) for key in keys]
69  return fernet.MultiFernet(fernet_instances)
70 
71  def pack(self, payload):
72  """Pack a payload for transport as a receipt.
73 
74  :type payload: bytes
75  :rtype: str
76 
77  """
78  # base64 padding (if any) is not URL-safe
79  return self.crypto.encrypt(payload).rstrip(b'=').decode('utf-8')
80 
81  def unpack(self, receipt):
82  """Unpack a receipt, and validate the payload.
83 
84  :type receipt: str
85  :rtype: bytes
86 
87  """
88  receipt = ReceiptFormatter.restore_padding(receipt)
89 
90  try:
91  return self.crypto.decrypt(receipt.encode('utf-8'))
92  except fernet.InvalidToken:
94  _('This is not a recognized Fernet receipt %s') % receipt)
95 
96  @classmethod
97  def restore_padding(cls, receipt):
98  """Restore padding based on receipt size.
99 
100  :param receipt: receipt to restore padding on
101  :type receipt: str
102  :returns: receipt with correct padding
103 
104  """
105  # Re-inflate the padding
106  mod_returned = len(receipt) % 4
107  if mod_returned:
108  missing_padding = 4 - mod_returned
109  receipt += '=' * missing_padding
110  return receipt
111 
112  @classmethod
113  def creation_time(cls, fernet_receipt):
114  """Return the creation time of a valid Fernet receipt.
115 
116  :type fernet_receipt: str
117 
118  """
119  fernet_receipt = ReceiptFormatter.restore_padding(fernet_receipt)
120  # fernet_receipt is str
121 
122  # Fernet receipts are base64 encoded, so we need to unpack them first
123  # urlsafe_b64decode() requires bytes
124  receipt_bytes = base64.urlsafe_b64decode(
125  fernet_receipt.encode('utf-8'))
126 
127  # slice into the byte array to get just the timestamp
128  timestamp_bytes = receipt_bytes[TIMESTAMP_START:TIMESTAMP_END]
129 
130  # convert those bytes to an integer
131  # (it's a 64-bit "unsigned long long int" in C)
132  timestamp_int = struct.unpack(">Q", timestamp_bytes)[0]
133 
134  # and with an integer, it's trivial to produce a datetime object
135  issued_at = datetime.datetime.utcfromtimestamp(timestamp_int)
136 
137  return issued_at
138 
139  def create_receipt(self, user_id, methods, expires_at):
140  """Given a set of payload attributes, generate a Fernet receipt."""
141  payload = ReceiptPayload.assemble(user_id, methods, expires_at)
142 
143  serialized_payload = msgpack.packb(payload)
144  receipt = self.pack(serialized_payload)
145 
146  # NOTE(lbragstad): We should warn against Fernet receipts that are over
147  # 255 characters in length. This is mostly due to persisting the
148  # receipts in a backend store of some kind that might have a limit of
149  # 255 characters. Even though Keystone isn't storing a Fernet receipt
150  # anywhere, we can't say it isn't being stored somewhere else with
151  # those kind of backend constraints.
152  if len(receipt) > 255:
153  LOG.info('Fernet receipt created with length of %d '
154  'characters, which exceeds 255 characters',
155  len(receipt))
156 
157  return receipt
158 
159  def validate_receipt(self, receipt):
160  """Validate a Fernet receipt and returns the payload attributes.
161 
162  :type receipt: str
163 
164  """
165  serialized_payload = self.unpack(receipt)
166  payload = msgpack.unpackb(serialized_payload)
167 
168  (user_id, methods, expires_at) = ReceiptPayload.disassemble(payload)
169 
170  # rather than appearing in the payload, the creation time is encoded
171  # into the receipt format itself
172  issued_at = ReceiptFormatter.creation_time(receipt)
173  issued_at = ks_utils.isotime(at=issued_at, subsecond=True)
174  expires_at = timeutils.parse_isotime(expires_at)
175  expires_at = ks_utils.isotime(at=expires_at, subsecond=True)
176 
177  return (user_id, methods, issued_at, expires_at)
178 
179 
180 class ReceiptPayload(object):
181 
182  @classmethod
183  def assemble(cls, user_id, methods, expires_at):
184  """Assemble the payload of a receipt.
185 
186  :param user_id: identifier of the user in the receipt request
187  :param methods: list of authentication methods used
188  :param expires_at: datetime of the receipt's expiration
189  :returns: the payload of a receipt
190 
191  """
192  b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id)
193  methods = auth_plugins.convert_method_list_to_integer(methods)
194  expires_at_int = cls._convert_time_string_to_float(expires_at)
195  return (b_user_id, methods, expires_at_int)
196 
197  @classmethod
198  def disassemble(cls, payload):
199  """Disassemble a payload into the component data.
200 
201  The tuple consists of::
202 
203  (user_id, methods, expires_at_str)
204 
205  * ``methods`` are the auth methods.
206 
207  :param payload: this variant of payload
208  :returns: a tuple of the payloads component data
209 
210  """
211  (is_stored_as_bytes, user_id) = payload[0]
212  if is_stored_as_bytes:
213  user_id = cls.convert_uuid_bytes_to_hex(user_id)
214  methods = auth_plugins.convert_integer_to_method_list(payload[1])
215  expires_at_str = cls._convert_float_to_time_string(payload[2])
216  return (user_id, methods, expires_at_str)
217 
218  @classmethod
219  def convert_uuid_hex_to_bytes(cls, uuid_string):
220  """Compress UUID formatted strings to bytes.
221 
222  :param uuid_string: uuid string to compress to bytes
223  :returns: a byte representation of the uuid
224 
225  """
226  uuid_obj = uuid.UUID(uuid_string)
227  return uuid_obj.bytes
228 
229  @classmethod
230  def convert_uuid_bytes_to_hex(cls, uuid_byte_string):
231  """Generate uuid.hex format based on byte string.
232 
233  :param uuid_byte_string: uuid string to generate from
234  :returns: uuid hex formatted string
235 
236  """
237  uuid_obj = uuid.UUID(bytes=uuid_byte_string)
238  return uuid_obj.hex
239 
240  @classmethod
241  def _convert_time_string_to_float(cls, time_string):
242  """Convert a time formatted string to a float.
243 
244  :param time_string: time formatted string
245  :returns: a timestamp as a float
246 
247  """
248  time_object = timeutils.parse_isotime(time_string)
249  return (timeutils.normalize_time(time_object) -
250  datetime.datetime.utcfromtimestamp(0)).total_seconds()
251 
252  @classmethod
253  def _convert_float_to_time_string(cls, time_float):
254  """Convert a floating point timestamp to a string.
255 
256  :param time_float: integer representing timestamp
257  :returns: a time formatted strings
258 
259  """
260  time_object = datetime.datetime.utcfromtimestamp(time_float)
261  return ks_utils.isotime(time_object, subsecond=True)
262 
263  @classmethod
265  """Attempt to convert value to bytes or return value.
266 
267  :param value: value to attempt to convert to bytes
268  :returns: tuple containing boolean indicating whether user_id was
269  stored as bytes and uuid value as bytes or the original value
270 
271  """
272  try:
273  return (True, cls.convert_uuid_hex_to_bytes(value))
274  except ValueError:
275  # this might not be a UUID, depending on the situation (i.e.
276  # federation)
277  return (False, value)
278 
279  @classmethod
280  def base64_encode(cls, s):
281  """Encode a URL-safe string.
282 
283  :type s: str
284  :rtype: str
285 
286  """
287  # urlsafe_b64encode() returns bytes so need to convert to
288  # str, might as well do it before stripping.
289  return base64.urlsafe_b64encode(s).decode('utf-8').rstrip('=')
290 
291  @classmethod
293  """Convert string from :func:`random_urlsafe_str()` to bytes.
294 
295  :type s: str
296  :rtype: bytes
297 
298  """
299  # urlsafe_b64decode() requires str, unicode isn't accepted.
300  s = str(s)
301 
302  # restore the padding (==) at the end of the string
303  return base64.urlsafe_b64decode(s + '==')
keystone.receipt.receipt_formatters.ReceiptFormatter.pack
def pack(self, payload)
Definition: receipt_formatters.py:71
keystone.receipt.receipt_formatters.ReceiptPayload.attempt_convert_uuid_hex_to_bytes
def attempt_convert_uuid_hex_to_bytes(cls, value)
Definition: receipt_formatters.py:264
keystone.receipt.receipt_formatters.ReceiptFormatter.crypto
def crypto(self)
Definition: receipt_formatters.py:46
keystone.auth
Definition: __init__.py:1
keystone.receipt.receipt_formatters.ReceiptFormatter.creation_time
def creation_time(cls, fernet_receipt)
Definition: receipt_formatters.py:113
keystone.exception.ValidationError
Definition: exception.py:98
keystone.receipt.receipt_formatters.ReceiptPayload.convert_uuid_hex_to_bytes
def convert_uuid_hex_to_bytes(cls, uuid_string)
Definition: receipt_formatters.py:219
keystone.receipt.receipt_formatters.ReceiptPayload.base64_encode
def base64_encode(cls, s)
Definition: receipt_formatters.py:280
keystone.receipt.receipt_formatters.ReceiptPayload._convert_float_to_time_string
def _convert_float_to_time_string(cls, time_float)
Definition: receipt_formatters.py:253
keystone.receipt.receipt_formatters.ReceiptPayload.convert_uuid_bytes_to_hex
def convert_uuid_bytes_to_hex(cls, uuid_byte_string)
Definition: receipt_formatters.py:230
keystone.receipt.receipt_formatters.ReceiptFormatter.unpack
def unpack(self, receipt)
Definition: receipt_formatters.py:81
keystone.receipt.receipt_formatters.ReceiptFormatter.create_receipt
def create_receipt(self, user_id, methods, expires_at)
Definition: receipt_formatters.py:139
keystone.exception.KeysNotFound
Definition: exception.py:640
keystone.receipt.receipt_formatters.ReceiptPayload.assemble
def assemble(cls, user_id, methods, expires_at)
Definition: receipt_formatters.py:183
keystone.receipt.receipt_formatters.ReceiptFormatter
Definition: receipt_formatters.py:42
keystone.receipt.receipt_formatters.ReceiptPayload.random_urlsafe_str_to_bytes
def random_urlsafe_str_to_bytes(cls, s)
Definition: receipt_formatters.py:292
keystone.receipt.receipt_formatters.ReceiptPayload._convert_time_string_to_float
def _convert_time_string_to_float(cls, time_string)
Definition: receipt_formatters.py:241
keystone.receipt.receipt_formatters.ReceiptFormatter.validate_receipt
def validate_receipt(self, receipt)
Definition: receipt_formatters.py:159
keystone.conf
Definition: __init__.py:1
keystone.i18n._
_
Definition: i18n.py:29
keystone.receipt.receipt_formatters.ReceiptPayload
Definition: receipt_formatters.py:180
keystone.common
Definition: __init__.py:1
keystone.i18n
Definition: i18n.py:1
keystone.receipt.receipt_formatters.ReceiptPayload.disassemble
def disassemble(cls, payload)
Definition: receipt_formatters.py:198
keystone.receipt.receipt_formatters.ReceiptFormatter.restore_padding
def restore_padding(cls, receipt)
Definition: receipt_formatters.py:97