deb-zaqar/zaqar/common/urls.py

115 lines
4.1 KiB
Python

# Copyright (c) 2015 Red Hat, Inc.
#
# 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 datetime
import hashlib
import hmac
from oslo_utils import timeutils
import six
from zaqar.i18n import _LE
_DATE_FORMAT = '%Y-%m-%dT%H:%M:%S'
def create_signed_url(key, paths, project=None, expires=None, methods=None):
"""Creates a signed url for the specified path
This function will create a pre-signed URL for `path` using the
specified `options` or the default ones. The signature will be the
hex value of the hmac created using `key`
:param key: A string to use as a `key` for the hmac generation.
:param paths: A list of strings representing URL paths.
:param project: (Default None) The ID of the project this URL belongs to.
:param methods: (Default ['GET']) A list of methods that will be
supported by the generated URL.
:params expires: (Default time() + 86400) The expiration date for
the generated URL.
"""
methods = methods or ['GET']
if key is None:
raise ValueError(_LE('The `key` can\'t be None'))
if not isinstance(paths, list) or not paths:
raise ValueError(_LE('`paths` must be a non-empty list'))
if not isinstance(methods, list):
raise ValueError(_LE('`methods` should be a list'))
# NOTE(flaper87): The default expiration time is 1day
# Evaluate whether this should be configurable. We may
# also want to have a "maximum" expiration time. Food
# for thoughts.
if expires is not None:
# NOTE(flaper87): Verify if the format is correct
# and normalize the value to UTC.
check_expires = None
try:
check_expires = int(expires)
except ValueError:
pass
if check_expires:
raise ValueError(_LE('`expires` should be date format, '
'for example 2016-01-01T00:00:00, '
'not integer value: %s') % check_expires)
parsed = timeutils.parse_isotime(expires)
expires = timeutils.normalize_time(parsed)
else:
delta = datetime.timedelta(days=1)
expires = timeutils.utcnow() + delta
if expires <= timeutils.utcnow():
raise ValueError(_LE('`expires` is lower than the current time'))
methods = sorted(methods)
paths = sorted(paths)
expires_str = expires.strftime(_DATE_FORMAT)
hmac_body = six.b(r'%(paths)s\n%(methods)s\n%(project)s\n%(expires)s' %
{'paths': ','.join(paths), 'methods': ','.join(methods),
'project': project, 'expires': expires_str})
if not isinstance(key, six.binary_type):
key = six.binary_type(key.encode('utf-8'))
return {'paths': paths,
'methods': methods,
'project': project,
'expires': expires_str,
'signature': hmac.new(key, hmac_body, hashlib.sha256).hexdigest()}
def verify_signed_headers_data(key, paths, project,
signature, methods, expires):
"""Verify that `signature` matches for the given values
:param key: A string to use as a `key` for the hmac generation.
:param paths: A list of strings representing URL paths.
:param project: The ID of the project this URL belongs to.
:param signature: The pre-generated signature
:param methods: A list of methods that will be
supported by the generated URL.
:params expires: The expiration date for
the generated URL.
"""
generated = create_signed_url(key, paths, project=project,
methods=methods, expires=expires)
return signature == generated['signature']