Commit | Line | Data |
---|---|---|
5907154a E |
1 | # GNU MediaGoblin -- federated, autonomous media hosting |
2 | # Copyright (C) 2013 MediaGoblin contributors. See AUTHORS. | |
3 | # | |
4 | # This program is free software: you can redistribute it and/or modify | |
5 | # it under the terms of the GNU Affero General Public License as published by | |
6 | # the Free Software Foundation, either version 3 of the License, or | |
7 | # (at your option) any later version. | |
8 | # | |
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU Affero General Public License for more details. | |
13 | # | |
14 | # You should have received a copy of the GNU Affero General Public License | |
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | ||
4990b47c | 17 | import base64 |
18 | import string | |
09102e07 BS |
19 | import errno |
20 | import itsdangerous | |
5907154a | 21 | import logging |
09102e07 | 22 | import os.path |
5907154a | 23 | import random |
09102e07 | 24 | import tempfile |
5907154a E |
25 | from mediagoblin import mg_globals |
26 | ||
27 | _log = logging.getLogger(__name__) | |
28 | ||
4990b47c | 29 | # produces base64 alphabet |
c5eb24b8 | 30 | ALPHABET = string.ascii_letters + "-_" |
5907154a E |
31 | |
32 | # Use the system (hardware-based) random number generator if it exists. | |
33 | # -- this optimization is lifted from Django | |
09102e07 | 34 | try: |
5907154a | 35 | getrandbits = random.SystemRandom().getrandbits |
09102e07 | 36 | except AttributeError: |
5907154a E |
37 | getrandbits = random.getrandbits |
38 | ||
39 | ||
40 | __itsda_secret = None | |
41 | ||
42 | ||
09102e07 | 43 | def load_key(filename): |
5907154a | 44 | global __itsda_secret |
09102e07 BS |
45 | key_file = open(filename) |
46 | try: | |
47 | __itsda_secret = key_file.read() | |
48 | finally: | |
49 | key_file.close() | |
5907154a | 50 | |
11780855 | 51 | |
09102e07 BS |
52 | def create_key(key_dir, key_filepath): |
53 | global __itsda_secret | |
7f342c72 | 54 | old_umask = os.umask(0o77) |
09102e07 BS |
55 | key_file = None |
56 | try: | |
57 | if not os.path.isdir(key_dir): | |
58 | os.makedirs(key_dir) | |
11780855 | 59 | _log.info("Created %s", key_dir) |
09102e07 BS |
60 | key = str(getrandbits(192)) |
61 | key_file = tempfile.NamedTemporaryFile(dir=key_dir, suffix='.bin', | |
62 | delete=False) | |
cda3055b | 63 | key_file.write(key.encode('ascii')) |
09102e07 BS |
64 | key_file.flush() |
65 | os.rename(key_file.name, key_filepath) | |
66 | key_file.close() | |
67 | finally: | |
68 | os.umask(old_umask) | |
69 | if (key_file is not None) and (not key_file.closed): | |
70 | key_file.close() | |
71 | os.unlink(key_file.name) | |
72 | __itsda_secret = key | |
73 | _log.info("Saved new key for It's Dangerous") | |
74 | ||
11780855 | 75 | |
09102e07 BS |
76 | def setup_crypto(): |
77 | global __itsda_secret | |
78 | key_dir = mg_globals.app_config["crypto_path"] | |
79 | key_filepath = os.path.join(key_dir, 'itsdangeroussecret.bin') | |
80 | try: | |
81 | load_key(key_filepath) | |
7f342c72 | 82 | except IOError as error: |
09102e07 BS |
83 | if error.errno != errno.ENOENT: |
84 | raise | |
85 | create_key(key_dir, key_filepath) | |
5907154a | 86 | |
11780855 | 87 | |
5907154a | 88 | def get_timed_signer_url(namespace): |
5a8aae3a E |
89 | """ |
90 | This gives a basic signing/verifying object. | |
91 | ||
92 | The namespace makes sure signed tokens can't be used in | |
93 | a different area. Like using a forgot-password-token as | |
94 | a session cookie. | |
95 | ||
96 | Basic usage: | |
97 | ||
98 | .. code-block:: python | |
99 | ||
100 | _signer = None | |
101 | TOKEN_VALID_DAYS = 10 | |
102 | def setup(): | |
103 | global _signer | |
104 | _signer = get_timed_signer_url("session cookie") | |
105 | def create_token(obj): | |
106 | return _signer.dumps(obj) | |
107 | def parse_token(token): | |
108 | # This might raise an exception in case | |
109 | # of an invalid token, or an expired token. | |
110 | return _signer.loads(token, max_age=TOKEN_VALID_DAYS*24*3600) | |
111 | ||
112 | For more details see | |
113 | http://pythonhosted.org/itsdangerous/#itsdangerous.URLSafeTimedSerializer | |
114 | """ | |
5907154a E |
115 | assert __itsda_secret is not None |
116 | return itsdangerous.URLSafeTimedSerializer(__itsda_secret, | |
117 | salt=namespace) | |
4990b47c | 118 | |
c5eb24b8 | 119 | def random_string(length, alphabet=ALPHABET): |
4990b47c | 120 | """ Returns a URL safe base64 encoded crypographically strong string """ |
c5eb24b8 | 121 | base = len(alphabet) |
4990b47c | 122 | rstring = "" |
123 | for i in range(length): | |
124 | n = getrandbits(6) # 6 bytes = 2^6 = 64 | |
125 | n = divmod(n, base)[1] | |
126 | rstring += alphabet[n] | |
127 | ||
128 | return rstring |