query signing check

This commit is contained in:
Serghei Mihai 2014-10-06 10:48:46 +02:00
parent 67092eb4f4
commit 8b0721980d
4 changed files with 66 additions and 1 deletions

View File

@ -1,6 +1,7 @@
class AppSettings(object):
__DEFAULTS = {
'ENABLED': True,
'API_KEY': '12345',
}
def __init__(self, prefix):
@ -18,6 +19,6 @@ class AppSettings(object):
# Ugly? Guido recommends this himself ...
# http://mail.python.org/pipermail/python-ideas/2012-May/014969.html
import sys
app_settings = AppSettings('A2_PLUGIN_TEMPLATE_')
app_settings = AppSettings('A2_PLUGIN_USERINFO_')
app_settings.__name__ = __name__
sys.modules[__name__] = app_settings

View File

@ -0,0 +1,19 @@
from urlparse import urlparse
from functools import wraps
from django.http import HttpResponseForbidden
from . import app_settings, signature
def valid_signature(func):
@wraps(func)
def wrapper(*args, **kwargs):
api_key = app_settings.API_KEY
request = args[1]
signed_query = urlparse(request.get_full_path()).query
try:
assert signature.check_query(signed_query, api_key) is True
return func(*args, **kwargs)
except (KeyError, AssertionError):
return HttpResponseForbidden()
return wrapper

View File

@ -0,0 +1,42 @@
import datetime
import base64
import hmac
import hashlib
import urllib
import random
import urlparse
def sign_string(s, key, algo='sha256', timedelta=30):
digestmod = getattr(hashlib, algo)
hash = hmac.HMAC(key, digestmod=digestmod, msg=s)
return hash.digest()
def check_query(query, key, known_nonce=None, timedelta=30):
parsed = urlparse.parse_qs(query)
signature = base64.b64decode(parsed['signature'][0])
algo = parsed['algo'][0]
timestamp = parsed['timestamp'][0]
timestamp = datetime.datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%SZ')
nonce = parsed['nonce']
unsigned_query = query.split('&signature=')[0]
if known_nonce is not None and known_nonce(nonce):
return False
if abs(datetime.datetime.utcnow() - timestamp) > datetime.timedelta(seconds=timedelta):
return False
return check_string(unsigned_query, signature, key, algo=algo)
def check_string(s, signature, key, algo='sha256'):
# constant time compare
signature2 = sign_string(s, key, algo=algo)
if len(signature2) != len(signature):
return False
res = 0
for a, b in zip(signature, signature2):
res |= ord(a) ^ ord(b)
return res == 0
if __name__ == '__main__':
test_key = '12345'
signed_query = sign_query('NameId=_12345&orig=montpellier', test_key)
assert check_query(signed_query, test_key, timedelta=0) is False
assert check_query(signed_query, test_key) is True

View File

@ -4,10 +4,13 @@ from jsonresponse import to_json
from authentic2 import compat
from .decorators import valid_signature
user_model = compat.get_user_model()
class UserFullData(View):
@valid_signature
@to_json('api')
def get(self, request, nameid):
user = user_model.objects.get(libertyfederation__name_id_content=nameid, deleteduser__isnull=True)