##// END OF EJS Templates
users: fix crash when creating users with non ASCII characters...
Mads Kiilerich -
r5697:4db2e72c stable
parent child Browse files
Show More
@@ -1,516 +1,516 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.model.user
15 kallithea.model.user
16 ~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~
17
17
18 users model for Kallithea
18 users model for Kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Apr 9, 2010
22 :created_on: Apr 9, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28
28
29 import hashlib
29 import hashlib
30 import hmac
30 import hmac
31 import logging
31 import logging
32 import time
32 import time
33 import traceback
33 import traceback
34
34
35 from pylons import config
35 from pylons import config
36 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
37
37
38 from sqlalchemy.exc import DatabaseError
38 from sqlalchemy.exc import DatabaseError
39
39
40 from kallithea import EXTERN_TYPE_INTERNAL
40 from kallithea import EXTERN_TYPE_INTERNAL
41 from kallithea.lib.utils2 import safe_unicode, generate_api_key, get_current_authuser
41 from kallithea.lib.utils2 import safe_unicode, generate_api_key, get_current_authuser
42 from kallithea.lib.caching_query import FromCache
42 from kallithea.lib.caching_query import FromCache
43 from kallithea.model import BaseModel
43 from kallithea.model import BaseModel
44 from kallithea.model.db import User, UserToPerm, Notification, \
44 from kallithea.model.db import User, UserToPerm, Notification, \
45 UserEmailMap, UserIpMap
45 UserEmailMap, UserIpMap
46 from kallithea.lib.exceptions import DefaultUserException, \
46 from kallithea.lib.exceptions import DefaultUserException, \
47 UserOwnsReposException
47 UserOwnsReposException
48 from kallithea.model.meta import Session
48 from kallithea.model.meta import Session
49
49
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 class UserModel(BaseModel):
54 class UserModel(BaseModel):
55 password_reset_token_lifetime = 86400 # 24 hours
55 password_reset_token_lifetime = 86400 # 24 hours
56
56
57 cls = User
57 cls = User
58
58
59 def get(self, user_id, cache=False):
59 def get(self, user_id, cache=False):
60 user = self.sa.query(User)
60 user = self.sa.query(User)
61 if cache:
61 if cache:
62 user = user.options(FromCache("sql_cache_short",
62 user = user.options(FromCache("sql_cache_short",
63 "get_user_%s" % user_id))
63 "get_user_%s" % user_id))
64 return user.get(user_id)
64 return user.get(user_id)
65
65
66 def get_user(self, user):
66 def get_user(self, user):
67 return self._get_user(user)
67 return self._get_user(user)
68
68
69 def create(self, form_data, cur_user=None):
69 def create(self, form_data, cur_user=None):
70 if not cur_user:
70 if not cur_user:
71 cur_user = getattr(get_current_authuser(), 'username', None)
71 cur_user = getattr(get_current_authuser(), 'username', None)
72
72
73 from kallithea.lib.hooks import log_create_user, \
73 from kallithea.lib.hooks import log_create_user, \
74 check_allowed_create_user
74 check_allowed_create_user
75 _fd = form_data
75 _fd = form_data
76 user_data = {
76 user_data = {
77 'username': _fd['username'],
77 'username': _fd['username'],
78 'password': _fd['password'],
78 'password': _fd['password'],
79 'email': _fd['email'],
79 'email': _fd['email'],
80 'firstname': _fd['firstname'],
80 'firstname': _fd['firstname'],
81 'lastname': _fd['lastname'],
81 'lastname': _fd['lastname'],
82 'active': _fd['active'],
82 'active': _fd['active'],
83 'admin': False
83 'admin': False
84 }
84 }
85 # raises UserCreationError if it's not allowed
85 # raises UserCreationError if it's not allowed
86 check_allowed_create_user(user_data, cur_user)
86 check_allowed_create_user(user_data, cur_user)
87 from kallithea.lib.auth import get_crypt_password
87 from kallithea.lib.auth import get_crypt_password
88
88
89 new_user = User()
89 new_user = User()
90 for k, v in form_data.items():
90 for k, v in form_data.items():
91 if k == 'password':
91 if k == 'password':
92 v = get_crypt_password(v)
92 v = get_crypt_password(v)
93 if k == 'firstname':
93 if k == 'firstname':
94 k = 'name'
94 k = 'name'
95 setattr(new_user, k, v)
95 setattr(new_user, k, v)
96
96
97 new_user.api_key = generate_api_key()
97 new_user.api_key = generate_api_key()
98 self.sa.add(new_user)
98 self.sa.add(new_user)
99
99
100 log_create_user(new_user.get_dict(), cur_user)
100 log_create_user(new_user.get_dict(), cur_user)
101 return new_user
101 return new_user
102
102
103 def create_or_update(self, username, password, email, firstname='',
103 def create_or_update(self, username, password, email, firstname='',
104 lastname='', active=True, admin=False,
104 lastname='', active=True, admin=False,
105 extern_type=None, extern_name=None, cur_user=None):
105 extern_type=None, extern_name=None, cur_user=None):
106 """
106 """
107 Creates a new instance if not found, or updates current one
107 Creates a new instance if not found, or updates current one
108
108
109 :param username:
109 :param username:
110 :param password:
110 :param password:
111 :param email:
111 :param email:
112 :param active:
112 :param active:
113 :param firstname:
113 :param firstname:
114 :param lastname:
114 :param lastname:
115 :param active:
115 :param active:
116 :param admin:
116 :param admin:
117 :param extern_name:
117 :param extern_name:
118 :param extern_type:
118 :param extern_type:
119 :param cur_user:
119 :param cur_user:
120 """
120 """
121 if not cur_user:
121 if not cur_user:
122 cur_user = getattr(get_current_authuser(), 'username', None)
122 cur_user = getattr(get_current_authuser(), 'username', None)
123
123
124 from kallithea.lib.auth import get_crypt_password, check_password
124 from kallithea.lib.auth import get_crypt_password, check_password
125 from kallithea.lib.hooks import log_create_user, \
125 from kallithea.lib.hooks import log_create_user, \
126 check_allowed_create_user
126 check_allowed_create_user
127 user_data = {
127 user_data = {
128 'username': username, 'password': password,
128 'username': username, 'password': password,
129 'email': email, 'firstname': firstname, 'lastname': lastname,
129 'email': email, 'firstname': firstname, 'lastname': lastname,
130 'active': active, 'admin': admin
130 'active': active, 'admin': admin
131 }
131 }
132 # raises UserCreationError if it's not allowed
132 # raises UserCreationError if it's not allowed
133 check_allowed_create_user(user_data, cur_user)
133 check_allowed_create_user(user_data, cur_user)
134
134
135 log.debug('Checking for %s account in Kallithea database', username)
135 log.debug('Checking for %s account in Kallithea database', username)
136 user = User.get_by_username(username, case_insensitive=True)
136 user = User.get_by_username(username, case_insensitive=True)
137 if user is None:
137 if user is None:
138 log.debug('creating new user %s', username)
138 log.debug('creating new user %s', username)
139 new_user = User()
139 new_user = User()
140 edit = False
140 edit = False
141 else:
141 else:
142 log.debug('updating user %s', username)
142 log.debug('updating user %s', username)
143 new_user = user
143 new_user = user
144 edit = True
144 edit = True
145
145
146 try:
146 try:
147 new_user.username = username
147 new_user.username = username
148 new_user.admin = admin
148 new_user.admin = admin
149 new_user.email = email
149 new_user.email = email
150 new_user.active = active
150 new_user.active = active
151 new_user.extern_name = safe_unicode(extern_name) \
151 new_user.extern_name = safe_unicode(extern_name) \
152 if extern_name else None
152 if extern_name else None
153 new_user.extern_type = safe_unicode(extern_type) \
153 new_user.extern_type = safe_unicode(extern_type) \
154 if extern_type else None
154 if extern_type else None
155 new_user.name = firstname
155 new_user.name = firstname
156 new_user.lastname = lastname
156 new_user.lastname = lastname
157
157
158 if not edit:
158 if not edit:
159 new_user.api_key = generate_api_key()
159 new_user.api_key = generate_api_key()
160
160
161 # set password only if creating an user or password is changed
161 # set password only if creating an user or password is changed
162 password_change = new_user.password and \
162 password_change = new_user.password and \
163 not check_password(password, new_user.password)
163 not check_password(password, new_user.password)
164 if not edit or password_change:
164 if not edit or password_change:
165 reason = 'new password' if edit else 'new user'
165 reason = 'new password' if edit else 'new user'
166 log.debug('Updating password reason=>%s', reason)
166 log.debug('Updating password reason=>%s', reason)
167 new_user.password = get_crypt_password(password) \
167 new_user.password = get_crypt_password(password) \
168 if password else None
168 if password else None
169
169
170 self.sa.add(new_user)
170 self.sa.add(new_user)
171
171
172 if not edit:
172 if not edit:
173 log_create_user(new_user.get_dict(), cur_user)
173 log_create_user(new_user.get_dict(), cur_user)
174 return new_user
174 return new_user
175 except (DatabaseError,):
175 except (DatabaseError,):
176 log.error(traceback.format_exc())
176 log.error(traceback.format_exc())
177 raise
177 raise
178
178
179 def create_registration(self, form_data):
179 def create_registration(self, form_data):
180 from kallithea.model.notification import NotificationModel
180 from kallithea.model.notification import NotificationModel
181 import kallithea.lib.helpers as h
181 import kallithea.lib.helpers as h
182
182
183 form_data['admin'] = False
183 form_data['admin'] = False
184 form_data['extern_name'] = EXTERN_TYPE_INTERNAL
184 form_data['extern_name'] = EXTERN_TYPE_INTERNAL
185 form_data['extern_type'] = EXTERN_TYPE_INTERNAL
185 form_data['extern_type'] = EXTERN_TYPE_INTERNAL
186 new_user = self.create(form_data)
186 new_user = self.create(form_data)
187
187
188 self.sa.add(new_user)
188 self.sa.add(new_user)
189 self.sa.flush()
189 self.sa.flush()
190
190
191 # notification to admins
191 # notification to admins
192 subject = _('New user registration')
192 subject = _('New user registration')
193 body = (
193 body = (
194 'New user registration\n'
194 u'New user registration\n'
195 '---------------------\n'
195 '---------------------\n'
196 '- Username: {user.username}\n'
196 '- Username: {user.username}\n'
197 '- Full Name: {user.full_name}\n'
197 '- Full Name: {user.full_name}\n'
198 '- Email: {user.email}\n'
198 '- Email: {user.email}\n'
199 ).format(user=new_user)
199 ).format(user=new_user)
200 edit_url = h.canonical_url('edit_user', id=new_user.user_id)
200 edit_url = h.canonical_url('edit_user', id=new_user.user_id)
201 email_kwargs = {
201 email_kwargs = {
202 'registered_user_url': edit_url,
202 'registered_user_url': edit_url,
203 'new_username': new_user.username}
203 'new_username': new_user.username}
204 NotificationModel().create(created_by=new_user, subject=subject,
204 NotificationModel().create(created_by=new_user, subject=subject,
205 body=body, recipients=None,
205 body=body, recipients=None,
206 type_=Notification.TYPE_REGISTRATION,
206 type_=Notification.TYPE_REGISTRATION,
207 email_kwargs=email_kwargs)
207 email_kwargs=email_kwargs)
208
208
209 def update(self, user_id, form_data, skip_attrs=[]):
209 def update(self, user_id, form_data, skip_attrs=[]):
210 from kallithea.lib.auth import get_crypt_password
210 from kallithea.lib.auth import get_crypt_password
211
211
212 user = self.get(user_id, cache=False)
212 user = self.get(user_id, cache=False)
213 if user.username == User.DEFAULT_USER:
213 if user.username == User.DEFAULT_USER:
214 raise DefaultUserException(
214 raise DefaultUserException(
215 _("You can't edit this user since it's "
215 _("You can't edit this user since it's "
216 "crucial for entire application"))
216 "crucial for entire application"))
217
217
218 for k, v in form_data.items():
218 for k, v in form_data.items():
219 if k in skip_attrs:
219 if k in skip_attrs:
220 continue
220 continue
221 if k == 'new_password' and v:
221 if k == 'new_password' and v:
222 user.password = get_crypt_password(v)
222 user.password = get_crypt_password(v)
223 else:
223 else:
224 # old legacy thing orm models store firstname as name,
224 # old legacy thing orm models store firstname as name,
225 # need proper refactor to username
225 # need proper refactor to username
226 if k == 'firstname':
226 if k == 'firstname':
227 k = 'name'
227 k = 'name'
228 setattr(user, k, v)
228 setattr(user, k, v)
229 self.sa.add(user)
229 self.sa.add(user)
230
230
231 def update_user(self, user, **kwargs):
231 def update_user(self, user, **kwargs):
232 from kallithea.lib.auth import get_crypt_password
232 from kallithea.lib.auth import get_crypt_password
233
233
234 user = self._get_user(user)
234 user = self._get_user(user)
235 if user.username == User.DEFAULT_USER:
235 if user.username == User.DEFAULT_USER:
236 raise DefaultUserException(
236 raise DefaultUserException(
237 _("You can't edit this user since it's"
237 _("You can't edit this user since it's"
238 " crucial for entire application")
238 " crucial for entire application")
239 )
239 )
240
240
241 for k, v in kwargs.items():
241 for k, v in kwargs.items():
242 if k == 'password' and v:
242 if k == 'password' and v:
243 v = get_crypt_password(v)
243 v = get_crypt_password(v)
244
244
245 setattr(user, k, v)
245 setattr(user, k, v)
246 self.sa.add(user)
246 self.sa.add(user)
247 return user
247 return user
248
248
249 def delete(self, user, cur_user=None):
249 def delete(self, user, cur_user=None):
250 if cur_user is None:
250 if cur_user is None:
251 cur_user = getattr(get_current_authuser(), 'username', None)
251 cur_user = getattr(get_current_authuser(), 'username', None)
252 user = self._get_user(user)
252 user = self._get_user(user)
253
253
254 if user.username == User.DEFAULT_USER:
254 if user.username == User.DEFAULT_USER:
255 raise DefaultUserException(
255 raise DefaultUserException(
256 _("You can't remove this user since it is"
256 _("You can't remove this user since it is"
257 " crucial for the entire application"))
257 " crucial for the entire application"))
258 if user.repositories:
258 if user.repositories:
259 repos = [x.repo_name for x in user.repositories]
259 repos = [x.repo_name for x in user.repositories]
260 raise UserOwnsReposException(
260 raise UserOwnsReposException(
261 _('User "%s" still owns %s repositories and cannot be '
261 _('User "%s" still owns %s repositories and cannot be '
262 'removed. Switch owners or remove those repositories: %s')
262 'removed. Switch owners or remove those repositories: %s')
263 % (user.username, len(repos), ', '.join(repos)))
263 % (user.username, len(repos), ', '.join(repos)))
264 if user.repo_groups:
264 if user.repo_groups:
265 repogroups = [x.group_name for x in user.repo_groups]
265 repogroups = [x.group_name for x in user.repo_groups]
266 raise UserOwnsReposException(_(
266 raise UserOwnsReposException(_(
267 'User "%s" still owns %s repository groups and cannot be '
267 'User "%s" still owns %s repository groups and cannot be '
268 'removed. Switch owners or remove those repository groups: %s')
268 'removed. Switch owners or remove those repository groups: %s')
269 % (user.username, len(repogroups), ', '.join(repogroups)))
269 % (user.username, len(repogroups), ', '.join(repogroups)))
270 if user.user_groups:
270 if user.user_groups:
271 usergroups = [x.users_group_name for x in user.user_groups]
271 usergroups = [x.users_group_name for x in user.user_groups]
272 raise UserOwnsReposException(
272 raise UserOwnsReposException(
273 _('User "%s" still owns %s user groups and cannot be '
273 _('User "%s" still owns %s user groups and cannot be '
274 'removed. Switch owners or remove those user groups: %s')
274 'removed. Switch owners or remove those user groups: %s')
275 % (user.username, len(usergroups), ', '.join(usergroups)))
275 % (user.username, len(usergroups), ', '.join(usergroups)))
276 self.sa.delete(user)
276 self.sa.delete(user)
277
277
278 from kallithea.lib.hooks import log_delete_user
278 from kallithea.lib.hooks import log_delete_user
279 log_delete_user(user.get_dict(), cur_user)
279 log_delete_user(user.get_dict(), cur_user)
280
280
281 def get_reset_password_token(self, user, timestamp, session_id):
281 def get_reset_password_token(self, user, timestamp, session_id):
282 """
282 """
283 The token is a 40-digit hexstring, calculated as a HMAC-SHA1.
283 The token is a 40-digit hexstring, calculated as a HMAC-SHA1.
284
284
285 In a traditional HMAC scenario, an attacker is unable to know or
285 In a traditional HMAC scenario, an attacker is unable to know or
286 influence the secret key, but can know or influence the message
286 influence the secret key, but can know or influence the message
287 and token. This scenario is slightly different (in particular
287 and token. This scenario is slightly different (in particular
288 since the message sender is also the message recipient), but
288 since the message sender is also the message recipient), but
289 sufficiently similar to use an HMAC. Benefits compared to a plain
289 sufficiently similar to use an HMAC. Benefits compared to a plain
290 SHA1 hash includes resistance against a length extension attack.
290 SHA1 hash includes resistance against a length extension attack.
291
291
292 The HMAC key consists of the following values (known only to the
292 The HMAC key consists of the following values (known only to the
293 server and authorized users):
293 server and authorized users):
294
294
295 * per-application secret (the `app_instance_uuid` setting), without
295 * per-application secret (the `app_instance_uuid` setting), without
296 which an attacker cannot counterfeit tokens
296 which an attacker cannot counterfeit tokens
297 * hashed user password, invalidating the token upon password change
297 * hashed user password, invalidating the token upon password change
298
298
299 The HMAC message consists of the following values (potentially known
299 The HMAC message consists of the following values (potentially known
300 to an attacker):
300 to an attacker):
301
301
302 * session ID (the anti-CSRF token), requiring an attacker to have
302 * session ID (the anti-CSRF token), requiring an attacker to have
303 access to the browser session in which the token was created
303 access to the browser session in which the token was created
304 * numeric user ID, limiting the token to a specific user (yet allowing
304 * numeric user ID, limiting the token to a specific user (yet allowing
305 users to be renamed)
305 users to be renamed)
306 * user email address
306 * user email address
307 * time of token issue (a Unix timestamp, to enable token expiration)
307 * time of token issue (a Unix timestamp, to enable token expiration)
308
308
309 The key and message values are separated by NUL characters, which are
309 The key and message values are separated by NUL characters, which are
310 guaranteed not to occur in any of the values.
310 guaranteed not to occur in any of the values.
311 """
311 """
312 app_secret = config.get('app_instance_uuid')
312 app_secret = config.get('app_instance_uuid')
313 return hmac.HMAC(
313 return hmac.HMAC(
314 key=u'\0'.join([app_secret, user.password]).encode('utf-8'),
314 key=u'\0'.join([app_secret, user.password]).encode('utf-8'),
315 msg=u'\0'.join([session_id, str(user.user_id), user.email, str(timestamp)]).encode('utf-8'),
315 msg=u'\0'.join([session_id, str(user.user_id), user.email, str(timestamp)]).encode('utf-8'),
316 digestmod=hashlib.sha1,
316 digestmod=hashlib.sha1,
317 ).hexdigest()
317 ).hexdigest()
318
318
319 def send_reset_password_email(self, data):
319 def send_reset_password_email(self, data):
320 """
320 """
321 Sends email with a password reset token and link to the password
321 Sends email with a password reset token and link to the password
322 reset confirmation page with all information (including the token)
322 reset confirmation page with all information (including the token)
323 pre-filled. Also returns URL of that page, only without the token,
323 pre-filled. Also returns URL of that page, only without the token,
324 allowing users to copy-paste or manually enter the token from the
324 allowing users to copy-paste or manually enter the token from the
325 email.
325 email.
326 """
326 """
327 from kallithea.lib.celerylib import tasks, run_task
327 from kallithea.lib.celerylib import tasks, run_task
328 from kallithea.model.notification import EmailNotificationModel
328 from kallithea.model.notification import EmailNotificationModel
329 import kallithea.lib.helpers as h
329 import kallithea.lib.helpers as h
330
330
331 user_email = data['email']
331 user_email = data['email']
332 user = User.get_by_email(user_email)
332 user = User.get_by_email(user_email)
333 timestamp = int(time.time())
333 timestamp = int(time.time())
334 if user is not None:
334 if user is not None:
335 log.debug('password reset user %s found', user)
335 log.debug('password reset user %s found', user)
336 token = self.get_reset_password_token(user,
336 token = self.get_reset_password_token(user,
337 timestamp,
337 timestamp,
338 h.authentication_token())
338 h.authentication_token())
339 # URL must be fully qualified; but since the token is locked to
339 # URL must be fully qualified; but since the token is locked to
340 # the current browser session, we must provide a URL with the
340 # the current browser session, we must provide a URL with the
341 # current scheme and hostname, rather than the canonical_url.
341 # current scheme and hostname, rather than the canonical_url.
342 link = h.url('reset_password_confirmation', qualified=True,
342 link = h.url('reset_password_confirmation', qualified=True,
343 email=user_email,
343 email=user_email,
344 timestamp=timestamp,
344 timestamp=timestamp,
345 token=token)
345 token=token)
346
346
347 reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
347 reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
348 body = EmailNotificationModel().get_email_tmpl(
348 body = EmailNotificationModel().get_email_tmpl(
349 reg_type, 'txt',
349 reg_type, 'txt',
350 user=user.short_contact,
350 user=user.short_contact,
351 reset_token=token,
351 reset_token=token,
352 reset_url=link)
352 reset_url=link)
353 html_body = EmailNotificationModel().get_email_tmpl(
353 html_body = EmailNotificationModel().get_email_tmpl(
354 reg_type, 'html',
354 reg_type, 'html',
355 user=user.short_contact,
355 user=user.short_contact,
356 reset_token=token,
356 reset_token=token,
357 reset_url=link)
357 reset_url=link)
358 log.debug('sending email')
358 log.debug('sending email')
359 run_task(tasks.send_email, [user_email],
359 run_task(tasks.send_email, [user_email],
360 _("Password reset link"), body, html_body)
360 _("Password reset link"), body, html_body)
361 log.info('send new password mail to %s', user_email)
361 log.info('send new password mail to %s', user_email)
362 else:
362 else:
363 log.debug("password reset email %s not found", user_email)
363 log.debug("password reset email %s not found", user_email)
364
364
365 return h.url('reset_password_confirmation',
365 return h.url('reset_password_confirmation',
366 email=user_email,
366 email=user_email,
367 timestamp=timestamp)
367 timestamp=timestamp)
368
368
369 def verify_reset_password_token(self, email, timestamp, token):
369 def verify_reset_password_token(self, email, timestamp, token):
370 from kallithea.lib.celerylib import tasks, run_task
370 from kallithea.lib.celerylib import tasks, run_task
371 from kallithea.lib import auth
371 from kallithea.lib import auth
372 import kallithea.lib.helpers as h
372 import kallithea.lib.helpers as h
373 user = User.get_by_email(email)
373 user = User.get_by_email(email)
374 if user is None:
374 if user is None:
375 log.debug("user with email %s not found", email)
375 log.debug("user with email %s not found", email)
376 return False
376 return False
377
377
378 token_age = int(time.time()) - int(timestamp)
378 token_age = int(time.time()) - int(timestamp)
379
379
380 if token_age < 0:
380 if token_age < 0:
381 log.debug('timestamp is from the future')
381 log.debug('timestamp is from the future')
382 return False
382 return False
383
383
384 if token_age > UserModel.password_reset_token_lifetime:
384 if token_age > UserModel.password_reset_token_lifetime:
385 log.debug('password reset token expired')
385 log.debug('password reset token expired')
386 return False
386 return False
387
387
388 expected_token = self.get_reset_password_token(user,
388 expected_token = self.get_reset_password_token(user,
389 timestamp,
389 timestamp,
390 h.authentication_token())
390 h.authentication_token())
391 log.debug('computed password reset token: %s', expected_token)
391 log.debug('computed password reset token: %s', expected_token)
392 log.debug('received password reset token: %s', token)
392 log.debug('received password reset token: %s', token)
393 return expected_token == token
393 return expected_token == token
394
394
395 def reset_password(self, user_email, new_passwd):
395 def reset_password(self, user_email, new_passwd):
396 from kallithea.lib.celerylib import tasks, run_task
396 from kallithea.lib.celerylib import tasks, run_task
397 from kallithea.lib import auth
397 from kallithea.lib import auth
398 user = User.get_by_email(user_email)
398 user = User.get_by_email(user_email)
399 if user is not None:
399 if user is not None:
400 user.password = auth.get_crypt_password(new_passwd)
400 user.password = auth.get_crypt_password(new_passwd)
401 Session().add(user)
401 Session().add(user)
402 Session().commit()
402 Session().commit()
403 log.info('change password for %s', user_email)
403 log.info('change password for %s', user_email)
404 if new_passwd is None:
404 if new_passwd is None:
405 raise Exception('unable to set new password')
405 raise Exception('unable to set new password')
406
406
407 run_task(tasks.send_email, [user_email],
407 run_task(tasks.send_email, [user_email],
408 _('Password reset notification'),
408 _('Password reset notification'),
409 _('The password to your account %s has been changed using password reset form.') % (user.username,))
409 _('The password to your account %s has been changed using password reset form.') % (user.username,))
410 log.info('send password reset mail to %s', user_email)
410 log.info('send password reset mail to %s', user_email)
411
411
412 return True
412 return True
413
413
414 def has_perm(self, user, perm):
414 def has_perm(self, user, perm):
415 perm = self._get_perm(perm)
415 perm = self._get_perm(perm)
416 user = self._get_user(user)
416 user = self._get_user(user)
417
417
418 return UserToPerm.query().filter(UserToPerm.user == user)\
418 return UserToPerm.query().filter(UserToPerm.user == user)\
419 .filter(UserToPerm.permission == perm).scalar() is not None
419 .filter(UserToPerm.permission == perm).scalar() is not None
420
420
421 def grant_perm(self, user, perm):
421 def grant_perm(self, user, perm):
422 """
422 """
423 Grant user global permissions
423 Grant user global permissions
424
424
425 :param user:
425 :param user:
426 :param perm:
426 :param perm:
427 """
427 """
428 user = self._get_user(user)
428 user = self._get_user(user)
429 perm = self._get_perm(perm)
429 perm = self._get_perm(perm)
430 # if this permission is already granted skip it
430 # if this permission is already granted skip it
431 _perm = UserToPerm.query()\
431 _perm = UserToPerm.query()\
432 .filter(UserToPerm.user == user)\
432 .filter(UserToPerm.user == user)\
433 .filter(UserToPerm.permission == perm)\
433 .filter(UserToPerm.permission == perm)\
434 .scalar()
434 .scalar()
435 if _perm:
435 if _perm:
436 return
436 return
437 new = UserToPerm()
437 new = UserToPerm()
438 new.user = user
438 new.user = user
439 new.permission = perm
439 new.permission = perm
440 self.sa.add(new)
440 self.sa.add(new)
441 return new
441 return new
442
442
443 def revoke_perm(self, user, perm):
443 def revoke_perm(self, user, perm):
444 """
444 """
445 Revoke users global permissions
445 Revoke users global permissions
446
446
447 :param user:
447 :param user:
448 :param perm:
448 :param perm:
449 """
449 """
450 user = self._get_user(user)
450 user = self._get_user(user)
451 perm = self._get_perm(perm)
451 perm = self._get_perm(perm)
452
452
453 UserToPerm.query().filter(
453 UserToPerm.query().filter(
454 UserToPerm.user == user,
454 UserToPerm.user == user,
455 UserToPerm.permission == perm,
455 UserToPerm.permission == perm,
456 ).delete()
456 ).delete()
457
457
458 def add_extra_email(self, user, email):
458 def add_extra_email(self, user, email):
459 """
459 """
460 Adds email address to UserEmailMap
460 Adds email address to UserEmailMap
461
461
462 :param user:
462 :param user:
463 :param email:
463 :param email:
464 """
464 """
465 from kallithea.model import forms
465 from kallithea.model import forms
466 form = forms.UserExtraEmailForm()()
466 form = forms.UserExtraEmailForm()()
467 data = form.to_python(dict(email=email))
467 data = form.to_python(dict(email=email))
468 user = self._get_user(user)
468 user = self._get_user(user)
469
469
470 obj = UserEmailMap()
470 obj = UserEmailMap()
471 obj.user = user
471 obj.user = user
472 obj.email = data['email']
472 obj.email = data['email']
473 self.sa.add(obj)
473 self.sa.add(obj)
474 return obj
474 return obj
475
475
476 def delete_extra_email(self, user, email_id):
476 def delete_extra_email(self, user, email_id):
477 """
477 """
478 Removes email address from UserEmailMap
478 Removes email address from UserEmailMap
479
479
480 :param user:
480 :param user:
481 :param email_id:
481 :param email_id:
482 """
482 """
483 user = self._get_user(user)
483 user = self._get_user(user)
484 obj = UserEmailMap.query().get(email_id)
484 obj = UserEmailMap.query().get(email_id)
485 if obj is not None:
485 if obj is not None:
486 self.sa.delete(obj)
486 self.sa.delete(obj)
487
487
488 def add_extra_ip(self, user, ip):
488 def add_extra_ip(self, user, ip):
489 """
489 """
490 Adds IP address to UserIpMap
490 Adds IP address to UserIpMap
491
491
492 :param user:
492 :param user:
493 :param ip:
493 :param ip:
494 """
494 """
495 from kallithea.model import forms
495 from kallithea.model import forms
496 form = forms.UserExtraIpForm()()
496 form = forms.UserExtraIpForm()()
497 data = form.to_python(dict(ip=ip))
497 data = form.to_python(dict(ip=ip))
498 user = self._get_user(user)
498 user = self._get_user(user)
499
499
500 obj = UserIpMap()
500 obj = UserIpMap()
501 obj.user = user
501 obj.user = user
502 obj.ip_addr = data['ip']
502 obj.ip_addr = data['ip']
503 self.sa.add(obj)
503 self.sa.add(obj)
504 return obj
504 return obj
505
505
506 def delete_extra_ip(self, user, ip_id):
506 def delete_extra_ip(self, user, ip_id):
507 """
507 """
508 Removes IP address from UserIpMap
508 Removes IP address from UserIpMap
509
509
510 :param user:
510 :param user:
511 :param ip_id:
511 :param ip_id:
512 """
512 """
513 user = self._get_user(user)
513 user = self._get_user(user)
514 obj = UserIpMap.query().get(ip_id)
514 obj = UserIpMap.query().get(ip_id)
515 if obj:
515 if obj:
516 self.sa.delete(obj)
516 self.sa.delete(obj)
General Comments 0
You need to be logged in to leave comments. Login now