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