Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#3 Change modules structure; tests refactoring #5

Merged
merged 2 commits into from
Mar 12, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ default) signatures that could be used to sign requests to the APIs.

On the **client** side you could

* sign your requests using ``signit.create_signature()``
* sign your requests using ``signit.signature.create()``

On the **server** side you could

* parse a signature retrieved from request header or query string using
``signit.parse_signature()``
* verify retrieved signature using ``signit.verify_signature()``
* generate access and secret keys for client using ``signit.generate_key()``
``signit.signature.parse()``
* verify retrieved signature using ``signit.signature.verify()``
* generate access and secret keys for client using ``signit.key.generate()``

--------------

Expand All @@ -33,7 +33,7 @@ Example of usage (Py3k):

def create_user(user: dict) -> bool:
msg = str(datetime.datetime.utcnow().timestamp())
auth = signit.create_signature(MY_ACCESS_KEY, MY_SECRET_KEY, msg)
auth = signit.signature.create(MY_ACCESS_KEY, MY_SECRET_KEY, msg)
headers = {
'Unix-Timestamp': msg,
'Authorization': auth,
Expand Down Expand Up @@ -65,9 +65,9 @@ this way:

async def post(request):
message = request.headers['Unix-Timestamp']
access_key, signature = signit.parse_signature(request.headers['Authorization'])
access_key, signature = signit.signature.parse(request.headers['Authorization'])
secret_key = await get_secret_key_from_db(access_key)
if not signit.verify_signature(access_key, secret_key, message, signature):
if not signit.signature.verify(access_key, secret_key, message, signature):
raise web.HTTPUnauthorized('Invalid signature')
try:
await create_user(request)
Expand Down
6 changes: 2 additions & 4 deletions signit/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
from .signature import generate_key
from .signature import create_signature
from .signature import parse_signature
from .signature import verify_signature
from . import key
from . import signature

__version__ = '0.1.3'
5 changes: 3 additions & 2 deletions signit/_constants.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import string

UTF8 = 'utf-8'
KEY_CHARS = string.ascii_letters + string.digits
AUTH_PREFIX_HEADER = 'HMAC-SHA256'
KEY_CHARS = string.ascii_letters + string.digits
KEY_LENGTH = 32
SIGNATURE_FORMAT = '{prefix} {access_key}:{signature}'
UTF8 = 'utf-8'
10 changes: 0 additions & 10 deletions signit/_random.py

This file was deleted.

8 changes: 8 additions & 0 deletions signit/key.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from random import SystemRandom
from ._constants import KEY_CHARS
from ._constants import KEY_LENGTH


def generate(key_length=KEY_LENGTH, key_chars=KEY_CHARS):
rand = SystemRandom()
return ''.join((rand.choice(key_chars) for x in range(key_length)))
34 changes: 13 additions & 21 deletions signit/signature.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,12 @@
import base64
import hmac
from hashlib import sha256
from ._constants import AUTH_PREFIX_HEADER
from ._constants import KEY_CHARS
from ._constants import SIGNATURE_FORMAT
from ._constants import UTF8
from ._helpers import _bytes
from ._random import get_random_string


def generate_key(key_length=32, key_chars=KEY_CHARS):
return get_random_string(key_length, key_chars)


def parse_signature(signature, auth_header_prefix=None):
prefix, _signature = signature.split(' ', 1)
if auth_header_prefix and prefix != auth_header_prefix:
raise ValueError('Invalid prefix value in `Authorization` header.')
return _signature.split(':') # access_key, signature


def create_signature(access_key, secret_key, message, algorithm=sha256,
auth_header_prefix=AUTH_PREFIX_HEADER):
def create(access_key, secret_key, message, algorithm=sha256,
auth_header_prefix=AUTH_PREFIX_HEADER):
new_hmac = hmac.new(_bytes(secret_key), msg=_bytes(message),
digestmod=algorithm)
signature = SIGNATURE_FORMAT.format(prefix=auth_header_prefix,
Expand All @@ -30,10 +15,17 @@ def create_signature(access_key, secret_key, message, algorithm=sha256,
return signature.strip() # in case if `auth_header_prefix` is empty string


def verify_signature(access_key, secret_key, message, signature,
auth_header_prefix=AUTH_PREFIX_HEADER):
def parse(signature, auth_header_prefix=None):
prefix, _signature = signature.split(' ', 1)
if auth_header_prefix and prefix != auth_header_prefix:
raise ValueError('Invalid prefix value in `Authorization` header.')
return _signature.split(':') # access_key, signature


def verify(access_key, secret_key, message, signature,
auth_header_prefix=AUTH_PREFIX_HEADER):
return hmac.compare_digest(
signature,
create_signature(access_key, secret_key, message,
auth_header_prefix=auth_header_prefix)
create(access_key, secret_key, message,
auth_header_prefix=auth_header_prefix)
)
18 changes: 18 additions & 0 deletions tests/test_key.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import pytest
import signit


@pytest.fixture(scope='module')
def _key_settings():
return {
'key_length': 5,
'key_chars': 'abc123',
}


def test_generate_key(_key_settings):
key = signit.key.generate(**_key_settings)
assert (len(key) == _key_settings['key_length'],
'Key should have defined length')
assert (not set(key) - set(_key_settings['key_chars']),
'Key should contain only defined chars')
38 changes: 10 additions & 28 deletions tests/test_signature.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import pytest
from signit import create_signature
from signit import parse_signature
from signit import verify_signature
from signit import generate_key
import signit


@pytest.fixture(scope='module')
Expand Down Expand Up @@ -35,40 +32,25 @@ def _full_signature(_prefix, _access_key, _signature):
return '{} {}:{}'.format(_prefix, _access_key, _signature)


@pytest.fixture(scope='module')
def _key_settings():
return {
'key_length': 5,
'key_chars': 'abc123',
}


def test_generate_key(_key_settings):
key = generate_key(**_key_settings)
assert (len(key) == _key_settings['key_length'],
'Key should have defined length')
assert (not set(key) - set(_key_settings['key_chars']),
'Key should contain only defined chars')


def test_create_signature(_access_key, _secret_key, _message, _full_signature):
def test_create(_access_key, _secret_key, _message, _full_signature):
expected_result = _full_signature
actual_result = create_signature(_access_key, _secret_key, _message)
actual_result = signit.signature.create(_access_key, _secret_key, _message)
assert actual_result == expected_result, 'Should produce correct signature'


def test_parse_signature(_access_key, _signature, _full_signature):
access_key, signature = parse_signature(_full_signature)
def test_parse(_access_key, _signature, _full_signature):
access_key, signature = signit.signature.parse(_full_signature)
assert (
(access_key, signature) == (_access_key, _signature),
'Should parse access key and signature correctly'
)
with pytest.raises(ValueError) as e:
parse_signature(_full_signature, auth_header_prefix='WRONG_PREFIX')
signit.signature.parse(_full_signature,
auth_header_prefix='WRONG_PREFIX')
assert 'Invalid prefix value in `Authorization` header.' in str(e.value)


def test_verify_signature(_access_key, _secret_key, _message, _full_signature):
def test_verify(_access_key, _secret_key, _message, _full_signature):
valid = (_access_key, _secret_key, _message, _full_signature)
len_valid = len(valid)

Expand All @@ -80,7 +62,7 @@ def _invalid(wrong_index):

invalid = (_invalid(i) for i in range(len_valid - 1))

assert verify_signature(*valid)
assert signit.signature.verify(*valid)

for args in invalid:
assert not verify_signature(*args)
assert not signit.signature.verify(*args)