summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSerghei MIHAI <smihai@entrouvert.com>2014-10-06 08:48:46 (GMT)
committerSerghei MIHAI <smihai@entrouvert.com>2014-10-06 12:10:22 (GMT)
commit8b0721980d4f0c6b4c0e7d827ff6f68c8feaada9 (patch)
tree750daf25a1f818c91f1f7dffdc00eb1d3afc3e34
parent67092eb4f430e0c7f07499f21fb50802b9d9f862 (diff)
downloadauthentic2-userinfo-8b0721980d4f0c6b4c0e7d827ff6f68c8feaada9.zip
authentic2-userinfo-8b0721980d4f0c6b4c0e7d827ff6f68c8feaada9.tar.gz
authentic2-userinfo-8b0721980d4f0c6b4c0e7d827ff6f68c8feaada9.tar.bz2
query signing check
-rw-r--r--authentic2_userinfo/app_settings.py3
-rw-r--r--authentic2_userinfo/decorators.py19
-rw-r--r--authentic2_userinfo/signature.py42
-rw-r--r--authentic2_userinfo/views.py3
4 files changed, 66 insertions, 1 deletions
diff --git a/authentic2_userinfo/app_settings.py b/authentic2_userinfo/app_settings.py
index 434d87e..bd5306e 100644
--- a/authentic2_userinfo/app_settings.py
+++ b/authentic2_userinfo/app_settings.py
@@ -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
diff --git a/authentic2_userinfo/decorators.py b/authentic2_userinfo/decorators.py
new file mode 100644
index 0000000..e8b3b9e
--- /dev/null
+++ b/authentic2_userinfo/decorators.py
@@ -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
diff --git a/authentic2_userinfo/signature.py b/authentic2_userinfo/signature.py
new file mode 100644
index 0000000..f1f0f5d
--- /dev/null
+++ b/authentic2_userinfo/signature.py
@@ -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
diff --git a/authentic2_userinfo/views.py b/authentic2_userinfo/views.py
index 18768e0..c1ee391 100644
--- a/authentic2_userinfo/views.py
+++ b/authentic2_userinfo/views.py
@@ -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)