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