diff --git a/docs/Makefile b/docs/Makefile index 95ddd23b7c..c1e0125cf3 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -3,7 +3,7 @@ # You can set these variables from the command line. SPHINXOPTS = -SPHINXBUILD = ../env/bin/swaddle ../tests/env ../env/bin/sphinx-build +SPHINXBUILD = ../env/bin/honcho -e ../tests/env run ../env/bin/sphinx-build BUILDDIR = _build # User-friendly check for sphinx-build diff --git a/gratipay/models/__init__.py b/gratipay/models/__init__.py index e21edbe2d4..85fb8723fb 100644 --- a/gratipay/models/__init__.py +++ b/gratipay/models/__init__.py @@ -193,6 +193,18 @@ def _check_orphans_no_tips(cursor): def add_event(c, type, payload): + """Log an event. + + This is the function we use to capture interesting events that happen + across the system in one place, the ``events`` table. + + :param c: a :py:class:`Postres` or :py:class:`Cursor` instance + :param unicode type: an indicator of what type of event it is--either ``participant``, ``team`` + or ``payday`` + :param payload: an arbitrary JSON-serializable data structure; for ``participant`` type, ``id`` + must be the id of the participant in question + + """ SQL = """ INSERT INTO events (type, payload) VALUES (%s, %s) diff --git a/gratipay/models/participant.py b/gratipay/models/participant/__init__.py similarity index 100% rename from gratipay/models/participant.py rename to gratipay/models/participant/__init__.py diff --git a/gratipay/security/crypto.py b/gratipay/security/crypto.py index 85cb218424..e4adf0fe78 100644 --- a/gratipay/security/crypto.py +++ b/gratipay/security/crypto.py @@ -1,102 +1,28 @@ -""" -Django's standard crypto functions and utilities. -""" -from __future__ import unicode_literals +from __future__ import absolute_import, division, print_function, unicode_literals -import hmac -import struct import hashlib -import binascii -import operator +import random +import string import time -from functools import reduce -# Use the system PRNG if possible -import random -try: + +# utils +# ===== +# borrowed from Django + +try: # use the system PRNG if possible random = random.SystemRandom() using_sysrandom = True -except NotImplementedError: +except NotImplementedError: # fall back import warnings warnings.warn('A secure pseudo-random number generator is not available ' 'on your system. Falling back to Mersenne Twister.') using_sysrandom = False -#from django.conf import settings -SECRET_KEY = "" -import string pool = string.digits + string.letters + string.punctuation UNSECURE_RANDOM_STRING = b"".join([random.choice(pool) for i in range(64)]) -# I get wet. - -#from django.utils.functional import Promise -class Promise(object): - """ - This is just a base class for the proxy class created in - the closure of the lazy function. It can be used to recognize - promises in code. - """ - pass - -#from django.utils.encoding import smart_str -def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'): - """ - Returns a bytestring version of 's', encoded as specified in 'encoding'. - - If strings_only is True, don't convert (some) non-string-like objects. - """ - if strings_only and (s is None or isinstance(s, int)): - return s - if isinstance(s, Promise): - return unicode(s).encode(encoding, errors) - elif not isinstance(s, basestring): - try: - return str(s) - except UnicodeEncodeError: - if isinstance(s, Exception): - # An Exception subclass containing non-ASCII data that doesn't - # know how to print itself properly. We shouldn't raise a - # further exception. - return ' '.join([smart_str(arg, encoding, strings_only, - errors) for arg in s]) - return unicode(s).encode(encoding, errors) - elif isinstance(s, unicode): - return s.encode(encoding, errors) - elif s and encoding != 'utf-8': - return s.decode('utf-8', errors).encode(encoding, errors) - else: - return s - - -_trans_5c = b"".join([chr(x ^ 0x5C) for x in xrange(256)]) -_trans_36 = b"".join([chr(x ^ 0x36) for x in xrange(256)]) - - -def salted_hmac(key_salt, value, secret=None): - """ - Returns the HMAC-SHA1 of 'value', using a key generated from key_salt and a - secret (which defaults to settings.SECRET_KEY). - - A different key_salt should be passed in for every application of HMAC. - """ - if secret is None: - raise NotImplementedError - #secret = settings.SECRET_KEY - - # We need to generate a derived key from our base key. We can do this by - # passing the key_salt and our base key through a pseudo-random function and - # SHA1 works nicely. - key = hashlib.sha1((key_salt + secret).encode('utf-8')).digest() - - # If len(key_salt + secret) > sha_constructor().block_size, the above - # line is redundant and could be replaced by key = key_salt + secret, since - # the hmac module does the same thing for keys longer than the block size. - # However, we need to ensure that we *always* do this. - return hmac.new(key, msg=value, digestmod=hashlib.sha1) - - def get_random_string(length=12, allowed_chars='abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'): @@ -135,75 +61,3 @@ def constant_time_compare(val1, val2): for x, y in zip(val1, val2): result |= ord(x) ^ ord(y) return result == 0 - - -def _bin_to_long(x): - """ - Convert a binary string into a long integer - - This is a clever optimization for fast xor vector math - """ - return long(x.encode('hex'), 16) - - -def _long_to_bin(x, hex_format_string): - """ - Convert a long integer into a binary string. - hex_format_string is like "%020x" for padding 10 characters. - """ - return binascii.unhexlify((hex_format_string % x).encode('ascii')) - - -def _fast_hmac(key, msg, digest): - """ - A trimmed down version of Python's HMAC implementation - """ - dig1, dig2 = digest(), digest() - key = smart_str(key) - if len(key) > dig1.block_size: - key = digest(key).digest() - key += chr(0) * (dig1.block_size - len(key)) - dig1.update(key.translate(_trans_36)) - dig1.update(msg) - dig2.update(key.translate(_trans_5c)) - dig2.update(dig1.digest()) - return dig2 - - -def pbkdf2(password, salt, iterations, dklen=0, digest=None): - """ - Implements PBKDF2 as defined in RFC 2898, section 5.2 - - HMAC+SHA256 is used as the default pseudo random function. - - Right now 10,000 iterations is the recommended default which takes - 100ms on a 2.2Ghz Core 2 Duo. This is probably the bare minimum - for security given 1000 iterations was recommended in 2001. This - code is very well optimized for CPython and is only four times - slower than openssl's implementation. - """ - assert iterations > 0 - if not digest: - digest = hashlib.sha256 - password = smart_str(password) - salt = smart_str(salt) - hlen = digest().digest_size - if not dklen: - dklen = hlen - if dklen > (2 ** 32 - 1) * hlen: - raise OverflowError('dklen too big') - l = -(-dklen // hlen) - r = dklen - (l - 1) * hlen - - hex_format_string = "%%0%ix" % (hlen * 2) - - def F(i): - def U(): - u = salt + struct.pack(b'>I', i) - for j in xrange(int(iterations)): - u = _fast_hmac(password, u, digest).digest() - yield _bin_to_long(u) - return _long_to_bin(reduce(operator.xor, U()), hex_format_string) - - T = [F(x) for x in range(1, l + 1)] - return b''.join(T[:-1]) + T[-1][:r]