##// END OF EJS Templates
users: autocomplete now sorts by matched username to show best matches first.
marcink -
r4518:0cddd671 stable
parent child
Show More
@@ -1,1046 +1,1050
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 users model for RhodeCode
22 users model for RhodeCode
23 """
23 """
24
24
25 import logging
25 import logging
26 import traceback
26 import traceback
27 import datetime
27 import datetime
28 import ipaddress
28 import ipaddress
29
29
30 from pyramid.threadlocal import get_current_request
30 from pyramid.threadlocal import get_current_request
31 from sqlalchemy.exc import DatabaseError
31 from sqlalchemy.exc import DatabaseError
32
32
33 from rhodecode import events
33 from rhodecode import events
34 from rhodecode.lib.user_log_filter import user_log_filter
34 from rhodecode.lib.user_log_filter import user_log_filter
35 from rhodecode.lib.utils2 import (
35 from rhodecode.lib.utils2 import (
36 safe_unicode, get_current_rhodecode_user, action_logger_generic,
36 safe_unicode, get_current_rhodecode_user, action_logger_generic,
37 AttributeDict, str2bool)
37 AttributeDict, str2bool)
38 from rhodecode.lib.exceptions import (
38 from rhodecode.lib.exceptions import (
39 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
39 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
40 UserOwnsUserGroupsException, NotAllowedToCreateUserError,
40 UserOwnsUserGroupsException, NotAllowedToCreateUserError,
41 UserOwnsPullRequestsException, UserOwnsArtifactsException)
41 UserOwnsPullRequestsException, UserOwnsArtifactsException)
42 from rhodecode.lib.caching_query import FromCache
42 from rhodecode.lib.caching_query import FromCache
43 from rhodecode.model import BaseModel
43 from rhodecode.model import BaseModel
44 from rhodecode.model.db import (
44 from rhodecode.model.db import (
45 _hash_key, true, false, or_, joinedload, User, UserToPerm,
45 _hash_key, func, true, false, or_, joinedload, User, UserToPerm,
46 UserEmailMap, UserIpMap, UserLog)
46 UserEmailMap, UserIpMap, UserLog)
47 from rhodecode.model.meta import Session
47 from rhodecode.model.meta import Session
48 from rhodecode.model.auth_token import AuthTokenModel
48 from rhodecode.model.auth_token import AuthTokenModel
49 from rhodecode.model.repo_group import RepoGroupModel
49 from rhodecode.model.repo_group import RepoGroupModel
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 cls = User
55 cls = User
56
56
57 def get(self, user_id, cache=False):
57 def get(self, user_id, cache=False):
58 user = self.sa.query(User)
58 user = self.sa.query(User)
59 if cache:
59 if cache:
60 user = user.options(
60 user = user.options(
61 FromCache("sql_cache_short", "get_user_%s" % user_id))
61 FromCache("sql_cache_short", "get_user_%s" % user_id))
62 return user.get(user_id)
62 return user.get(user_id)
63
63
64 def get_user(self, user):
64 def get_user(self, user):
65 return self._get_user(user)
65 return self._get_user(user)
66
66
67 def _serialize_user(self, user):
67 def _serialize_user(self, user):
68 import rhodecode.lib.helpers as h
68 import rhodecode.lib.helpers as h
69
69
70 return {
70 return {
71 'id': user.user_id,
71 'id': user.user_id,
72 'first_name': user.first_name,
72 'first_name': user.first_name,
73 'last_name': user.last_name,
73 'last_name': user.last_name,
74 'username': user.username,
74 'username': user.username,
75 'email': user.email,
75 'email': user.email,
76 'icon_link': h.gravatar_url(user.email, 30),
76 'icon_link': h.gravatar_url(user.email, 30),
77 'profile_link': h.link_to_user(user),
77 'profile_link': h.link_to_user(user),
78 'value_display': h.escape(h.person(user)),
78 'value_display': h.escape(h.person(user)),
79 'value': user.username,
79 'value': user.username,
80 'value_type': 'user',
80 'value_type': 'user',
81 'active': user.active,
81 'active': user.active,
82 }
82 }
83
83
84 def get_users(self, name_contains=None, limit=20, only_active=True):
84 def get_users(self, name_contains=None, limit=20, only_active=True):
85
85
86 query = self.sa.query(User)
86 query = self.sa.query(User)
87 if only_active:
87 if only_active:
88 query = query.filter(User.active == true())
88 query = query.filter(User.active == true())
89
89
90 if name_contains:
90 if name_contains:
91 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
91 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
92 query = query.filter(
92 query = query.filter(
93 or_(
93 or_(
94 User.name.ilike(ilike_expression),
94 User.name.ilike(ilike_expression),
95 User.lastname.ilike(ilike_expression),
95 User.lastname.ilike(ilike_expression),
96 User.username.ilike(ilike_expression)
96 User.username.ilike(ilike_expression)
97 )
97 )
98 )
98 )
99 # sort by len to have top most matches first
100 query = query.order_by(func.length(User.username))\
101 .order_by(User.username)
99 query = query.limit(limit)
102 query = query.limit(limit)
103
100 users = query.all()
104 users = query.all()
101
105
102 _users = [
106 _users = [
103 self._serialize_user(user) for user in users
107 self._serialize_user(user) for user in users
104 ]
108 ]
105 return _users
109 return _users
106
110
107 def get_by_username(self, username, cache=False, case_insensitive=False):
111 def get_by_username(self, username, cache=False, case_insensitive=False):
108
112
109 if case_insensitive:
113 if case_insensitive:
110 user = self.sa.query(User).filter(User.username.ilike(username))
114 user = self.sa.query(User).filter(User.username.ilike(username))
111 else:
115 else:
112 user = self.sa.query(User)\
116 user = self.sa.query(User)\
113 .filter(User.username == username)
117 .filter(User.username == username)
114 if cache:
118 if cache:
115 name_key = _hash_key(username)
119 name_key = _hash_key(username)
116 user = user.options(
120 user = user.options(
117 FromCache("sql_cache_short", "get_user_%s" % name_key))
121 FromCache("sql_cache_short", "get_user_%s" % name_key))
118 return user.scalar()
122 return user.scalar()
119
123
120 def get_by_email(self, email, cache=False, case_insensitive=False):
124 def get_by_email(self, email, cache=False, case_insensitive=False):
121 return User.get_by_email(email, case_insensitive, cache)
125 return User.get_by_email(email, case_insensitive, cache)
122
126
123 def get_by_auth_token(self, auth_token, cache=False):
127 def get_by_auth_token(self, auth_token, cache=False):
124 return User.get_by_auth_token(auth_token, cache)
128 return User.get_by_auth_token(auth_token, cache)
125
129
126 def get_active_user_count(self, cache=False):
130 def get_active_user_count(self, cache=False):
127 qry = User.query().filter(
131 qry = User.query().filter(
128 User.active == true()).filter(
132 User.active == true()).filter(
129 User.username != User.DEFAULT_USER)
133 User.username != User.DEFAULT_USER)
130 if cache:
134 if cache:
131 qry = qry.options(
135 qry = qry.options(
132 FromCache("sql_cache_short", "get_active_users"))
136 FromCache("sql_cache_short", "get_active_users"))
133 return qry.count()
137 return qry.count()
134
138
135 def create(self, form_data, cur_user=None):
139 def create(self, form_data, cur_user=None):
136 if not cur_user:
140 if not cur_user:
137 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
141 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
138
142
139 user_data = {
143 user_data = {
140 'username': form_data['username'],
144 'username': form_data['username'],
141 'password': form_data['password'],
145 'password': form_data['password'],
142 'email': form_data['email'],
146 'email': form_data['email'],
143 'firstname': form_data['firstname'],
147 'firstname': form_data['firstname'],
144 'lastname': form_data['lastname'],
148 'lastname': form_data['lastname'],
145 'active': form_data['active'],
149 'active': form_data['active'],
146 'extern_type': form_data['extern_type'],
150 'extern_type': form_data['extern_type'],
147 'extern_name': form_data['extern_name'],
151 'extern_name': form_data['extern_name'],
148 'admin': False,
152 'admin': False,
149 'cur_user': cur_user
153 'cur_user': cur_user
150 }
154 }
151
155
152 if 'create_repo_group' in form_data:
156 if 'create_repo_group' in form_data:
153 user_data['create_repo_group'] = str2bool(
157 user_data['create_repo_group'] = str2bool(
154 form_data.get('create_repo_group'))
158 form_data.get('create_repo_group'))
155
159
156 try:
160 try:
157 if form_data.get('password_change'):
161 if form_data.get('password_change'):
158 user_data['force_password_change'] = True
162 user_data['force_password_change'] = True
159 return UserModel().create_or_update(**user_data)
163 return UserModel().create_or_update(**user_data)
160 except Exception:
164 except Exception:
161 log.error(traceback.format_exc())
165 log.error(traceback.format_exc())
162 raise
166 raise
163
167
164 def update_user(self, user, skip_attrs=None, **kwargs):
168 def update_user(self, user, skip_attrs=None, **kwargs):
165 from rhodecode.lib.auth import get_crypt_password
169 from rhodecode.lib.auth import get_crypt_password
166
170
167 user = self._get_user(user)
171 user = self._get_user(user)
168 if user.username == User.DEFAULT_USER:
172 if user.username == User.DEFAULT_USER:
169 raise DefaultUserException(
173 raise DefaultUserException(
170 "You can't edit this user (`%(username)s`) since it's "
174 "You can't edit this user (`%(username)s`) since it's "
171 "crucial for entire application" % {
175 "crucial for entire application" % {
172 'username': user.username})
176 'username': user.username})
173
177
174 # first store only defaults
178 # first store only defaults
175 user_attrs = {
179 user_attrs = {
176 'updating_user_id': user.user_id,
180 'updating_user_id': user.user_id,
177 'username': user.username,
181 'username': user.username,
178 'password': user.password,
182 'password': user.password,
179 'email': user.email,
183 'email': user.email,
180 'firstname': user.name,
184 'firstname': user.name,
181 'lastname': user.lastname,
185 'lastname': user.lastname,
182 'description': user.description,
186 'description': user.description,
183 'active': user.active,
187 'active': user.active,
184 'admin': user.admin,
188 'admin': user.admin,
185 'extern_name': user.extern_name,
189 'extern_name': user.extern_name,
186 'extern_type': user.extern_type,
190 'extern_type': user.extern_type,
187 'language': user.user_data.get('language')
191 'language': user.user_data.get('language')
188 }
192 }
189
193
190 # in case there's new_password, that comes from form, use it to
194 # in case there's new_password, that comes from form, use it to
191 # store password
195 # store password
192 if kwargs.get('new_password'):
196 if kwargs.get('new_password'):
193 kwargs['password'] = kwargs['new_password']
197 kwargs['password'] = kwargs['new_password']
194
198
195 # cleanups, my_account password change form
199 # cleanups, my_account password change form
196 kwargs.pop('current_password', None)
200 kwargs.pop('current_password', None)
197 kwargs.pop('new_password', None)
201 kwargs.pop('new_password', None)
198
202
199 # cleanups, user edit password change form
203 # cleanups, user edit password change form
200 kwargs.pop('password_confirmation', None)
204 kwargs.pop('password_confirmation', None)
201 kwargs.pop('password_change', None)
205 kwargs.pop('password_change', None)
202
206
203 # create repo group on user creation
207 # create repo group on user creation
204 kwargs.pop('create_repo_group', None)
208 kwargs.pop('create_repo_group', None)
205
209
206 # legacy forms send name, which is the firstname
210 # legacy forms send name, which is the firstname
207 firstname = kwargs.pop('name', None)
211 firstname = kwargs.pop('name', None)
208 if firstname:
212 if firstname:
209 kwargs['firstname'] = firstname
213 kwargs['firstname'] = firstname
210
214
211 for k, v in kwargs.items():
215 for k, v in kwargs.items():
212 # skip if we don't want to update this
216 # skip if we don't want to update this
213 if skip_attrs and k in skip_attrs:
217 if skip_attrs and k in skip_attrs:
214 continue
218 continue
215
219
216 user_attrs[k] = v
220 user_attrs[k] = v
217
221
218 try:
222 try:
219 return self.create_or_update(**user_attrs)
223 return self.create_or_update(**user_attrs)
220 except Exception:
224 except Exception:
221 log.error(traceback.format_exc())
225 log.error(traceback.format_exc())
222 raise
226 raise
223
227
224 def create_or_update(
228 def create_or_update(
225 self, username, password, email, firstname='', lastname='',
229 self, username, password, email, firstname='', lastname='',
226 active=True, admin=False, extern_type=None, extern_name=None,
230 active=True, admin=False, extern_type=None, extern_name=None,
227 cur_user=None, plugin=None, force_password_change=False,
231 cur_user=None, plugin=None, force_password_change=False,
228 allow_to_create_user=True, create_repo_group=None,
232 allow_to_create_user=True, create_repo_group=None,
229 updating_user_id=None, language=None, description='',
233 updating_user_id=None, language=None, description='',
230 strict_creation_check=True):
234 strict_creation_check=True):
231 """
235 """
232 Creates a new instance if not found, or updates current one
236 Creates a new instance if not found, or updates current one
233
237
234 :param username:
238 :param username:
235 :param password:
239 :param password:
236 :param email:
240 :param email:
237 :param firstname:
241 :param firstname:
238 :param lastname:
242 :param lastname:
239 :param active:
243 :param active:
240 :param admin:
244 :param admin:
241 :param extern_type:
245 :param extern_type:
242 :param extern_name:
246 :param extern_name:
243 :param cur_user:
247 :param cur_user:
244 :param plugin: optional plugin this method was called from
248 :param plugin: optional plugin this method was called from
245 :param force_password_change: toggles new or existing user flag
249 :param force_password_change: toggles new or existing user flag
246 for password change
250 for password change
247 :param allow_to_create_user: Defines if the method can actually create
251 :param allow_to_create_user: Defines if the method can actually create
248 new users
252 new users
249 :param create_repo_group: Defines if the method should also
253 :param create_repo_group: Defines if the method should also
250 create an repo group with user name, and owner
254 create an repo group with user name, and owner
251 :param updating_user_id: if we set it up this is the user we want to
255 :param updating_user_id: if we set it up this is the user we want to
252 update this allows to editing username.
256 update this allows to editing username.
253 :param language: language of user from interface.
257 :param language: language of user from interface.
254 :param description: user description
258 :param description: user description
255 :param strict_creation_check: checks for allowed creation license wise etc.
259 :param strict_creation_check: checks for allowed creation license wise etc.
256
260
257 :returns: new User object with injected `is_new_user` attribute.
261 :returns: new User object with injected `is_new_user` attribute.
258 """
262 """
259
263
260 if not cur_user:
264 if not cur_user:
261 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
265 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
262
266
263 from rhodecode.lib.auth import (
267 from rhodecode.lib.auth import (
264 get_crypt_password, check_password)
268 get_crypt_password, check_password)
265 from rhodecode.lib import hooks_base
269 from rhodecode.lib import hooks_base
266
270
267 def _password_change(new_user, password):
271 def _password_change(new_user, password):
268 old_password = new_user.password or ''
272 old_password = new_user.password or ''
269 # empty password
273 # empty password
270 if not old_password:
274 if not old_password:
271 return False
275 return False
272
276
273 # password check is only needed for RhodeCode internal auth calls
277 # password check is only needed for RhodeCode internal auth calls
274 # in case it's a plugin we don't care
278 # in case it's a plugin we don't care
275 if not plugin:
279 if not plugin:
276
280
277 # first check if we gave crypted password back, and if it
281 # first check if we gave crypted password back, and if it
278 # matches it's not password change
282 # matches it's not password change
279 if new_user.password == password:
283 if new_user.password == password:
280 return False
284 return False
281
285
282 password_match = check_password(password, old_password)
286 password_match = check_password(password, old_password)
283 if not password_match:
287 if not password_match:
284 return True
288 return True
285
289
286 return False
290 return False
287
291
288 # read settings on default personal repo group creation
292 # read settings on default personal repo group creation
289 if create_repo_group is None:
293 if create_repo_group is None:
290 default_create_repo_group = RepoGroupModel()\
294 default_create_repo_group = RepoGroupModel()\
291 .get_default_create_personal_repo_group()
295 .get_default_create_personal_repo_group()
292 create_repo_group = default_create_repo_group
296 create_repo_group = default_create_repo_group
293
297
294 user_data = {
298 user_data = {
295 'username': username,
299 'username': username,
296 'password': password,
300 'password': password,
297 'email': email,
301 'email': email,
298 'firstname': firstname,
302 'firstname': firstname,
299 'lastname': lastname,
303 'lastname': lastname,
300 'active': active,
304 'active': active,
301 'admin': admin
305 'admin': admin
302 }
306 }
303
307
304 if updating_user_id:
308 if updating_user_id:
305 log.debug('Checking for existing account in RhodeCode '
309 log.debug('Checking for existing account in RhodeCode '
306 'database with user_id `%s` ', updating_user_id)
310 'database with user_id `%s` ', updating_user_id)
307 user = User.get(updating_user_id)
311 user = User.get(updating_user_id)
308 else:
312 else:
309 log.debug('Checking for existing account in RhodeCode '
313 log.debug('Checking for existing account in RhodeCode '
310 'database with username `%s` ', username)
314 'database with username `%s` ', username)
311 user = User.get_by_username(username, case_insensitive=True)
315 user = User.get_by_username(username, case_insensitive=True)
312
316
313 if user is None:
317 if user is None:
314 # we check internal flag if this method is actually allowed to
318 # we check internal flag if this method is actually allowed to
315 # create new user
319 # create new user
316 if not allow_to_create_user:
320 if not allow_to_create_user:
317 msg = ('Method wants to create new user, but it is not '
321 msg = ('Method wants to create new user, but it is not '
318 'allowed to do so')
322 'allowed to do so')
319 log.warning(msg)
323 log.warning(msg)
320 raise NotAllowedToCreateUserError(msg)
324 raise NotAllowedToCreateUserError(msg)
321
325
322 log.debug('Creating new user %s', username)
326 log.debug('Creating new user %s', username)
323
327
324 # only if we create user that is active
328 # only if we create user that is active
325 new_active_user = active
329 new_active_user = active
326 if new_active_user and strict_creation_check:
330 if new_active_user and strict_creation_check:
327 # raises UserCreationError if it's not allowed for any reason to
331 # raises UserCreationError if it's not allowed for any reason to
328 # create new active user, this also executes pre-create hooks
332 # create new active user, this also executes pre-create hooks
329 hooks_base.check_allowed_create_user(user_data, cur_user, strict_check=True)
333 hooks_base.check_allowed_create_user(user_data, cur_user, strict_check=True)
330 events.trigger(events.UserPreCreate(user_data))
334 events.trigger(events.UserPreCreate(user_data))
331 new_user = User()
335 new_user = User()
332 edit = False
336 edit = False
333 else:
337 else:
334 log.debug('updating user `%s`', username)
338 log.debug('updating user `%s`', username)
335 events.trigger(events.UserPreUpdate(user, user_data))
339 events.trigger(events.UserPreUpdate(user, user_data))
336 new_user = user
340 new_user = user
337 edit = True
341 edit = True
338
342
339 # we're not allowed to edit default user
343 # we're not allowed to edit default user
340 if user.username == User.DEFAULT_USER:
344 if user.username == User.DEFAULT_USER:
341 raise DefaultUserException(
345 raise DefaultUserException(
342 "You can't edit this user (`%(username)s`) since it's "
346 "You can't edit this user (`%(username)s`) since it's "
343 "crucial for entire application"
347 "crucial for entire application"
344 % {'username': user.username})
348 % {'username': user.username})
345
349
346 # inject special attribute that will tell us if User is new or old
350 # inject special attribute that will tell us if User is new or old
347 new_user.is_new_user = not edit
351 new_user.is_new_user = not edit
348 # for users that didn's specify auth type, we use RhodeCode built in
352 # for users that didn's specify auth type, we use RhodeCode built in
349 from rhodecode.authentication.plugins import auth_rhodecode
353 from rhodecode.authentication.plugins import auth_rhodecode
350 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.uid
354 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.uid
351 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.uid
355 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.uid
352
356
353 try:
357 try:
354 new_user.username = username
358 new_user.username = username
355 new_user.admin = admin
359 new_user.admin = admin
356 new_user.email = email
360 new_user.email = email
357 new_user.active = active
361 new_user.active = active
358 new_user.extern_name = safe_unicode(extern_name)
362 new_user.extern_name = safe_unicode(extern_name)
359 new_user.extern_type = safe_unicode(extern_type)
363 new_user.extern_type = safe_unicode(extern_type)
360 new_user.name = firstname
364 new_user.name = firstname
361 new_user.lastname = lastname
365 new_user.lastname = lastname
362 new_user.description = description
366 new_user.description = description
363
367
364 # set password only if creating an user or password is changed
368 # set password only if creating an user or password is changed
365 if not edit or _password_change(new_user, password):
369 if not edit or _password_change(new_user, password):
366 reason = 'new password' if edit else 'new user'
370 reason = 'new password' if edit else 'new user'
367 log.debug('Updating password reason=>%s', reason)
371 log.debug('Updating password reason=>%s', reason)
368 new_user.password = get_crypt_password(password) if password else None
372 new_user.password = get_crypt_password(password) if password else None
369
373
370 if force_password_change:
374 if force_password_change:
371 new_user.update_userdata(force_password_change=True)
375 new_user.update_userdata(force_password_change=True)
372 if language:
376 if language:
373 new_user.update_userdata(language=language)
377 new_user.update_userdata(language=language)
374 new_user.update_userdata(notification_status=True)
378 new_user.update_userdata(notification_status=True)
375
379
376 self.sa.add(new_user)
380 self.sa.add(new_user)
377
381
378 if not edit and create_repo_group:
382 if not edit and create_repo_group:
379 RepoGroupModel().create_personal_repo_group(
383 RepoGroupModel().create_personal_repo_group(
380 new_user, commit_early=False)
384 new_user, commit_early=False)
381
385
382 if not edit:
386 if not edit:
383 # add the RSS token
387 # add the RSS token
384 self.add_auth_token(
388 self.add_auth_token(
385 user=username, lifetime_minutes=-1,
389 user=username, lifetime_minutes=-1,
386 role=self.auth_token_role.ROLE_FEED,
390 role=self.auth_token_role.ROLE_FEED,
387 description=u'Generated feed token')
391 description=u'Generated feed token')
388
392
389 kwargs = new_user.get_dict()
393 kwargs = new_user.get_dict()
390 # backward compat, require api_keys present
394 # backward compat, require api_keys present
391 kwargs['api_keys'] = kwargs['auth_tokens']
395 kwargs['api_keys'] = kwargs['auth_tokens']
392 hooks_base.create_user(created_by=cur_user, **kwargs)
396 hooks_base.create_user(created_by=cur_user, **kwargs)
393 events.trigger(events.UserPostCreate(user_data))
397 events.trigger(events.UserPostCreate(user_data))
394 return new_user
398 return new_user
395 except (DatabaseError,):
399 except (DatabaseError,):
396 log.error(traceback.format_exc())
400 log.error(traceback.format_exc())
397 raise
401 raise
398
402
399 def create_registration(self, form_data,
403 def create_registration(self, form_data,
400 extern_name='rhodecode', extern_type='rhodecode'):
404 extern_name='rhodecode', extern_type='rhodecode'):
401 from rhodecode.model.notification import NotificationModel
405 from rhodecode.model.notification import NotificationModel
402 from rhodecode.model.notification import EmailNotificationModel
406 from rhodecode.model.notification import EmailNotificationModel
403
407
404 try:
408 try:
405 form_data['admin'] = False
409 form_data['admin'] = False
406 form_data['extern_name'] = extern_name
410 form_data['extern_name'] = extern_name
407 form_data['extern_type'] = extern_type
411 form_data['extern_type'] = extern_type
408 new_user = self.create(form_data)
412 new_user = self.create(form_data)
409
413
410 self.sa.add(new_user)
414 self.sa.add(new_user)
411 self.sa.flush()
415 self.sa.flush()
412
416
413 user_data = new_user.get_dict()
417 user_data = new_user.get_dict()
414 user_data.update({
418 user_data.update({
415 'first_name': user_data.get('firstname'),
419 'first_name': user_data.get('firstname'),
416 'last_name': user_data.get('lastname'),
420 'last_name': user_data.get('lastname'),
417 })
421 })
418 kwargs = {
422 kwargs = {
419 # use SQLALCHEMY safe dump of user data
423 # use SQLALCHEMY safe dump of user data
420 'user': AttributeDict(user_data),
424 'user': AttributeDict(user_data),
421 'date': datetime.datetime.now()
425 'date': datetime.datetime.now()
422 }
426 }
423 notification_type = EmailNotificationModel.TYPE_REGISTRATION
427 notification_type = EmailNotificationModel.TYPE_REGISTRATION
424 # pre-generate the subject for notification itself
428 # pre-generate the subject for notification itself
425 (subject, _e, body_plaintext) = EmailNotificationModel().render_email(
429 (subject, _e, body_plaintext) = EmailNotificationModel().render_email(
426 notification_type, **kwargs)
430 notification_type, **kwargs)
427
431
428 # create notification objects, and emails
432 # create notification objects, and emails
429 NotificationModel().create(
433 NotificationModel().create(
430 created_by=new_user,
434 created_by=new_user,
431 notification_subject=subject,
435 notification_subject=subject,
432 notification_body=body_plaintext,
436 notification_body=body_plaintext,
433 notification_type=notification_type,
437 notification_type=notification_type,
434 recipients=None, # all admins
438 recipients=None, # all admins
435 email_kwargs=kwargs,
439 email_kwargs=kwargs,
436 )
440 )
437
441
438 return new_user
442 return new_user
439 except Exception:
443 except Exception:
440 log.error(traceback.format_exc())
444 log.error(traceback.format_exc())
441 raise
445 raise
442
446
443 def _handle_user_repos(self, username, repositories, handle_user,
447 def _handle_user_repos(self, username, repositories, handle_user,
444 handle_mode=None):
448 handle_mode=None):
445
449
446 left_overs = True
450 left_overs = True
447
451
448 from rhodecode.model.repo import RepoModel
452 from rhodecode.model.repo import RepoModel
449
453
450 if handle_mode == 'detach':
454 if handle_mode == 'detach':
451 for obj in repositories:
455 for obj in repositories:
452 obj.user = handle_user
456 obj.user = handle_user
453 # set description we know why we super admin now owns
457 # set description we know why we super admin now owns
454 # additional repositories that were orphaned !
458 # additional repositories that were orphaned !
455 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
459 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
456 self.sa.add(obj)
460 self.sa.add(obj)
457 left_overs = False
461 left_overs = False
458 elif handle_mode == 'delete':
462 elif handle_mode == 'delete':
459 for obj in repositories:
463 for obj in repositories:
460 RepoModel().delete(obj, forks='detach')
464 RepoModel().delete(obj, forks='detach')
461 left_overs = False
465 left_overs = False
462
466
463 # if nothing is done we have left overs left
467 # if nothing is done we have left overs left
464 return left_overs
468 return left_overs
465
469
466 def _handle_user_repo_groups(self, username, repository_groups, handle_user,
470 def _handle_user_repo_groups(self, username, repository_groups, handle_user,
467 handle_mode=None):
471 handle_mode=None):
468
472
469 left_overs = True
473 left_overs = True
470
474
471 from rhodecode.model.repo_group import RepoGroupModel
475 from rhodecode.model.repo_group import RepoGroupModel
472
476
473 if handle_mode == 'detach':
477 if handle_mode == 'detach':
474 for r in repository_groups:
478 for r in repository_groups:
475 r.user = handle_user
479 r.user = handle_user
476 # set description we know why we super admin now owns
480 # set description we know why we super admin now owns
477 # additional repositories that were orphaned !
481 # additional repositories that were orphaned !
478 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
482 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
479 r.personal = False
483 r.personal = False
480 self.sa.add(r)
484 self.sa.add(r)
481 left_overs = False
485 left_overs = False
482 elif handle_mode == 'delete':
486 elif handle_mode == 'delete':
483 for r in repository_groups:
487 for r in repository_groups:
484 RepoGroupModel().delete(r)
488 RepoGroupModel().delete(r)
485 left_overs = False
489 left_overs = False
486
490
487 # if nothing is done we have left overs left
491 # if nothing is done we have left overs left
488 return left_overs
492 return left_overs
489
493
490 def _handle_user_user_groups(self, username, user_groups, handle_user,
494 def _handle_user_user_groups(self, username, user_groups, handle_user,
491 handle_mode=None):
495 handle_mode=None):
492
496
493 left_overs = True
497 left_overs = True
494
498
495 from rhodecode.model.user_group import UserGroupModel
499 from rhodecode.model.user_group import UserGroupModel
496
500
497 if handle_mode == 'detach':
501 if handle_mode == 'detach':
498 for r in user_groups:
502 for r in user_groups:
499 for user_user_group_to_perm in r.user_user_group_to_perm:
503 for user_user_group_to_perm in r.user_user_group_to_perm:
500 if user_user_group_to_perm.user.username == username:
504 if user_user_group_to_perm.user.username == username:
501 user_user_group_to_perm.user = handle_user
505 user_user_group_to_perm.user = handle_user
502 r.user = handle_user
506 r.user = handle_user
503 # set description we know why we super admin now owns
507 # set description we know why we super admin now owns
504 # additional repositories that were orphaned !
508 # additional repositories that were orphaned !
505 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
509 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
506 self.sa.add(r)
510 self.sa.add(r)
507 left_overs = False
511 left_overs = False
508 elif handle_mode == 'delete':
512 elif handle_mode == 'delete':
509 for r in user_groups:
513 for r in user_groups:
510 UserGroupModel().delete(r)
514 UserGroupModel().delete(r)
511 left_overs = False
515 left_overs = False
512
516
513 # if nothing is done we have left overs left
517 # if nothing is done we have left overs left
514 return left_overs
518 return left_overs
515
519
516 def _handle_user_pull_requests(self, username, pull_requests, handle_user,
520 def _handle_user_pull_requests(self, username, pull_requests, handle_user,
517 handle_mode=None):
521 handle_mode=None):
518 left_overs = True
522 left_overs = True
519
523
520 from rhodecode.model.pull_request import PullRequestModel
524 from rhodecode.model.pull_request import PullRequestModel
521
525
522 if handle_mode == 'detach':
526 if handle_mode == 'detach':
523 for pr in pull_requests:
527 for pr in pull_requests:
524 pr.user_id = handle_user.user_id
528 pr.user_id = handle_user.user_id
525 # set description we know why we super admin now owns
529 # set description we know why we super admin now owns
526 # additional repositories that were orphaned !
530 # additional repositories that were orphaned !
527 pr.description += ' \n::detached pull requests from deleted user: %s' % (username,)
531 pr.description += ' \n::detached pull requests from deleted user: %s' % (username,)
528 self.sa.add(pr)
532 self.sa.add(pr)
529 left_overs = False
533 left_overs = False
530 elif handle_mode == 'delete':
534 elif handle_mode == 'delete':
531 for pr in pull_requests:
535 for pr in pull_requests:
532 PullRequestModel().delete(pr)
536 PullRequestModel().delete(pr)
533
537
534 left_overs = False
538 left_overs = False
535
539
536 # if nothing is done we have left overs left
540 # if nothing is done we have left overs left
537 return left_overs
541 return left_overs
538
542
539 def _handle_user_artifacts(self, username, artifacts, handle_user,
543 def _handle_user_artifacts(self, username, artifacts, handle_user,
540 handle_mode=None):
544 handle_mode=None):
541
545
542 left_overs = True
546 left_overs = True
543
547
544 if handle_mode == 'detach':
548 if handle_mode == 'detach':
545 for a in artifacts:
549 for a in artifacts:
546 a.upload_user = handle_user
550 a.upload_user = handle_user
547 # set description we know why we super admin now owns
551 # set description we know why we super admin now owns
548 # additional artifacts that were orphaned !
552 # additional artifacts that were orphaned !
549 a.file_description += ' \n::detached artifact from deleted user: %s' % (username,)
553 a.file_description += ' \n::detached artifact from deleted user: %s' % (username,)
550 self.sa.add(a)
554 self.sa.add(a)
551 left_overs = False
555 left_overs = False
552 elif handle_mode == 'delete':
556 elif handle_mode == 'delete':
553 from rhodecode.apps.file_store import utils as store_utils
557 from rhodecode.apps.file_store import utils as store_utils
554 request = get_current_request()
558 request = get_current_request()
555 storage = store_utils.get_file_storage(request.registry.settings)
559 storage = store_utils.get_file_storage(request.registry.settings)
556 for a in artifacts:
560 for a in artifacts:
557 file_uid = a.file_uid
561 file_uid = a.file_uid
558 storage.delete(file_uid)
562 storage.delete(file_uid)
559 self.sa.delete(a)
563 self.sa.delete(a)
560
564
561 left_overs = False
565 left_overs = False
562
566
563 # if nothing is done we have left overs left
567 # if nothing is done we have left overs left
564 return left_overs
568 return left_overs
565
569
566 def delete(self, user, cur_user=None, handle_repos=None,
570 def delete(self, user, cur_user=None, handle_repos=None,
567 handle_repo_groups=None, handle_user_groups=None,
571 handle_repo_groups=None, handle_user_groups=None,
568 handle_pull_requests=None, handle_artifacts=None, handle_new_owner=None):
572 handle_pull_requests=None, handle_artifacts=None, handle_new_owner=None):
569 from rhodecode.lib import hooks_base
573 from rhodecode.lib import hooks_base
570
574
571 if not cur_user:
575 if not cur_user:
572 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
576 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
573
577
574 user = self._get_user(user)
578 user = self._get_user(user)
575
579
576 try:
580 try:
577 if user.username == User.DEFAULT_USER:
581 if user.username == User.DEFAULT_USER:
578 raise DefaultUserException(
582 raise DefaultUserException(
579 u"You can't remove this user since it's"
583 u"You can't remove this user since it's"
580 u" crucial for entire application")
584 u" crucial for entire application")
581 handle_user = handle_new_owner or self.cls.get_first_super_admin()
585 handle_user = handle_new_owner or self.cls.get_first_super_admin()
582 log.debug('New detached objects owner %s', handle_user)
586 log.debug('New detached objects owner %s', handle_user)
583
587
584 left_overs = self._handle_user_repos(
588 left_overs = self._handle_user_repos(
585 user.username, user.repositories, handle_user, handle_repos)
589 user.username, user.repositories, handle_user, handle_repos)
586 if left_overs and user.repositories:
590 if left_overs and user.repositories:
587 repos = [x.repo_name for x in user.repositories]
591 repos = [x.repo_name for x in user.repositories]
588 raise UserOwnsReposException(
592 raise UserOwnsReposException(
589 u'user "%(username)s" still owns %(len_repos)s repositories and cannot be '
593 u'user "%(username)s" still owns %(len_repos)s repositories and cannot be '
590 u'removed. Switch owners or remove those repositories:%(list_repos)s'
594 u'removed. Switch owners or remove those repositories:%(list_repos)s'
591 % {'username': user.username, 'len_repos': len(repos),
595 % {'username': user.username, 'len_repos': len(repos),
592 'list_repos': ', '.join(repos)})
596 'list_repos': ', '.join(repos)})
593