Merge branch 'persona_resquash'
[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 logging
18 import wtforms
19
20 from mediagoblin import mg_globals
21 from mediagoblin.tools.crypto import get_timed_signer_url
22 from mediagoblin.db.models import User
23 from mediagoblin.tools.mail import (normalize_email, send_email,
24 email_debug_message)
25 from mediagoblin.tools.template import render_template
26 from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
27 from mediagoblin.tools.pluginapi import hook_handle
28 from mediagoblin import auth
29
30 _log = logging.getLogger(__name__)
31
32
33 def normalize_user_or_email_field(allow_email=True, allow_user=True):
34 """
35 Check if we were passed a field that matches a username and/or email
36 pattern.
37
38 This is useful for fields that can take either a username or email
39 address. Use the parameters if you want to only allow a username for
40 instance"""
41 message = _(u'Invalid User name or email address.')
42 nomail_msg = _(u"This field does not take email addresses.")
43 nouser_msg = _(u"This field requires an email address.")
44
45 def _normalize_field(form, field):
46 email = u'@' in field.data
47 if email: # normalize email address casing
48 if not allow_email:
49 raise wtforms.ValidationError(nomail_msg)
50 wtforms.validators.Email()(form, field)
51 field.data = normalize_email(field.data)
52 else: # lower case user names
53 if not allow_user:
54 raise wtforms.ValidationError(nouser_msg)
55 wtforms.validators.Length(min=3, max=30)(form, field)
56 wtforms.validators.Regexp(r'^\w+$')(form, field)
57 field.data = field.data.lower()
58 if field.data is None: # should not happen, but be cautious anyway
59 raise wtforms.ValidationError(message)
60 return _normalize_field
61
62
63 EMAIL_VERIFICATION_TEMPLATE = (
64 u"{uri}?"
65 u"token={verification_key}")
66
67
68 def send_verification_email(user, request, email=None,
69 rendered_email=None):
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 if not email:
78 email = user.email
79
80 if not rendered_email:
81 verification_key = get_timed_signer_url('mail_verification_token') \
82 .dumps(user.id)
83 rendered_email = render_template(
84 request, 'mediagoblin/auth/verification_email.txt',
85 {'username': user.username,
86 'verification_url': EMAIL_VERIFICATION_TEMPLATE.format(
87 uri=request.urlgen('mediagoblin.auth.verify_email',
88 qualified=True),
89 verification_key=verification_key)})
90
91 # TODO: There is no error handling in place
92 send_email(
93 mg_globals.app_config['email_sender_address'],
94 [email],
95 # TODO
96 # Due to the distributed nature of GNU MediaGoblin, we should
97 # find a way to send some additional information about the
98 # specific GNU MediaGoblin instance in the subject line. For
99 # example "GNU MediaGoblin @ Wandborg - [...]".
100 'GNU MediaGoblin - Verify your email!',
101 rendered_email)
102
103
104 EMAIL_FP_VERIFICATION_TEMPLATE = (
105 u"{uri}?"
106 u"token={fp_verification_key}")
107
108
109 def send_fp_verification_email(user, request):
110 """
111 Send the verification email to users to change their password.
112
113 Args:
114 - user: a user object
115 - request: the request
116 """
117 fp_verification_key = get_timed_signer_url('mail_verification_token') \
118 .dumps(user.id)
119
120 rendered_email = render_template(
121 request, 'mediagoblin/auth/fp_verification_email.txt',
122 {'username': user.username,
123 'verification_url': EMAIL_FP_VERIFICATION_TEMPLATE.format(
124 uri=request.urlgen('mediagoblin.auth.verify_forgot_password',
125 qualified=True),
126 fp_verification_key=fp_verification_key)})
127
128 # TODO: There is no error handling in place
129 send_email(
130 mg_globals.app_config['email_sender_address'],
131 [user.email],
132 'GNU MediaGoblin - Change forgotten password!',
133 rendered_email)
134
135
136 def basic_extra_validation(register_form, *args):
137 users_with_username = User.query.filter_by(
138 username=register_form.username.data).count()
139 users_with_email = User.query.filter_by(
140 email=register_form.email.data).count()
141
142 extra_validation_passes = True
143
144 if users_with_username:
145 register_form.username.errors.append(
146 _(u'Sorry, a user with that name already exists.'))
147 extra_validation_passes = False
148 if users_with_email:
149 register_form.email.errors.append(
150 _(u'Sorry, a user with that email address already exists.'))
151 extra_validation_passes = False
152
153 return extra_validation_passes
154
155
156 def register_user(request, register_form):
157 """ Handle user registration """
158 extra_validation_passes = auth.extra_validation(register_form)
159
160 if extra_validation_passes:
161 # Create the user
162 user = auth.create_user(register_form)
163
164 # log the user in
165 request.session['user_id'] = unicode(user.id)
166 request.session.save()
167
168 # send verification email
169 email_debug_message(request)
170 send_verification_email(user, request)
171
172 return user
173
174 return None
175
176
177 def check_login_simple(username, password):
178 user = auth.get_user(username=username)
179 if not user:
180 _log.info("User %r not found", username)
181 hook_handle("auth_fake_login_attempt")
182 return None
183 if not auth.check_password(password, user.pw_hash):
184 _log.warn("Wrong password for %r", username)
185 return None
186 _log.info("Logging %r in", username)
187 return user
188
189
190 def check_auth_enabled():
191 if not hook_handle('authentication'):
192 _log.warning('No authentication is enabled')
193 return False
194 else:
195 return True
196
197
198 def no_auth_logout(request):
199 """Log out the user if authentication_disabled, but don't delete the messages"""
200 if not mg_globals.app.auth and 'user_id' in request.session:
201 del request.session['user_id']
202 request.session.save()
203
204
205 def create_basic_user(form):
206 user = User()
207 user.username = form.username.data
208 user.email = form.email.data
209 user.save()
210 return user