Merge branch 'persona_resquash'
[mediagoblin.git] / mediagoblin / auth / tools.py
CommitLineData
7cb7653c
RE
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
744f1c83 17import logging
7cb7653c
RE
18import wtforms
19
92783bc1 20from mediagoblin import mg_globals
69b888c2 21from mediagoblin.tools.crypto import get_timed_signer_url
310a44d5
RE
22from mediagoblin.db.models import User
23from mediagoblin.tools.mail import (normalize_email, send_email,
24 email_debug_message)
c3e3882e 25from mediagoblin.tools.template import render_template
d90f44d2 26from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
744f1c83 27from mediagoblin.tools.pluginapi import hook_handle
c3e3882e 28from mediagoblin import auth
bcd10ad6
RE
29
30_log = logging.getLogger(__name__)
744f1c83 31
7cb7653c
RE
32
33def 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
744f1c83
RE
61
62
92783bc1 63EMAIL_VERIFICATION_TEMPLATE = (
342f06f7
RE
64 u"{uri}?"
65 u"token={verification_key}")
92783bc1
RE
66
67
8087f56b
RE
68def send_verification_email(user, request, email=None,
69 rendered_email=None):
92783bc1
RE
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 """
8087f56b
RE
77 if not email:
78 email = user.email
79
80 if not rendered_email:
342f06f7
RE
81 verification_key = get_timed_signer_url('mail_verification_token') \
82 .dumps(user.id)
8087f56b
RE
83 rendered_email = render_template(
84 request, 'mediagoblin/auth/verification_email.txt',
85 {'username': user.username,
86 'verification_url': EMAIL_VERIFICATION_TEMPLATE.format(
342f06f7
RE
87 uri=request.urlgen('mediagoblin.auth.verify_email',
88 qualified=True),
89 verification_key=verification_key)})
92783bc1
RE
90
91 # TODO: There is no error handling in place
92 send_email(
93 mg_globals.app_config['email_sender_address'],
8087f56b 94 [email],
92783bc1
RE
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)
310a44d5
RE
102
103
f855efff 104EMAIL_FP_VERIFICATION_TEMPLATE = (
61741697
RE
105 u"{uri}?"
106 u"token={fp_verification_key}")
f855efff
RE
107
108
109def 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 """
61741697
RE
117 fp_verification_key = get_timed_signer_url('mail_verification_token') \
118 .dumps(user.id)
5adb906a 119
f855efff
RE
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(
61741697
RE
124 uri=request.urlgen('mediagoblin.auth.verify_forgot_password',
125 qualified=True),
126 fp_verification_key=fp_verification_key)})
f855efff
RE
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
c3e3882e
RE
136def basic_extra_validation(register_form, *args):
137 users_with_username = User.query.filter_by(
569873d8 138 username=register_form.username.data).count()
c3e3882e 139 users_with_email = User.query.filter_by(
569873d8 140 email=register_form.email.data).count()
c3e3882e
RE
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
5784c12d
RE
156def 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)
0578d8b3 171
5784c12d
RE
172 return user
173
174 return None
175
176
f81206df 177def check_login_simple(username, password):
b1e02e0a 178 user = auth.get_user(username=username)
1d321f1c
RE
179 if not user:
180 _log.info("User %r not found", username)
e4deacd9 181 hook_handle("auth_fake_login_attempt")
1d321f1c 182 return None
cdc6b571 183 if not auth.check_password(password, user.pw_hash):
1d321f1c
RE
184 _log.warn("Wrong password for %r", username)
185 return None
186 _log.info("Logging %r in", username)
187 return user
bd7fe0c2
RE
188
189
bd7fe0c2 190def check_auth_enabled():
e4deacd9 191 if not hook_handle('authentication'):
bd7fe0c2
RE
192 _log.warning('No authentication is enabled')
193 return False
194 else:
195 return True
196
197
198def no_auth_logout(request):
5101c469 199 """Log out the user if authentication_disabled, but don't delete the messages"""
8ce8faaf
RE
200 if not mg_globals.app.auth and 'user_id' in request.session:
201 del request.session['user_id']
202 request.session.save()
5adb906a
RE
203
204
205def 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