diff options
Diffstat (limited to 'mediagoblin/tools')
-rw-r--r-- | mediagoblin/tools/crypto.py | 110 | ||||
-rw-r--r-- | mediagoblin/tools/request.py | 2 | ||||
-rw-r--r-- | mediagoblin/tools/session.py | 67 |
3 files changed, 178 insertions, 1 deletions
diff --git a/mediagoblin/tools/crypto.py b/mediagoblin/tools/crypto.py new file mode 100644 index 00000000..55811aea --- /dev/null +++ b/mediagoblin/tools/crypto.py @@ -0,0 +1,110 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import errno +import itsdangerous +import logging +import os.path +import random +import tempfile +from mediagoblin import mg_globals + +_log = logging.getLogger(__name__) + + +# Use the system (hardware-based) random number generator if it exists. +# -- this optimization is lifted from Django +try: + getrandbits = random.SystemRandom().getrandbits +except AttributeError: + getrandbits = random.getrandbits + + +__itsda_secret = None + + +def load_key(filename): + global __itsda_secret + key_file = open(filename) + try: + __itsda_secret = key_file.read() + finally: + key_file.close() + +def create_key(key_dir, key_filepath): + global __itsda_secret + old_umask = os.umask(077) + key_file = None + try: + if not os.path.isdir(key_dir): + os.makedirs(key_dir) + _log.info("Created %s", dirname) + key = str(getrandbits(192)) + key_file = tempfile.NamedTemporaryFile(dir=key_dir, suffix='.bin', + delete=False) + key_file.write(key) + key_file.flush() + os.rename(key_file.name, key_filepath) + key_file.close() + finally: + os.umask(old_umask) + if (key_file is not None) and (not key_file.closed): + key_file.close() + os.unlink(key_file.name) + __itsda_secret = key + _log.info("Saved new key for It's Dangerous") + +def setup_crypto(): + global __itsda_secret + key_dir = mg_globals.app_config["crypto_path"] + key_filepath = os.path.join(key_dir, 'itsdangeroussecret.bin') + try: + load_key(key_filepath) + except IOError, error: + if error.errno != errno.ENOENT: + raise + create_key(key_dir, key_filepath) + +def get_timed_signer_url(namespace): + """ + This gives a basic signing/verifying object. + + The namespace makes sure signed tokens can't be used in + a different area. Like using a forgot-password-token as + a session cookie. + + Basic usage: + + .. code-block:: python + + _signer = None + TOKEN_VALID_DAYS = 10 + def setup(): + global _signer + _signer = get_timed_signer_url("session cookie") + def create_token(obj): + return _signer.dumps(obj) + def parse_token(token): + # This might raise an exception in case + # of an invalid token, or an expired token. + return _signer.loads(token, max_age=TOKEN_VALID_DAYS*24*3600) + + For more details see + http://pythonhosted.org/itsdangerous/#itsdangerous.URLSafeTimedSerializer + """ + assert __itsda_secret is not None + return itsdangerous.URLSafeTimedSerializer(__itsda_secret, + salt=namespace) diff --git a/mediagoblin/tools/request.py b/mediagoblin/tools/request.py index bc67b96f..ee342eae 100644 --- a/mediagoblin/tools/request.py +++ b/mediagoblin/tools/request.py @@ -35,4 +35,4 @@ def setup_user_in_request(request): # Something's wrong... this user doesn't exist? Invalidate # this session. _log.warn("Killing session for user id %r", request.session['user_id']) - request.session.invalidate() + request.session.delete() diff --git a/mediagoblin/tools/session.py b/mediagoblin/tools/session.py new file mode 100644 index 00000000..d452b851 --- /dev/null +++ b/mediagoblin/tools/session.py @@ -0,0 +1,67 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import itsdangerous +import logging + +import crypto + +_log = logging.getLogger(__name__) + +class Session(dict): + def __init__(self, *args, **kwargs): + self.send_new_cookie = False + dict.__init__(self, *args, **kwargs) + + def save(self): + self.send_new_cookie = True + + def is_updated(self): + return self.send_new_cookie + + def delete(self): + self.clear() + self.save() + + +class SessionManager(object): + def __init__(self, cookie_name='MGSession', namespace=None): + if namespace is None: + namespace = cookie_name + self.signer = crypto.get_timed_signer_url(namespace) + self.cookie_name = cookie_name + + def load_session_from_cookie(self, request): + cookie = request.cookies.get(self.cookie_name) + if not cookie: + return Session() + ### FIXME: Future cookie-blacklisting code + # m = BadCookie.query.filter_by(cookie = cookie) + # if m: + # _log.warn("Bad cookie received: %s", m.reason) + # raise BadRequest() + try: + return Session(self.signer.loads(cookie)) + except itsdangerous.BadData: + return Session() + + def save_session_to_cookie(self, session, response): + if not session.is_updated(): + return + elif not session: + response.delete_cookie(self.cookie_name) + else: + response.set_cookie(self.cookie_name, self.signer.dumps(session)) |