db6b6e3708c88a50b0c06dac2eb0db2c633d8dc4
[mediagoblin.git] / mediagoblin / auth / tools.py
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
17 import uuid
18 import logging
19
20 import wtforms
21 from sqlalchemy import or_
22
23 from mediagoblin import mg_globals
24 from mediagoblin.auth import lib as auth_lib
25 from mediagoblin.db.models import User
26 from mediagoblin.tools.mail import (normalize_email, send_email,
27 email_debug_message)
28 from mediagoblin.tools.template import render_template
29 from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
30
31 _log = logging.getLogger(__name__)
32
33
34 def normalize_user_or_email_field(allow_email=True, allow_user=True):
35 """
36 Check if we were passed a field that matches a username and/or email
37 pattern.
38
39 This is useful for fields that can take either a username or email
40 address. Use the parameters if you want to only allow a username for
41 instance"""
42 message = _(u'Invalid User name or email address.')
43 nomail_msg = _(u"This field does not take email addresses.")
44 nouser_msg = _(u"This field requires an email address.")
45
46 def _normalize_field(form, field):
47 email = u'@' in field.data
48 if email: # normalize email address casing
49 if not allow_email:
50 raise wtforms.ValidationError(nomail_msg)
51 wtforms.validators.Email()(form, field)
52 field.data = normalize_email(field.data)
53 else: # lower case user names
54 if not allow_user:
55 raise wtforms.ValidationError(nouser_msg)
56 wtforms.validators.Length(min=3, max=30)(form, field)
57 wtforms.validators.Regexp(r'^\w+$')(form, field)
58 field.data = field.data.lower()
59 if field.data is None: # should not happen, but be cautious anyway
60 raise wtforms.ValidationError(message)
61 return _normalize_field
62
63
64 EMAIL_VERIFICATION_TEMPLATE = (
65 u"http://{host}{uri}?"
66 u"userid={userid}&token={verification_key}")
67
68
69 def send_verification_email(user, request):
70 """
71 Send the verification email to users to activate their accounts.
72
73 Args:
74 - user: a user object
75 - request: the request
76 """
77 rendered_email = render_template(
78 request, 'mediagoblin/auth/verification_email.txt',
79 {'username': user.username,
80 'verification_url': EMAIL_VERIFICATION_TEMPLATE.format(
81 host=request.host,
82 uri=request.urlgen('mediagoblin.auth.verify_email'),
83 userid=unicode(user.id),
84 verification_key=user.verification_key)})
85
86 # TODO: There is no error handling in place
87 send_email(
88 mg_globals.app_config['email_sender_address'],
89 [user.email],
90 # TODO
91 # Due to the distributed nature of GNU MediaGoblin, we should
92 # find a way to send some additional information about the
93 # specific GNU MediaGoblin instance in the subject line. For
94 # example "GNU MediaGoblin @ Wandborg - [...]".
95 'GNU MediaGoblin - Verify your email!',
96 rendered_email)
97
98
99 def basic_extra_validation(register_form, *args):
100 users_with_username = User.query.filter_by(
101 username=register_form.data['username']).count()
102 users_with_email = User.query.filter_by(
103 email=register_form.data['email']).count()
104
105 extra_validation_passes = True
106
107 if users_with_username:
108 register_form.username.errors.append(
109 _(u'Sorry, a user with that name already exists.'))
110 extra_validation_passes = False
111 if users_with_email:
112 register_form.email.errors.append(
113 _(u'Sorry, a user with that email address already exists.'))
114 extra_validation_passes = False
115
116 return extra_validation_passes
117
118
119 def register_user(request, register_form):
120 """ Handle user registration """
121 extra_validation_passes = basic_extra_validation(register_form)
122
123 if extra_validation_passes:
124 # Create the user
125 user = User()
126 user.username = register_form.data['username']
127 user.email = register_form.data['email']
128 user.pw_hash = auth_lib.bcrypt_gen_password_hash(
129 register_form.password.data)
130 user.verification_key = unicode(uuid.uuid4())
131 user.save()
132
133 # log the user in
134 request.session['user_id'] = unicode(user.id)
135 request.session.save()
136
137 # send verification email
138 email_debug_message(request)
139 send_verification_email(user, request)
140
141 return user
142
143 return None
144
145
146 def check_login_simple(username, password, username_might_be_email=False):
147 search = (User.username == username)
148 if username_might_be_email and ('@' in username):
149 search = or_(search, User.email == username)
150 user = User.query.filter(search).first()
151 if not user:
152 _log.info("User %r not found", username)
153 auth_lib.fake_login_attempt()
154 return None
155 if not auth_lib.bcrypt_check_password(password, user.pw_hash):
156 _log.warn("Wrong password for %r", username)
157 return None
158 _log.info("Logging %r in", username)
159 return user