1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
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.
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.
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/>.
21 from mediagoblin
import mg_globals
22 from mediagoblin
.tools
.crypto
import get_timed_signer_url
23 from mediagoblin
.tools
.mail
import send_email
24 from mediagoblin
.tools
.template
import render_template
27 def bcrypt_check_password(raw_pass
, stored_hash
, extra_salt
=None):
29 Check to see if this password matches.
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.
40 True or False depending on success.
43 raw_pass
= u
"%s:%s" % (extra_salt
, raw_pass
)
45 hashed_pass
= bcrypt
.hashpw(raw_pass
.encode('utf-8'), stored_hash
)
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.)
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
)
56 return randplus_stored_hash
== randplus_hashed_pass
59 def bcrypt_gen_password_hash(raw_pass
, extra_salt
=None):
61 Generate a salt for this new password.
64 - raw_pass: user submitted password
65 - extra_salt: (optional) If this password is with stored with a
66 non-database extra salt
69 raw_pass
= u
"%s:%s" % (extra_salt
, raw_pass
)
72 bcrypt
.hashpw(raw_pass
.encode('utf-8'), bcrypt
.gensalt()))
75 def fake_login_attempt():
77 Pretend we're trying to login.
79 Nothing actually happens here, we're just trying to take up some
80 time, approximately the same amount of time as
81 bcrypt_check_password, so as to avoid figuring out what users are
82 on the system by intentionally faking logins a bunch of times.
84 rand_salt
= bcrypt
.gensalt(5)
86 hashed_pass
= bcrypt
.hashpw(str(random
.random()), rand_salt
)
88 randplus_stored_hash
= bcrypt
.hashpw(str(random
.random()), rand_salt
)
89 randplus_hashed_pass
= bcrypt
.hashpw(hashed_pass
, rand_salt
)
91 randplus_stored_hash
== randplus_hashed_pass
94 EMAIL_FP_VERIFICATION_TEMPLATE
= (
96 u
"token={fp_verification_key}")
99 def send_fp_verification_email(user
, request
):
101 Send the verification email to users to change their password.
104 - user: a user object
105 - request: the request
107 fp_verification_key
= get_timed_signer_url('mail_verification_token') \
110 rendered_email
= render_template(
111 request
, 'mediagoblin/plugins/basic_auth/fp_verification_email.txt',
112 {'username': user
.username
,
113 'verification_url': EMAIL_FP_VERIFICATION_TEMPLATE
.format(
114 uri
=request
.urlgen('mediagoblin.plugins.basic_auth.verify_forgot_password',
116 fp_verification_key
=fp_verification_key
)})
118 # TODO: There is no error handling in place
120 mg_globals
.app_config
['email_sender_address'],
122 'GNU MediaGoblin - Change forgotten password!',