Finished splitting util.py into separate files.
[mediagoblin.git] / mediagoblin / auth / lib.py
CommitLineData
8e1e744d 1# GNU MediaGoblin -- federated, autonomous media hosting
12a100e4 2# Copyright (C) 2011 MediaGoblin contributors. See AUTHORS.
6755f50e
CAW
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
17import os
c15c9843 18import random
6755f50e 19
51479a1d
CAW
20import bcrypt
21
152a3bfa 22from mediagoblin.tools.mail import send_email
ae3bc7fa 23from mediagoblin.tools.template import render_template
6e7ce8d1 24from mediagoblin import mg_globals
02d80437 25
6755f50e
CAW
26
27def 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 #
25ba955e 51 # See also:
6755f50e
CAW
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
59def 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
e0bc23d3 71 return unicode(bcrypt.hashpw(raw_pass, bcrypt.gensalt()))
c15c9843
CAW
72
73
74def 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
51479a1d
CAW
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.
c15c9843
CAW
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
02d80437
AM
91
92
2c3fd5c5
CAW
93EMAIL_VERIFICATION_TEMPLATE = (
94 u"http://{host}{uri}?"
95 u"userid={userid}&token={verification_key}")
96
02d80437
AM
97def send_verification_email(user, request):
98 """
99 Send the verification email to users to activate their accounts.
100
101 Args:
102 - user: a user object
25ba955e 103 - request: the request
02d80437 104 """
23cc15c9
CAW
105 rendered_email = render_template(
106 request, 'mediagoblin/auth/verification_email.txt',
107 {'username': user['username'],
108 'verification_url': EMAIL_VERIFICATION_TEMPLATE.format(
109 host=request.host,
110 uri=request.urlgen('mediagoblin.auth.verify_email'),
111 userid=unicode(user['_id']),
112 verification_key=user['verification_key'])})
02d80437
AM
113
114 # TODO: There is no error handling in place
115 send_email(
6ae8b541 116 mg_globals.app_config['email_sender_address'],
02d80437
AM
117 [user['email']],
118 # TODO
119 # Due to the distributed nature of GNU MediaGoblin, we should
25ba955e
AV
120 # find a way to send some additional information about the
121 # specific GNU MediaGoblin instance in the subject line. For
122 # example "GNU MediaGoblin @ Wandborg - [...]".
02d80437 123 'GNU MediaGoblin - Verify your email!',
23cc15c9 124 rendered_email)
25ba955e
AV
125
126
127EMAIL_FP_VERIFICATION_TEMPLATE = (
128 u"http://{host}{uri}?"
129 u"userid={userid}&token={fp_verification_key}")
130
63bf10f9 131def send_fp_verification_email(user, request):
25ba955e
AV
132 """
133 Send the verification email to users to change their password.
134
135 Args:
136 - user: a user object
137 - request: the request
138 """
139 rendered_email = render_template(
140 request, 'mediagoblin/auth/fp_verification_email.txt',
141 {'username': user['username'],
142 'verification_url': EMAIL_FP_VERIFICATION_TEMPLATE.format(
143 host=request.host,
144 uri=request.urlgen('mediagoblin.auth.verify_forgot_password'),
145 userid=unicode(user['_id']),
146 fp_verification_key=user['fp_verification_key'])})
147
148 # TODO: There is no error handling in place
149 send_email(
f85909c0 150 mg_globals.app_config['email_sender_address'],
25ba955e
AV
151 [user['email']],
152 'GNU MediaGoblin - Change forgotten password!',
153 rendered_email)
154