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