tools.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. # GNU MediaGoblin -- federated, autonomous media hosting
  2. # Copyright (C) 2011, 2012 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. import bcrypt
  17. import random
  18. import six
  19. from mediagoblin import mg_globals
  20. from mediagoblin.tools.crypto import get_timed_signer_url
  21. from mediagoblin.tools.mail import send_email
  22. from mediagoblin.tools.template import render_template
  23. def bcrypt_check_password(raw_pass, stored_hash, extra_salt=None):
  24. """
  25. Check to see if this password matches.
  26. Args:
  27. - raw_pass: user submitted password to check for authenticity.
  28. - stored_hash: The hash of the raw password (and possibly extra
  29. salt) to check against
  30. - extra_salt: (optional) If this password is with stored with a
  31. non-database extra salt (probably in the config file) for extra
  32. security, factor this into the check.
  33. Returns:
  34. True or False depending on success.
  35. """
  36. if extra_salt:
  37. raw_pass = u"%s:%s" % (extra_salt, raw_pass)
  38. hashed_pass = bcrypt.hashpw(raw_pass.encode('utf-8'), stored_hash)
  39. # Reduce risk of timing attacks by hashing again with a random
  40. # number (thx to zooko on this advice, which I hopefully
  41. # incorporated right.)
  42. #
  43. # See also:
  44. rand_salt = bcrypt.gensalt(5)
  45. randplus_stored_hash = bcrypt.hashpw(stored_hash, rand_salt)
  46. randplus_hashed_pass = bcrypt.hashpw(hashed_pass, rand_salt)
  47. return randplus_stored_hash == randplus_hashed_pass
  48. def bcrypt_gen_password_hash(raw_pass, extra_salt=None):
  49. """
  50. Generate a salt for this new password.
  51. Args:
  52. - raw_pass: user submitted password
  53. - extra_salt: (optional) If this password is with stored with a
  54. non-database extra salt
  55. """
  56. if extra_salt:
  57. raw_pass = u"%s:%s" % (extra_salt, raw_pass)
  58. return six.text_type(
  59. bcrypt.hashpw(raw_pass.encode('utf-8'), bcrypt.gensalt()))
  60. def fake_login_attempt():
  61. """
  62. Pretend we're trying to login.
  63. Nothing actually happens here, we're just trying to take up some
  64. time, approximately the same amount of time as
  65. bcrypt_check_password, so as to avoid figuring out what users are
  66. on the system by intentionally faking logins a bunch of times.
  67. """
  68. rand_salt = bcrypt.gensalt(5)
  69. hashed_pass = bcrypt.hashpw(str(random.random()), rand_salt)
  70. randplus_stored_hash = bcrypt.hashpw(str(random.random()), rand_salt)
  71. randplus_hashed_pass = bcrypt.hashpw(hashed_pass, rand_salt)
  72. randplus_stored_hash == randplus_hashed_pass
  73. EMAIL_FP_VERIFICATION_TEMPLATE = (
  74. u"{uri}?"
  75. u"token={fp_verification_key}")
  76. def send_fp_verification_email(user, request):
  77. """
  78. Send the verification email to users to change their password.
  79. Args:
  80. - user: a user object
  81. - request: the request
  82. """
  83. fp_verification_key = get_timed_signer_url('mail_verification_token') \
  84. .dumps(user.id)
  85. rendered_email = render_template(
  86. request, 'mediagoblin/plugins/basic_auth/fp_verification_email.txt',
  87. {'username': user.username,
  88. 'verification_url': EMAIL_FP_VERIFICATION_TEMPLATE.format(
  89. uri=request.urlgen('mediagoblin.plugins.basic_auth.verify_forgot_password',
  90. qualified=True),
  91. fp_verification_key=fp_verification_key)})
  92. # TODO: There is no error handling in place
  93. send_email(
  94. mg_globals.app_config['email_sender_address'],
  95. [user.email],
  96. 'GNU MediaGoblin - Change forgotten password!',
  97. rendered_email)