Merge branch 'video_gstreamer-only'
[mediagoblin.git] / mediagoblin / auth / lib.py
1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011 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
17 import os
18 import random
19
20 import bcrypt
21
22 from mediagoblin.tools.mail import send_email
23 from mediagoblin.tools.template import render_template
24 from mediagoblin import mg_globals
25
26
27 def bcrypt_check_password(raw_pass, stored_hash, extra_salt=None):
28 """
29 Check to see if this password matches.
30
31 Args:
32 - raw_pass: user submitted password to check for authenticity.
33 - stored_hash: The hash of the raw password (and possibly extra
34 salt) to check against
35 - extra_salt: (optional) If this password is with stored with a
36 non-database extra salt (probably in the config file) for extra
37 security, factor this into the check.
38
39 Returns:
40 True or False depending on success.
41 """
42 if extra_salt:
43 raw_pass = u"%s:%s" % (extra_salt, raw_pass)
44
45 hashed_pass = bcrypt.hashpw(raw_pass, stored_hash)
46
47 # Reduce risk of timing attacks by hashing again with a random
48 # number (thx to zooko on this advice, which I hopefully
49 # incorporated right.)
50 #
51 # See also:
52 rand_salt = bcrypt.gensalt(5)
53 randplus_stored_hash = bcrypt.hashpw(stored_hash, rand_salt)
54 randplus_hashed_pass = bcrypt.hashpw(hashed_pass, rand_salt)
55
56 return randplus_stored_hash == randplus_hashed_pass
57
58
59 def bcrypt_gen_password_hash(raw_pass, extra_salt=None):
60 """
61 Generate a salt for this new password.
62
63 Args:
64 - raw_pass: user submitted password
65 - extra_salt: (optional) If this password is with stored with a
66 non-database extra salt
67 """
68 if extra_salt:
69 raw_pass = u"%s:%s" % (extra_salt, raw_pass)
70
71 return unicode(bcrypt.hashpw(raw_pass, bcrypt.gensalt()))
72
73
74 def fake_login_attempt():
75 """
76 Pretend we're trying to login.
77
78 Nothing actually happens here, we're just trying to take up some
79 time, approximately the same amount of time as
80 bcrypt_check_password, so as to avoid figuring out what users are
81 on the system by intentionally faking logins a bunch of times.
82 """
83 rand_salt = bcrypt.gensalt(5)
84
85 hashed_pass = bcrypt.hashpw(str(random.random()), rand_salt)
86
87 randplus_stored_hash = bcrypt.hashpw(str(random.random()), rand_salt)
88 randplus_hashed_pass = bcrypt.hashpw(hashed_pass, rand_salt)
89
90 randplus_stored_hash == randplus_hashed_pass
91
92
93 EMAIL_VERIFICATION_TEMPLATE = (
94 u"http://{host}{uri}?"
95 u"userid={userid}&token={verification_key}")
96
97
98 def send_verification_email(user, request):
99 """
100 Send the verification email to users to activate their accounts.
101
102 Args:
103 - user: a user object
104 - request: the request
105 """
106 rendered_email = render_template(
107 request, 'mediagoblin/auth/verification_email.txt',
108 {'username': user['username'],
109 'verification_url': EMAIL_VERIFICATION_TEMPLATE.format(
110 host=request.host,
111 uri=request.urlgen('mediagoblin.auth.verify_email'),
112 userid=unicode(user._id),
113 verification_key=user['verification_key'])})
114
115 # TODO: There is no error handling in place
116 send_email(
117 mg_globals.app_config['email_sender_address'],
118 [user['email']],
119 # TODO
120 # Due to the distributed nature of GNU MediaGoblin, we should
121 # find a way to send some additional information about the
122 # specific GNU MediaGoblin instance in the subject line. For
123 # example "GNU MediaGoblin @ Wandborg - [...]".
124 'GNU MediaGoblin - Verify your email!',
125 rendered_email)
126
127
128 EMAIL_FP_VERIFICATION_TEMPLATE = (
129 u"http://{host}{uri}?"
130 u"userid={userid}&token={fp_verification_key}")
131
132
133 def send_fp_verification_email(user, request):
134 """
135 Send the verification email to users to change their password.
136
137 Args:
138 - user: a user object
139 - request: the request
140 """
141 rendered_email = render_template(
142 request, 'mediagoblin/auth/fp_verification_email.txt',
143 {'username': user['username'],
144 'verification_url': EMAIL_FP_VERIFICATION_TEMPLATE.format(
145 host=request.host,
146 uri=request.urlgen('mediagoblin.auth.verify_forgot_password'),
147 userid=unicode(user._id),
148 fp_verification_key=user['fp_verification_key'])})
149
150 # TODO: There is no error handling in place
151 send_email(
152 mg_globals.app_config['email_sender_address'],
153 [user['email']],
154 'GNU MediaGoblin - Change forgotten password!',
155 rendered_email)