##// END OF EJS Templates
users: moved get_users from RepoModel to UserModel.
marcink -
r1677:f2c95b1d default
parent child Browse files
Show More
@@ -1,236 +1,236 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import re
22 22 import logging
23 23
24 24 from pyramid.view import view_config
25 25
26 26 from rhodecode.apps._base import BaseAppView
27 27 from rhodecode.lib import helpers as h
28 28 from rhodecode.lib.auth import LoginRequired, NotAnonymous
29 29 from rhodecode.lib.index import searcher_from_config
30 30 from rhodecode.lib.utils2 import safe_unicode, str2bool
31 31 from rhodecode.model.db import func, Repository, RepoGroup
32 32 from rhodecode.model.repo import RepoModel
33 33 from rhodecode.model.scm import ScmModel
34 from rhodecode.model.user import UserModel
34 35 from rhodecode.model.user_group import UserGroupModel
35 36
36 37 log = logging.getLogger(__name__)
37 38
38 39
39 40 class HomeView(BaseAppView):
40 41
41 42 def load_default_context(self):
42 43 c = self._get_local_tmpl_context()
43 44 c.user = c.auth_user.get_instance()
44 45 self._register_global_c(c)
45 46 return c
46 47
47 48 @LoginRequired()
48 49 @view_config(
49 50 route_name='user_autocomplete_data', request_method='GET',
50 51 renderer='json_ext', xhr=True)
51 52 def user_autocomplete_data(self):
52 53 query = self.request.GET.get('query')
53 54 active = str2bool(self.request.GET.get('active') or True)
54 55 include_groups = str2bool(self.request.GET.get('user_groups'))
55 56
56 57 log.debug('generating user list, query:%s, active:%s, with_groups:%s',
57 58 query, active, include_groups)
58 59
59 repo_model = RepoModel()
60 _users = repo_model.get_users(
60 _users = UserModel().get_users(
61 61 name_contains=query, only_active=active)
62 62
63 63 if include_groups:
64 64 # extend with user groups
65 65 _user_groups = UserGroupModel().get_user_groups(
66 66 name_contains=query, only_active=active)
67 67 _users = _users + _user_groups
68 68
69 69 return {'suggestions': _users}
70 70
71 71 @LoginRequired()
72 72 @NotAnonymous()
73 73 @view_config(
74 74 route_name='user_group_autocomplete_data', request_method='GET',
75 75 renderer='json_ext', xhr=True)
76 76 def user_group_autocomplete_data(self):
77 77 query = self.request.GET.get('query')
78 78 active = str2bool(self.request.GET.get('active') or True)
79 79 log.debug('generating user group list, query:%s, active:%s',
80 80 query, active)
81 81
82 82 _user_groups = UserGroupModel().get_user_groups(
83 83 name_contains=query, only_active=active)
84 84 _user_groups = _user_groups
85 85
86 86 return {'suggestions': _user_groups}
87 87
88 88 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
89 89 query = Repository.query()\
90 90 .order_by(func.length(Repository.repo_name))\
91 91 .order_by(Repository.repo_name)
92 92
93 93 if repo_type:
94 94 query = query.filter(Repository.repo_type == repo_type)
95 95
96 96 if name_contains:
97 97 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
98 98 query = query.filter(
99 99 Repository.repo_name.ilike(ilike_expression))
100 100 query = query.limit(limit)
101 101
102 102 all_repos = query.all()
103 103 # permission checks are inside this function
104 104 repo_iter = ScmModel().get_repos(all_repos)
105 105 return [
106 106 {
107 107 'id': obj['name'],
108 108 'text': obj['name'],
109 109 'type': 'repo',
110 110 'obj': obj['dbrepo'],
111 111 'url': h.url('summary_home', repo_name=obj['name'])
112 112 }
113 113 for obj in repo_iter]
114 114
115 115 def _get_repo_group_list(self, name_contains=None, limit=20):
116 116 query = RepoGroup.query()\
117 117 .order_by(func.length(RepoGroup.group_name))\
118 118 .order_by(RepoGroup.group_name)
119 119
120 120 if name_contains:
121 121 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
122 122 query = query.filter(
123 123 RepoGroup.group_name.ilike(ilike_expression))
124 124 query = query.limit(limit)
125 125
126 126 all_groups = query.all()
127 127 repo_groups_iter = ScmModel().get_repo_groups(all_groups)
128 128 return [
129 129 {
130 130 'id': obj.group_name,
131 131 'text': obj.group_name,
132 132 'type': 'group',
133 133 'obj': {},
134 134 'url': h.url('repo_group_home', group_name=obj.group_name)
135 135 }
136 136 for obj in repo_groups_iter]
137 137
138 138 def _get_hash_commit_list(self, auth_user, hash_starts_with=None):
139 139 if not hash_starts_with or len(hash_starts_with) < 3:
140 140 return []
141 141
142 142 commit_hashes = re.compile('([0-9a-f]{2,40})').findall(hash_starts_with)
143 143
144 144 if len(commit_hashes) != 1:
145 145 return []
146 146
147 147 commit_hash_prefix = commit_hashes[0]
148 148
149 149 searcher = searcher_from_config(self.request.registry.settings)
150 150 result = searcher.search(
151 151 'commit_id:%s*' % commit_hash_prefix, 'commit', auth_user,
152 152 raise_on_exc=False)
153 153
154 154 return [
155 155 {
156 156 'id': entry['commit_id'],
157 157 'text': entry['commit_id'],
158 158 'type': 'commit',
159 159 'obj': {'repo': entry['repository']},
160 160 'url': h.url('changeset_home',
161 161 repo_name=entry['repository'],
162 162 revision=entry['commit_id'])
163 163 }
164 164 for entry in result['results']]
165 165
166 166 @LoginRequired()
167 167 @view_config(
168 168 route_name='repo_list_data', request_method='GET',
169 169 renderer='json_ext', xhr=True)
170 170 def repo_list_data(self):
171 171 _ = self.request.translate
172 172
173 173 query = self.request.GET.get('query')
174 174 repo_type = self.request.GET.get('repo_type')
175 175 log.debug('generating repo list, query:%s, repo_type:%s',
176 176 query, repo_type)
177 177
178 178 res = []
179 179 repos = self._get_repo_list(query, repo_type=repo_type)
180 180 if repos:
181 181 res.append({
182 182 'text': _('Repositories'),
183 183 'children': repos
184 184 })
185 185
186 186 data = {
187 187 'more': False,
188 188 'results': res
189 189 }
190 190 return data
191 191
192 192 @LoginRequired()
193 193 @view_config(
194 194 route_name='goto_switcher_data', request_method='GET',
195 195 renderer='json_ext', xhr=True)
196 196 def goto_switcher_data(self):
197 197 c = self.load_default_context()
198 198
199 199 _ = self.request.translate
200 200
201 201 query = self.request.GET.get('query')
202 202 log.debug('generating goto switcher list, query %s', query)
203 203
204 204 res = []
205 205 repo_groups = self._get_repo_group_list(query)
206 206 if repo_groups:
207 207 res.append({
208 208 'text': _('Groups'),
209 209 'children': repo_groups
210 210 })
211 211
212 212 repos = self._get_repo_list(query)
213 213 if repos:
214 214 res.append({
215 215 'text': _('Repositories'),
216 216 'children': repos
217 217 })
218 218
219 219 commits = self._get_hash_commit_list(c.auth_user, query)
220 220 if commits:
221 221 unique_repos = {}
222 222 for commit in commits:
223 223 unique_repos.setdefault(commit['obj']['repo'], []
224 224 ).append(commit)
225 225
226 226 for repo in unique_repos:
227 227 res.append({
228 228 'text': _('Commits in %(repo)s') % {'repo': repo},
229 229 'children': unique_repos[repo]
230 230 })
231 231
232 232 data = {
233 233 'more': False,
234 234 'results': res
235 235 }
236 236 return data
@@ -1,1036 +1,999 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Repository model for rhodecode
23 23 """
24 24
25 25 import logging
26 26 import os
27 27 import re
28 28 import shutil
29 29 import time
30 30 import traceback
31 31 from datetime import datetime, timedelta
32 32
33 from sqlalchemy.sql.expression import true, or_
34 33 from zope.cachedescriptors.property import Lazy as LazyProperty
35 34
36 35 from rhodecode import events
37 36 from rhodecode.lib import helpers as h
38 37 from rhodecode.lib.auth import HasUserGroupPermissionAny
39 38 from rhodecode.lib.caching_query import FromCache
40 39 from rhodecode.lib.exceptions import AttachedForksError
41 40 from rhodecode.lib.hooks_base import log_delete_repository
42 41 from rhodecode.lib.utils import make_db_config
43 42 from rhodecode.lib.utils2 import (
44 43 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
45 44 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
46 45 from rhodecode.lib.vcs.backends import get_backend
47 46 from rhodecode.model import BaseModel
48 47 from rhodecode.model.db import (
49 48 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
50 49 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
51 50 RepoGroup, RepositoryField)
52 51
53 52 from rhodecode.model.settings import VcsSettingsModel
54 53
55 54
56 55 log = logging.getLogger(__name__)
57 56
58 57
59 58 class RepoModel(BaseModel):
60 59
61 60 cls = Repository
62 61
63 62 def _get_user_group(self, users_group):
64 63 return self._get_instance(UserGroup, users_group,
65 64 callback=UserGroup.get_by_group_name)
66 65
67 66 def _get_repo_group(self, repo_group):
68 67 return self._get_instance(RepoGroup, repo_group,
69 68 callback=RepoGroup.get_by_group_name)
70 69
71 70 def _create_default_perms(self, repository, private):
72 71 # create default permission
73 72 default = 'repository.read'
74 73 def_user = User.get_default_user()
75 74 for p in def_user.user_perms:
76 75 if p.permission.permission_name.startswith('repository.'):
77 76 default = p.permission.permission_name
78 77 break
79 78
80 79 default_perm = 'repository.none' if private else default
81 80
82 81 repo_to_perm = UserRepoToPerm()
83 82 repo_to_perm.permission = Permission.get_by_key(default_perm)
84 83
85 84 repo_to_perm.repository = repository
86 85 repo_to_perm.user_id = def_user.user_id
87 86
88 87 return repo_to_perm
89 88
90 89 @LazyProperty
91 90 def repos_path(self):
92 91 """
93 92 Gets the repositories root path from database
94 93 """
95 94 settings_model = VcsSettingsModel(sa=self.sa)
96 95 return settings_model.get_repos_location()
97 96
98 97 def get(self, repo_id, cache=False):
99 98 repo = self.sa.query(Repository) \
100 99 .filter(Repository.repo_id == repo_id)
101 100
102 101 if cache:
103 102 repo = repo.options(FromCache("sql_cache_short",
104 103 "get_repo_%s" % repo_id))
105 104 return repo.scalar()
106 105
107 106 def get_repo(self, repository):
108 107 return self._get_repo(repository)
109 108
110 109 def get_by_repo_name(self, repo_name, cache=False):
111 110 repo = self.sa.query(Repository) \
112 111 .filter(Repository.repo_name == repo_name)
113 112
114 113 if cache:
115 114 repo = repo.options(FromCache("sql_cache_short",
116 115 "get_repo_%s" % repo_name))
117 116 return repo.scalar()
118 117
119 118 def _extract_id_from_repo_name(self, repo_name):
120 119 if repo_name.startswith('/'):
121 120 repo_name = repo_name.lstrip('/')
122 121 by_id_match = re.match(r'^_(\d{1,})', repo_name)
123 122 if by_id_match:
124 123 return by_id_match.groups()[0]
125 124
126 125 def get_repo_by_id(self, repo_name):
127 126 """
128 127 Extracts repo_name by id from special urls.
129 128 Example url is _11/repo_name
130 129
131 130 :param repo_name:
132 131 :return: repo object if matched else None
133 132 """
134 133 try:
135 134 _repo_id = self._extract_id_from_repo_name(repo_name)
136 135 if _repo_id:
137 136 return self.get(_repo_id)
138 137 except Exception:
139 138 log.exception('Failed to extract repo_name from URL')
140 139
141 140 return None
142 141
143 142 def get_repos_for_root(self, root, traverse=False):
144 143 if traverse:
145 144 like_expression = u'{}%'.format(safe_unicode(root))
146 145 repos = Repository.query().filter(
147 146 Repository.repo_name.like(like_expression)).all()
148 147 else:
149 148 if root and not isinstance(root, RepoGroup):
150 149 raise ValueError(
151 150 'Root must be an instance '
152 151 'of RepoGroup, got:{} instead'.format(type(root)))
153 152 repos = Repository.query().filter(Repository.group == root).all()
154 153 return repos
155 154
156 155 def get_url(self, repo):
157 156 return h.url('summary_home', repo_name=safe_str(repo.repo_name),
158 157 qualified=True)
159 158
160 def get_users(self, name_contains=None, limit=20, only_active=True):
161
162 # TODO: mikhail: move this method to the UserModel.
163 query = self.sa.query(User)
164 if only_active:
165 query = query.filter(User.active == true())
166
167 if name_contains:
168 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
169 query = query.filter(
170 or_(
171 User.name.ilike(ilike_expression),
172 User.lastname.ilike(ilike_expression),
173 User.username.ilike(ilike_expression)
174 )
175 )
176 query = query.limit(limit)
177 users = query.all()
178
179 _users = [
180 {
181 'id': user.user_id,
182 'first_name': user.name,
183 'last_name': user.lastname,
184 'username': user.username,
185 'email': user.email,
186 'icon_link': h.gravatar_url(user.email, 30),
187 'value_display': h.person(user),
188 'value': user.username,
189 'value_type': 'user',
190 'active': user.active,
191 }
192 for user in users
193 ]
194 return _users
195
196 159 @classmethod
197 160 def update_repoinfo(cls, repositories=None):
198 161 if not repositories:
199 162 repositories = Repository.getAll()
200 163 for repo in repositories:
201 164 repo.update_commit_cache()
202 165
203 166 def get_repos_as_dict(self, repo_list=None, admin=False,
204 167 super_user_actions=False):
205 168
206 169 from rhodecode.lib.utils import PartialRenderer
207 170 _render = PartialRenderer('data_table/_dt_elements.mako')
208 171 c = _render.c
209 172
210 173 def quick_menu(repo_name):
211 174 return _render('quick_menu', repo_name)
212 175
213 176 def repo_lnk(name, rtype, rstate, private, fork_of):
214 177 return _render('repo_name', name, rtype, rstate, private, fork_of,
215 178 short_name=not admin, admin=False)
216 179
217 180 def last_change(last_change):
218 181 if admin and isinstance(last_change, datetime) and not last_change.tzinfo:
219 182 last_change = last_change + timedelta(seconds=
220 183 (datetime.now() - datetime.utcnow()).seconds)
221 184 return _render("last_change", last_change)
222 185
223 186 def rss_lnk(repo_name):
224 187 return _render("rss", repo_name)
225 188
226 189 def atom_lnk(repo_name):
227 190 return _render("atom", repo_name)
228 191
229 192 def last_rev(repo_name, cs_cache):
230 193 return _render('revision', repo_name, cs_cache.get('revision'),
231 194 cs_cache.get('raw_id'), cs_cache.get('author'),
232 195 cs_cache.get('message'))
233 196
234 197 def desc(desc):
235 198 if c.visual.stylify_metatags:
236 199 desc = h.urlify_text(h.escaped_stylize(desc))
237 200 else:
238 201 desc = h.urlify_text(h.html_escape(desc))
239 202
240 203 return _render('repo_desc', desc)
241 204
242 205 def state(repo_state):
243 206 return _render("repo_state", repo_state)
244 207
245 208 def repo_actions(repo_name):
246 209 return _render('repo_actions', repo_name, super_user_actions)
247 210
248 211 def user_profile(username):
249 212 return _render('user_profile', username)
250 213
251 214 repos_data = []
252 215 for repo in repo_list:
253 216 cs_cache = repo.changeset_cache
254 217 row = {
255 218 "menu": quick_menu(repo.repo_name),
256 219
257 220 "name": repo_lnk(repo.repo_name, repo.repo_type,
258 221 repo.repo_state, repo.private, repo.fork),
259 222 "name_raw": repo.repo_name.lower(),
260 223
261 224 "last_change": last_change(repo.last_db_change),
262 225 "last_change_raw": datetime_to_time(repo.last_db_change),
263 226
264 227 "last_changeset": last_rev(repo.repo_name, cs_cache),
265 228 "last_changeset_raw": cs_cache.get('revision'),
266 229
267 230 "desc": desc(repo.description),
268 231 "owner": user_profile(repo.user.username),
269 232
270 233 "state": state(repo.repo_state),
271 234 "rss": rss_lnk(repo.repo_name),
272 235
273 236 "atom": atom_lnk(repo.repo_name),
274 237 }
275 238 if admin:
276 239 row.update({
277 240 "action": repo_actions(repo.repo_name),
278 241 })
279 242 repos_data.append(row)
280 243
281 244 return repos_data
282 245
283 246 def _get_defaults(self, repo_name):
284 247 """
285 248 Gets information about repository, and returns a dict for
286 249 usage in forms
287 250
288 251 :param repo_name:
289 252 """
290 253
291 254 repo_info = Repository.get_by_repo_name(repo_name)
292 255
293 256 if repo_info is None:
294 257 return None
295 258
296 259 defaults = repo_info.get_dict()
297 260 defaults['repo_name'] = repo_info.just_name
298 261
299 262 groups = repo_info.groups_with_parents
300 263 parent_group = groups[-1] if groups else None
301 264
302 265 # we use -1 as this is how in HTML, we mark an empty group
303 266 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
304 267
305 268 keys_to_process = (
306 269 {'k': 'repo_type', 'strip': False},
307 270 {'k': 'repo_enable_downloads', 'strip': True},
308 271 {'k': 'repo_description', 'strip': True},
309 272 {'k': 'repo_enable_locking', 'strip': True},
310 273 {'k': 'repo_landing_rev', 'strip': True},
311 274 {'k': 'clone_uri', 'strip': False},
312 275 {'k': 'repo_private', 'strip': True},
313 276 {'k': 'repo_enable_statistics', 'strip': True}
314 277 )
315 278
316 279 for item in keys_to_process:
317 280 attr = item['k']
318 281 if item['strip']:
319 282 attr = remove_prefix(item['k'], 'repo_')
320 283
321 284 val = defaults[attr]
322 285 if item['k'] == 'repo_landing_rev':
323 286 val = ':'.join(defaults[attr])
324 287 defaults[item['k']] = val
325 288 if item['k'] == 'clone_uri':
326 289 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
327 290
328 291 # fill owner
329 292 if repo_info.user:
330 293 defaults.update({'user': repo_info.user.username})
331 294 else:
332 295 replacement_user = User.get_first_super_admin().username
333 296 defaults.update({'user': replacement_user})
334 297
335 298 # fill repository users
336 299 for p in repo_info.repo_to_perm:
337 300 defaults.update({'u_perm_%s' % p.user.user_id:
338 301 p.permission.permission_name})
339 302
340 303 # fill repository groups
341 304 for p in repo_info.users_group_to_perm:
342 305 defaults.update({'g_perm_%s' % p.users_group.users_group_id:
343 306 p.permission.permission_name})
344 307
345 308 return defaults
346 309
347 310 def update(self, repo, **kwargs):
348 311 try:
349 312 cur_repo = self._get_repo(repo)
350 313 source_repo_name = cur_repo.repo_name
351 314 if 'user' in kwargs:
352 315 cur_repo.user = User.get_by_username(kwargs['user'])
353 316
354 317 if 'repo_group' in kwargs:
355 318 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
356 319 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
357 320
358 321 update_keys = [
359 322 (1, 'repo_description'),
360 323 (1, 'repo_landing_rev'),
361 324 (1, 'repo_private'),
362 325 (1, 'repo_enable_downloads'),
363 326 (1, 'repo_enable_locking'),
364 327 (1, 'repo_enable_statistics'),
365 328 (0, 'clone_uri'),
366 329 (0, 'fork_id')
367 330 ]
368 331 for strip, k in update_keys:
369 332 if k in kwargs:
370 333 val = kwargs[k]
371 334 if strip:
372 335 k = remove_prefix(k, 'repo_')
373 336 if k == 'clone_uri':
374 337 from rhodecode.model.validators import Missing
375 338 _change = kwargs.get('clone_uri_change')
376 339 if _change in [Missing, 'OLD']:
377 340 # we don't change the value, so use original one
378 341 val = cur_repo.clone_uri
379 342
380 343 setattr(cur_repo, k, val)
381 344
382 345 new_name = cur_repo.get_new_name(kwargs['repo_name'])
383 346 cur_repo.repo_name = new_name
384 347
385 348 # if private flag is set, reset default permission to NONE
386 349 if kwargs.get('repo_private'):
387 350 EMPTY_PERM = 'repository.none'
388 351 RepoModel().grant_user_permission(
389 352 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
390 353 )
391 354
392 355 # handle extra fields
393 356 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
394 357 kwargs):
395 358 k = RepositoryField.un_prefix_key(field)
396 359 ex_field = RepositoryField.get_by_key_name(
397 360 key=k, repo=cur_repo)
398 361 if ex_field:
399 362 ex_field.field_value = kwargs[field]
400 363 self.sa.add(ex_field)
401 364 self.sa.add(cur_repo)
402 365
403 366 if source_repo_name != new_name:
404 367 # rename repository
405 368 self._rename_filesystem_repo(
406 369 old=source_repo_name, new=new_name)
407 370
408 371 return cur_repo
409 372 except Exception:
410 373 log.error(traceback.format_exc())
411 374 raise
412 375
413 376 def _create_repo(self, repo_name, repo_type, description, owner,
414 377 private=False, clone_uri=None, repo_group=None,
415 378 landing_rev='rev:tip', fork_of=None,
416 379 copy_fork_permissions=False, enable_statistics=False,
417 380 enable_locking=False, enable_downloads=False,
418 381 copy_group_permissions=False,
419 382 state=Repository.STATE_PENDING):
420 383 """
421 384 Create repository inside database with PENDING state, this should be
422 385 only executed by create() repo. With exception of importing existing
423 386 repos
424 387 """
425 388 from rhodecode.model.scm import ScmModel
426 389
427 390 owner = self._get_user(owner)
428 391 fork_of = self._get_repo(fork_of)
429 392 repo_group = self._get_repo_group(safe_int(repo_group))
430 393
431 394 try:
432 395 repo_name = safe_unicode(repo_name)
433 396 description = safe_unicode(description)
434 397 # repo name is just a name of repository
435 398 # while repo_name_full is a full qualified name that is combined
436 399 # with name and path of group
437 400 repo_name_full = repo_name
438 401 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
439 402
440 403 new_repo = Repository()
441 404 new_repo.repo_state = state
442 405 new_repo.enable_statistics = False
443 406 new_repo.repo_name = repo_name_full
444 407 new_repo.repo_type = repo_type
445 408 new_repo.user = owner
446 409 new_repo.group = repo_group
447 410 new_repo.description = description or repo_name
448 411 new_repo.private = private
449 412 new_repo.clone_uri = clone_uri
450 413 new_repo.landing_rev = landing_rev
451 414
452 415 new_repo.enable_statistics = enable_statistics
453 416 new_repo.enable_locking = enable_locking
454 417 new_repo.enable_downloads = enable_downloads
455 418
456 419 if repo_group:
457 420 new_repo.enable_locking = repo_group.enable_locking
458 421
459 422 if fork_of:
460 423 parent_repo = fork_of
461 424 new_repo.fork = parent_repo
462 425
463 426 events.trigger(events.RepoPreCreateEvent(new_repo))
464 427
465 428 self.sa.add(new_repo)
466 429
467 430 EMPTY_PERM = 'repository.none'
468 431 if fork_of and copy_fork_permissions:
469 432 repo = fork_of
470 433 user_perms = UserRepoToPerm.query() \
471 434 .filter(UserRepoToPerm.repository == repo).all()
472 435 group_perms = UserGroupRepoToPerm.query() \
473 436 .filter(UserGroupRepoToPerm.repository == repo).all()
474 437
475 438 for perm in user_perms:
476 439 UserRepoToPerm.create(
477 440 perm.user, new_repo, perm.permission)
478 441
479 442 for perm in group_perms:
480 443 UserGroupRepoToPerm.create(
481 444 perm.users_group, new_repo, perm.permission)
482 445 # in case we copy permissions and also set this repo to private
483 446 # override the default user permission to make it a private
484 447 # repo
485 448 if private:
486 449 RepoModel(self.sa).grant_user_permission(
487 450 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
488 451
489 452 elif repo_group and copy_group_permissions:
490 453 user_perms = UserRepoGroupToPerm.query() \
491 454 .filter(UserRepoGroupToPerm.group == repo_group).all()
492 455
493 456 group_perms = UserGroupRepoGroupToPerm.query() \
494 457 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
495 458
496 459 for perm in user_perms:
497 460 perm_name = perm.permission.permission_name.replace(
498 461 'group.', 'repository.')
499 462 perm_obj = Permission.get_by_key(perm_name)
500 463 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
501 464
502 465 for perm in group_perms:
503 466 perm_name = perm.permission.permission_name.replace(
504 467 'group.', 'repository.')
505 468 perm_obj = Permission.get_by_key(perm_name)
506 469 UserGroupRepoToPerm.create(
507 470 perm.users_group, new_repo, perm_obj)
508 471
509 472 if private:
510 473 RepoModel(self.sa).grant_user_permission(
511 474 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
512 475
513 476 else:
514 477 perm_obj = self._create_default_perms(new_repo, private)
515 478 self.sa.add(perm_obj)
516 479
517 480 # now automatically start following this repository as owner
518 481 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
519 482 owner.user_id)
520 483
521 484 # we need to flush here, in order to check if database won't
522 485 # throw any exceptions, create filesystem dirs at the very end
523 486 self.sa.flush()
524 487 events.trigger(events.RepoCreateEvent(new_repo))
525 488 return new_repo
526 489
527 490 except Exception:
528 491 log.error(traceback.format_exc())
529 492 raise
530 493
531 494 def create(self, form_data, cur_user):
532 495 """
533 496 Create repository using celery tasks
534 497
535 498 :param form_data:
536 499 :param cur_user:
537 500 """
538 501 from rhodecode.lib.celerylib import tasks, run_task
539 502 return run_task(tasks.create_repo, form_data, cur_user)
540 503
541 504 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
542 505 perm_deletions=None, check_perms=True,
543 506 cur_user=None):
544 507 if not perm_additions:
545 508 perm_additions = []
546 509 if not perm_updates:
547 510 perm_updates = []
548 511 if not perm_deletions:
549 512 perm_deletions = []
550 513
551 514 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
552 515
553 516 # update permissions
554 517 for member_id, perm, member_type in perm_updates:
555 518 member_id = int(member_id)
556 519 if member_type == 'user':
557 520 # this updates also current one if found
558 521 self.grant_user_permission(
559 522 repo=repo, user=member_id, perm=perm)
560 523 else: # set for user group
561 524 # check if we have permissions to alter this usergroup
562 525 member_name = UserGroup.get(member_id).users_group_name
563 526 if not check_perms or HasUserGroupPermissionAny(
564 527 *req_perms)(member_name, user=cur_user):
565 528 self.grant_user_group_permission(
566 529 repo=repo, group_name=member_id, perm=perm)
567 530
568 531 # set new permissions
569 532 for member_id, perm, member_type in perm_additions:
570 533 member_id = int(member_id)
571 534 if member_type == 'user':
572 535 self.grant_user_permission(
573 536 repo=repo, user=member_id, perm=perm)
574 537 else: # set for user group
575 538 # check if we have permissions to alter this usergroup
576 539 member_name = UserGroup.get(member_id).users_group_name
577 540 if not check_perms or HasUserGroupPermissionAny(
578 541 *req_perms)(member_name, user=cur_user):
579 542 self.grant_user_group_permission(
580 543 repo=repo, group_name=member_id, perm=perm)
581 544
582 545 # delete permissions
583 546 for member_id, perm, member_type in perm_deletions:
584 547 member_id = int(member_id)
585 548 if member_type == 'user':
586 549 self.revoke_user_permission(repo=repo, user=member_id)
587 550 else: # set for user group
588 551 # check if we have permissions to alter this usergroup
589 552 member_name = UserGroup.get(member_id).users_group_name
590 553 if not check_perms or HasUserGroupPermissionAny(
591 554 *req_perms)(member_name, user=cur_user):
592 555 self.revoke_user_group_permission(
593 556 repo=repo, group_name=member_id)
594 557
595 558 def create_fork(self, form_data, cur_user):
596 559 """
597 560 Simple wrapper into executing celery task for fork creation
598 561
599 562 :param form_data:
600 563 :param cur_user:
601 564 """
602 565 from rhodecode.lib.celerylib import tasks, run_task
603 566 return run_task(tasks.create_repo_fork, form_data, cur_user)
604 567
605 568 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
606 569 """
607 570 Delete given repository, forks parameter defines what do do with
608 571 attached forks. Throws AttachedForksError if deleted repo has attached
609 572 forks
610 573
611 574 :param repo:
612 575 :param forks: str 'delete' or 'detach'
613 576 :param fs_remove: remove(archive) repo from filesystem
614 577 """
615 578 if not cur_user:
616 579 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
617 580 repo = self._get_repo(repo)
618 581 if repo:
619 582 if forks == 'detach':
620 583 for r in repo.forks:
621 584 r.fork = None
622 585 self.sa.add(r)
623 586 elif forks == 'delete':
624 587 for r in repo.forks:
625 588 self.delete(r, forks='delete')
626 589 elif [f for f in repo.forks]:
627 590 raise AttachedForksError()
628 591
629 592 old_repo_dict = repo.get_dict()
630 593 events.trigger(events.RepoPreDeleteEvent(repo))
631 594 try:
632 595 self.sa.delete(repo)
633 596 if fs_remove:
634 597 self._delete_filesystem_repo(repo)
635 598 else:
636 599 log.debug('skipping removal from filesystem')
637 600 old_repo_dict.update({
638 601 'deleted_by': cur_user,
639 602 'deleted_on': time.time(),
640 603 })
641 604 log_delete_repository(**old_repo_dict)
642 605 events.trigger(events.RepoDeleteEvent(repo))
643 606 except Exception:
644 607 log.error(traceback.format_exc())
645 608 raise
646 609
647 610 def grant_user_permission(self, repo, user, perm):
648 611 """
649 612 Grant permission for user on given repository, or update existing one
650 613 if found
651 614
652 615 :param repo: Instance of Repository, repository_id, or repository name
653 616 :param user: Instance of User, user_id or username
654 617 :param perm: Instance of Permission, or permission_name
655 618 """
656 619 user = self._get_user(user)
657 620 repo = self._get_repo(repo)
658 621 permission = self._get_perm(perm)
659 622
660 623 # check if we have that permission already
661 624 obj = self.sa.query(UserRepoToPerm) \
662 625 .filter(UserRepoToPerm.user == user) \
663 626 .filter(UserRepoToPerm.repository == repo) \
664 627 .scalar()
665 628 if obj is None:
666 629 # create new !
667 630 obj = UserRepoToPerm()
668 631 obj.repository = repo
669 632 obj.user = user
670 633 obj.permission = permission
671 634 self.sa.add(obj)
672 635 log.debug('Granted perm %s to %s on %s', perm, user, repo)
673 636 action_logger_generic(
674 637 'granted permission: {} to user: {} on repo: {}'.format(
675 638 perm, user, repo), namespace='security.repo')
676 639 return obj
677 640
678 641 def revoke_user_permission(self, repo, user):
679 642 """
680 643 Revoke permission for user on given repository
681 644
682 645 :param repo: Instance of Repository, repository_id, or repository name
683 646 :param user: Instance of User, user_id or username
684 647 """
685 648
686 649 user = self._get_user(user)
687 650 repo = self._get_repo(repo)
688 651
689 652 obj = self.sa.query(UserRepoToPerm) \
690 653 .filter(UserRepoToPerm.repository == repo) \
691 654 .filter(UserRepoToPerm.user == user) \
692 655 .scalar()
693 656 if obj:
694 657 self.sa.delete(obj)
695 658 log.debug('Revoked perm on %s on %s', repo, user)
696 659 action_logger_generic(
697 660 'revoked permission from user: {} on repo: {}'.format(
698 661 user, repo), namespace='security.repo')
699 662
700 663 def grant_user_group_permission(self, repo, group_name, perm):
701 664 """
702 665 Grant permission for user group on given repository, or update
703 666 existing one if found
704 667
705 668 :param repo: Instance of Repository, repository_id, or repository name
706 669 :param group_name: Instance of UserGroup, users_group_id,
707 670 or user group name
708 671 :param perm: Instance of Permission, or permission_name
709 672 """
710 673 repo = self._get_repo(repo)
711 674 group_name = self._get_user_group(group_name)
712 675 permission = self._get_perm(perm)
713 676
714 677 # check if we have that permission already
715 678 obj = self.sa.query(UserGroupRepoToPerm) \
716 679 .filter(UserGroupRepoToPerm.users_group == group_name) \
717 680 .filter(UserGroupRepoToPerm.repository == repo) \
718 681 .scalar()
719 682
720 683 if obj is None:
721 684 # create new
722 685 obj = UserGroupRepoToPerm()
723 686
724 687 obj.repository = repo
725 688 obj.users_group = group_name
726 689 obj.permission = permission
727 690 self.sa.add(obj)
728 691 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
729 692 action_logger_generic(
730 693 'granted permission: {} to usergroup: {} on repo: {}'.format(
731 694 perm, group_name, repo), namespace='security.repo')
732 695
733 696 return obj
734 697
735 698 def revoke_user_group_permission(self, repo, group_name):
736 699 """
737 700 Revoke permission for user group on given repository
738 701
739 702 :param repo: Instance of Repository, repository_id, or repository name
740 703 :param group_name: Instance of UserGroup, users_group_id,
741 704 or user group name
742 705 """
743 706 repo = self._get_repo(repo)
744 707 group_name = self._get_user_group(group_name)
745 708
746 709 obj = self.sa.query(UserGroupRepoToPerm) \
747 710 .filter(UserGroupRepoToPerm.repository == repo) \
748 711 .filter(UserGroupRepoToPerm.users_group == group_name) \
749 712 .scalar()
750 713 if obj:
751 714 self.sa.delete(obj)
752 715 log.debug('Revoked perm to %s on %s', repo, group_name)
753 716 action_logger_generic(
754 717 'revoked permission from usergroup: {} on repo: {}'.format(
755 718 group_name, repo), namespace='security.repo')
756 719
757 720 def delete_stats(self, repo_name):
758 721 """
759 722 removes stats for given repo
760 723
761 724 :param repo_name:
762 725 """
763 726 repo = self._get_repo(repo_name)
764 727 try:
765 728 obj = self.sa.query(Statistics) \
766 729 .filter(Statistics.repository == repo).scalar()
767 730 if obj:
768 731 self.sa.delete(obj)
769 732 except Exception:
770 733 log.error(traceback.format_exc())
771 734 raise
772 735
773 736 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
774 737 field_type='str', field_desc=''):
775 738
776 739 repo = self._get_repo(repo_name)
777 740
778 741 new_field = RepositoryField()
779 742 new_field.repository = repo
780 743 new_field.field_key = field_key
781 744 new_field.field_type = field_type # python type
782 745 new_field.field_value = field_value
783 746 new_field.field_desc = field_desc
784 747 new_field.field_label = field_label
785 748 self.sa.add(new_field)
786 749 return new_field
787 750
788 751 def delete_repo_field(self, repo_name, field_key):
789 752 repo = self._get_repo(repo_name)
790 753 field = RepositoryField.get_by_key_name(field_key, repo)
791 754 if field:
792 755 self.sa.delete(field)
793 756
794 757 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
795 758 clone_uri=None, repo_store_location=None,
796 759 use_global_config=False):
797 760 """
798 761 makes repository on filesystem. It's group aware means it'll create
799 762 a repository within a group, and alter the paths accordingly of
800 763 group location
801 764
802 765 :param repo_name:
803 766 :param alias:
804 767 :param parent:
805 768 :param clone_uri:
806 769 :param repo_store_location:
807 770 """
808 771 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
809 772 from rhodecode.model.scm import ScmModel
810 773
811 774 if Repository.NAME_SEP in repo_name:
812 775 raise ValueError(
813 776 'repo_name must not contain groups got `%s`' % repo_name)
814 777
815 778 if isinstance(repo_group, RepoGroup):
816 779 new_parent_path = os.sep.join(repo_group.full_path_splitted)
817 780 else:
818 781 new_parent_path = repo_group or ''
819 782
820 783 if repo_store_location:
821 784 _paths = [repo_store_location]
822 785 else:
823 786 _paths = [self.repos_path, new_parent_path, repo_name]
824 787 # we need to make it str for mercurial
825 788 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
826 789
827 790 # check if this path is not a repository
828 791 if is_valid_repo(repo_path, self.repos_path):
829 792 raise Exception('This path %s is a valid repository' % repo_path)
830 793
831 794 # check if this path is a group
832 795 if is_valid_repo_group(repo_path, self.repos_path):
833 796 raise Exception('This path %s is a valid group' % repo_path)
834 797
835 798 log.info('creating repo %s in %s from url: `%s`',
836 799 repo_name, safe_unicode(repo_path),
837 800 obfuscate_url_pw(clone_uri))
838 801
839 802 backend = get_backend(repo_type)
840 803
841 804 config_repo = None if use_global_config else repo_name
842 805 if config_repo and new_parent_path:
843 806 config_repo = Repository.NAME_SEP.join(
844 807 (new_parent_path, config_repo))
845 808 config = make_db_config(clear_session=False, repo=config_repo)
846 809 config.set('extensions', 'largefiles', '')
847 810
848 811 # patch and reset hooks section of UI config to not run any
849 812 # hooks on creating remote repo
850 813 config.clear_section('hooks')
851 814
852 815 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
853 816 if repo_type == 'git':
854 817 repo = backend(
855 818 repo_path, config=config, create=True, src_url=clone_uri,
856 819 bare=True)
857 820 else:
858 821 repo = backend(
859 822 repo_path, config=config, create=True, src_url=clone_uri)
860 823
861 824 ScmModel().install_hooks(repo, repo_type=repo_type)
862 825
863 826 log.debug('Created repo %s with %s backend',
864 827 safe_unicode(repo_name), safe_unicode(repo_type))
865 828 return repo
866 829
867 830 def _rename_filesystem_repo(self, old, new):
868 831 """
869 832 renames repository on filesystem
870 833
871 834 :param old: old name
872 835 :param new: new name
873 836 """
874 837 log.info('renaming repo from %s to %s', old, new)
875 838
876 839 old_path = os.path.join(self.repos_path, old)
877 840 new_path = os.path.join(self.repos_path, new)
878 841 if os.path.isdir(new_path):
879 842 raise Exception(
880 843 'Was trying to rename to already existing dir %s' % new_path
881 844 )
882 845 shutil.move(old_path, new_path)
883 846
884 847 def _delete_filesystem_repo(self, repo):
885 848 """
886 849 removes repo from filesystem, the removal is acctually made by
887 850 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
888 851 repository is no longer valid for rhodecode, can be undeleted later on
889 852 by reverting the renames on this repository
890 853
891 854 :param repo: repo object
892 855 """
893 856 rm_path = os.path.join(self.repos_path, repo.repo_name)
894 857 repo_group = repo.group
895 858 log.info("Removing repository %s", rm_path)
896 859 # disable hg/git internal that it doesn't get detected as repo
897 860 alias = repo.repo_type
898 861
899 862 config = make_db_config(clear_session=False)
900 863 config.set('extensions', 'largefiles', '')
901 864 bare = getattr(repo.scm_instance(config=config), 'bare', False)
902 865
903 866 # skip this for bare git repos
904 867 if not bare:
905 868 # disable VCS repo
906 869 vcs_path = os.path.join(rm_path, '.%s' % alias)
907 870 if os.path.exists(vcs_path):
908 871 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
909 872
910 873 _now = datetime.now()
911 874 _ms = str(_now.microsecond).rjust(6, '0')
912 875 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
913 876 repo.just_name)
914 877 if repo_group:
915 878 # if repository is in group, prefix the removal path with the group
916 879 args = repo_group.full_path_splitted + [_d]
917 880 _d = os.path.join(*args)
918 881
919 882 if os.path.isdir(rm_path):
920 883 shutil.move(rm_path, os.path.join(self.repos_path, _d))
921 884
922 885
923 886 class ReadmeFinder:
924 887 """
925 888 Utility which knows how to find a readme for a specific commit.
926 889
927 890 The main idea is that this is a configurable algorithm. When creating an
928 891 instance you can define parameters, currently only the `default_renderer`.
929 892 Based on this configuration the method :meth:`search` behaves slightly
930 893 different.
931 894 """
932 895
933 896 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
934 897 path_re = re.compile(r'^docs?', re.IGNORECASE)
935 898
936 899 default_priorities = {
937 900 None: 0,
938 901 '.text': 2,
939 902 '.txt': 3,
940 903 '.rst': 1,
941 904 '.rest': 2,
942 905 '.md': 1,
943 906 '.mkdn': 2,
944 907 '.mdown': 3,
945 908 '.markdown': 4,
946 909 }
947 910
948 911 path_priority = {
949 912 'doc': 0,
950 913 'docs': 1,
951 914 }
952 915
953 916 FALLBACK_PRIORITY = 99
954 917
955 918 RENDERER_TO_EXTENSION = {
956 919 'rst': ['.rst', '.rest'],
957 920 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
958 921 }
959 922
960 923 def __init__(self, default_renderer=None):
961 924 self._default_renderer = default_renderer
962 925 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
963 926 default_renderer, [])
964 927
965 928 def search(self, commit, path='/'):
966 929 """
967 930 Find a readme in the given `commit`.
968 931 """
969 932 nodes = commit.get_nodes(path)
970 933 matches = self._match_readmes(nodes)
971 934 matches = self._sort_according_to_priority(matches)
972 935 if matches:
973 936 return matches[0].node
974 937
975 938 paths = self._match_paths(nodes)
976 939 paths = self._sort_paths_according_to_priority(paths)
977 940 for path in paths:
978 941 match = self.search(commit, path=path)
979 942 if match:
980 943 return match
981 944
982 945 return None
983 946
984 947 def _match_readmes(self, nodes):
985 948 for node in nodes:
986 949 if not node.is_file():
987 950 continue
988 951 path = node.path.rsplit('/', 1)[-1]
989 952 match = self.readme_re.match(path)
990 953 if match:
991 954 extension = match.group(1)
992 955 yield ReadmeMatch(node, match, self._priority(extension))
993 956
994 957 def _match_paths(self, nodes):
995 958 for node in nodes:
996 959 if not node.is_dir():
997 960 continue
998 961 match = self.path_re.match(node.path)
999 962 if match:
1000 963 yield node.path
1001 964
1002 965 def _priority(self, extension):
1003 966 renderer_priority = (
1004 967 0 if extension in self._renderer_extensions else 1)
1005 968 extension_priority = self.default_priorities.get(
1006 969 extension, self.FALLBACK_PRIORITY)
1007 970 return (renderer_priority, extension_priority)
1008 971
1009 972 def _sort_according_to_priority(self, matches):
1010 973
1011 974 def priority_and_path(match):
1012 975 return (match.priority, match.path)
1013 976
1014 977 return sorted(matches, key=priority_and_path)
1015 978
1016 979 def _sort_paths_according_to_priority(self, paths):
1017 980
1018 981 def priority_and_path(path):
1019 982 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1020 983
1021 984 return sorted(paths, key=priority_and_path)
1022 985
1023 986
1024 987 class ReadmeMatch:
1025 988
1026 989 def __init__(self, node, match, priority):
1027 990 self.node = node
1028 991 self._match = match
1029 992 self.priority = priority
1030 993
1031 994 @property
1032 995 def path(self):
1033 996 return self.node.path
1034 997
1035 998 def __repr__(self):
1036 999 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
@@ -1,861 +1,897 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 users model for RhodeCode
23 23 """
24 24
25 25 import logging
26 26 import traceback
27 27
28 28 import datetime
29 29 from pylons.i18n.translation import _
30 30
31 31 import ipaddress
32 32 from sqlalchemy.exc import DatabaseError
33 33 from sqlalchemy.sql.expression import true, false
34 34
35 35 from rhodecode import events
36 36 from rhodecode.lib.user_log_filter import user_log_filter
37 37 from rhodecode.lib.utils2 import (
38 38 safe_unicode, get_current_rhodecode_user, action_logger_generic,
39 39 AttributeDict, str2bool)
40 40 from rhodecode.lib.caching_query import FromCache
41 41 from rhodecode.model import BaseModel
42 42 from rhodecode.model.auth_token import AuthTokenModel
43 43 from rhodecode.model.db import (
44 44 or_, joinedload, User, UserToPerm, UserEmailMap, UserIpMap, UserLog)
45 45 from rhodecode.lib.exceptions import (
46 46 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
47 47 UserOwnsUserGroupsException, NotAllowedToCreateUserError)
48 48 from rhodecode.model.meta import Session
49 49 from rhodecode.model.repo_group import RepoGroupModel
50 50
51 51
52 52 log = logging.getLogger(__name__)
53 53
54 54
55 55 class UserModel(BaseModel):
56 56 cls = User
57 57
58 58 def get(self, user_id, cache=False):
59 59 user = self.sa.query(User)
60 60 if cache:
61 61 user = user.options(FromCache("sql_cache_short",
62 62 "get_user_%s" % user_id))
63 63 return user.get(user_id)
64 64
65 65 def get_user(self, user):
66 66 return self._get_user(user)
67 67
68 def get_users(self, name_contains=None, limit=20, only_active=True):
69 import rhodecode.lib.helpers as h
70
71 query = self.sa.query(User)
72 if only_active:
73 query = query.filter(User.active == true())
74
75 if name_contains:
76 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
77 query = query.filter(
78 or_(
79 User.name.ilike(ilike_expression),
80 User.lastname.ilike(ilike_expression),
81 User.username.ilike(ilike_expression)
82 )
83 )
84 query = query.limit(limit)
85 users = query.all()
86
87 _users = [
88 {
89 'id': user.user_id,
90 'first_name': user.name,
91 'last_name': user.lastname,
92 'username': user.username,
93 'email': user.email,
94 'icon_link': h.gravatar_url(user.email, 30),
95 'value_display': h.person(user),
96 'value': user.username,
97 'value_type': 'user',
98 'active': user.active,
99 }
100 for user in users
101 ]
102 return _users
103
68 104 def get_by_username(self, username, cache=False, case_insensitive=False):
69 105
70 106 if case_insensitive:
71 107 user = self.sa.query(User).filter(User.username.ilike(username))
72 108 else:
73 109 user = self.sa.query(User)\
74 110 .filter(User.username == username)
75 111 if cache:
76 112 user = user.options(FromCache("sql_cache_short",
77 113 "get_user_%s" % username))
78 114 return user.scalar()
79 115
80 116 def get_by_email(self, email, cache=False, case_insensitive=False):
81 117 return User.get_by_email(email, case_insensitive, cache)
82 118
83 119 def get_by_auth_token(self, auth_token, cache=False):
84 120 return User.get_by_auth_token(auth_token, cache)
85 121
86 122 def get_active_user_count(self, cache=False):
87 123 return User.query().filter(
88 124 User.active == True).filter(
89 125 User.username != User.DEFAULT_USER).count()
90 126
91 127 def create(self, form_data, cur_user=None):
92 128 if not cur_user:
93 129 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
94 130
95 131 user_data = {
96 132 'username': form_data['username'],
97 133 'password': form_data['password'],
98 134 'email': form_data['email'],
99 135 'firstname': form_data['firstname'],
100 136 'lastname': form_data['lastname'],
101 137 'active': form_data['active'],
102 138 'extern_type': form_data['extern_type'],
103 139 'extern_name': form_data['extern_name'],
104 140 'admin': False,
105 141 'cur_user': cur_user
106 142 }
107 143
108 144 if 'create_repo_group' in form_data:
109 145 user_data['create_repo_group'] = str2bool(
110 146 form_data.get('create_repo_group'))
111 147
112 148 try:
113 149 if form_data.get('password_change'):
114 150 user_data['force_password_change'] = True
115 151 return UserModel().create_or_update(**user_data)
116 152 except Exception:
117 153 log.error(traceback.format_exc())
118 154 raise
119 155
120 156 def update_user(self, user, skip_attrs=None, **kwargs):
121 157 from rhodecode.lib.auth import get_crypt_password
122 158
123 159 user = self._get_user(user)
124 160 if user.username == User.DEFAULT_USER:
125 161 raise DefaultUserException(
126 162 _("You can't Edit this user since it's"
127 163 " crucial for entire application"))
128 164
129 165 # first store only defaults
130 166 user_attrs = {
131 167 'updating_user_id': user.user_id,
132 168 'username': user.username,
133 169 'password': user.password,
134 170 'email': user.email,
135 171 'firstname': user.name,
136 172 'lastname': user.lastname,
137 173 'active': user.active,
138 174 'admin': user.admin,
139 175 'extern_name': user.extern_name,
140 176 'extern_type': user.extern_type,
141 177 'language': user.user_data.get('language')
142 178 }
143 179
144 180 # in case there's new_password, that comes from form, use it to
145 181 # store password
146 182 if kwargs.get('new_password'):
147 183 kwargs['password'] = kwargs['new_password']
148 184
149 185 # cleanups, my_account password change form
150 186 kwargs.pop('current_password', None)
151 187 kwargs.pop('new_password', None)
152 188
153 189 # cleanups, user edit password change form
154 190 kwargs.pop('password_confirmation', None)
155 191 kwargs.pop('password_change', None)
156 192
157 193 # create repo group on user creation
158 194 kwargs.pop('create_repo_group', None)
159 195
160 196 # legacy forms send name, which is the firstname
161 197 firstname = kwargs.pop('name', None)
162 198 if firstname:
163 199 kwargs['firstname'] = firstname
164 200
165 201 for k, v in kwargs.items():
166 202 # skip if we don't want to update this
167 203 if skip_attrs and k in skip_attrs:
168 204 continue
169 205
170 206 user_attrs[k] = v
171 207
172 208 try:
173 209 return self.create_or_update(**user_attrs)
174 210 except Exception:
175 211 log.error(traceback.format_exc())
176 212 raise
177 213
178 214 def create_or_update(
179 215 self, username, password, email, firstname='', lastname='',
180 216 active=True, admin=False, extern_type=None, extern_name=None,
181 217 cur_user=None, plugin=None, force_password_change=False,
182 218 allow_to_create_user=True, create_repo_group=None,
183 219 updating_user_id=None, language=None, strict_creation_check=True):
184 220 """
185 221 Creates a new instance if not found, or updates current one
186 222
187 223 :param username:
188 224 :param password:
189 225 :param email:
190 226 :param firstname:
191 227 :param lastname:
192 228 :param active:
193 229 :param admin:
194 230 :param extern_type:
195 231 :param extern_name:
196 232 :param cur_user:
197 233 :param plugin: optional plugin this method was called from
198 234 :param force_password_change: toggles new or existing user flag
199 235 for password change
200 236 :param allow_to_create_user: Defines if the method can actually create
201 237 new users
202 238 :param create_repo_group: Defines if the method should also
203 239 create an repo group with user name, and owner
204 240 :param updating_user_id: if we set it up this is the user we want to
205 241 update this allows to editing username.
206 242 :param language: language of user from interface.
207 243
208 244 :returns: new User object with injected `is_new_user` attribute.
209 245 """
210 246 if not cur_user:
211 247 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
212 248
213 249 from rhodecode.lib.auth import (
214 250 get_crypt_password, check_password, generate_auth_token)
215 251 from rhodecode.lib.hooks_base import (
216 252 log_create_user, check_allowed_create_user)
217 253
218 254 def _password_change(new_user, password):
219 255 # empty password
220 256 if not new_user.password:
221 257 return False
222 258
223 259 # password check is only needed for RhodeCode internal auth calls
224 260 # in case it's a plugin we don't care
225 261 if not plugin:
226 262
227 263 # first check if we gave crypted password back, and if it
228 264 # matches it's not password change
229 265 if new_user.password == password:
230 266 return False
231 267
232 268 password_match = check_password(password, new_user.password)
233 269 if not password_match:
234 270 return True
235 271
236 272 return False
237 273
238 274 # read settings on default personal repo group creation
239 275 if create_repo_group is None:
240 276 default_create_repo_group = RepoGroupModel()\
241 277 .get_default_create_personal_repo_group()
242 278 create_repo_group = default_create_repo_group
243 279
244 280 user_data = {
245 281 'username': username,
246 282 'password': password,
247 283 'email': email,
248 284 'firstname': firstname,
249 285 'lastname': lastname,
250 286 'active': active,
251 287 'admin': admin
252 288 }
253 289
254 290 if updating_user_id:
255 291 log.debug('Checking for existing account in RhodeCode '
256 292 'database with user_id `%s` ' % (updating_user_id,))
257 293 user = User.get(updating_user_id)
258 294 else:
259 295 log.debug('Checking for existing account in RhodeCode '
260 296 'database with username `%s` ' % (username,))
261 297 user = User.get_by_username(username, case_insensitive=True)
262 298
263 299 if user is None:
264 300 # we check internal flag if this method is actually allowed to
265 301 # create new user
266 302 if not allow_to_create_user:
267 303 msg = ('Method wants to create new user, but it is not '
268 304 'allowed to do so')
269 305 log.warning(msg)
270 306 raise NotAllowedToCreateUserError(msg)
271 307
272 308 log.debug('Creating new user %s', username)
273 309
274 310 # only if we create user that is active
275 311 new_active_user = active
276 312 if new_active_user and strict_creation_check:
277 313 # raises UserCreationError if it's not allowed for any reason to
278 314 # create new active user, this also executes pre-create hooks
279 315 check_allowed_create_user(user_data, cur_user, strict_check=True)
280 316 events.trigger(events.UserPreCreate(user_data))
281 317 new_user = User()
282 318 edit = False
283 319 else:
284 320 log.debug('updating user %s', username)
285 321 events.trigger(events.UserPreUpdate(user, user_data))
286 322 new_user = user
287 323 edit = True
288 324
289 325 # we're not allowed to edit default user
290 326 if user.username == User.DEFAULT_USER:
291 327 raise DefaultUserException(
292 328 _("You can't edit this user (`%(username)s`) since it's "
293 329 "crucial for entire application") % {'username': user.username})
294 330
295 331 # inject special attribute that will tell us if User is new or old
296 332 new_user.is_new_user = not edit
297 333 # for users that didn's specify auth type, we use RhodeCode built in
298 334 from rhodecode.authentication.plugins import auth_rhodecode
299 335 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.name
300 336 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.name
301 337
302 338 try:
303 339 new_user.username = username
304 340 new_user.admin = admin
305 341 new_user.email = email
306 342 new_user.active = active
307 343 new_user.extern_name = safe_unicode(extern_name)
308 344 new_user.extern_type = safe_unicode(extern_type)
309 345 new_user.name = firstname
310 346 new_user.lastname = lastname
311 347
312 348 # set password only if creating an user or password is changed
313 349 if not edit or _password_change(new_user, password):
314 350 reason = 'new password' if edit else 'new user'
315 351 log.debug('Updating password reason=>%s', reason)
316 352 new_user.password = get_crypt_password(password) if password else None
317 353
318 354 if force_password_change:
319 355 new_user.update_userdata(force_password_change=True)
320 356 if language:
321 357 new_user.update_userdata(language=language)
322 358 new_user.update_userdata(notification_status=True)
323 359
324 360 self.sa.add(new_user)
325 361
326 362 if not edit and create_repo_group:
327 363 RepoGroupModel().create_personal_repo_group(
328 364 new_user, commit_early=False)
329 365
330 366 if not edit:
331 367 # add the RSS token
332 368 AuthTokenModel().create(username,
333 369 description='Generated feed token',
334 370 role=AuthTokenModel.cls.ROLE_FEED)
335 371 log_create_user(created_by=cur_user, **new_user.get_dict())
336 372 events.trigger(events.UserPostCreate(user_data))
337 373 return new_user
338 374 except (DatabaseError,):
339 375 log.error(traceback.format_exc())
340 376 raise
341 377
342 378 def create_registration(self, form_data):
343 379 from rhodecode.model.notification import NotificationModel
344 380 from rhodecode.model.notification import EmailNotificationModel
345 381
346 382 try:
347 383 form_data['admin'] = False
348 384 form_data['extern_name'] = 'rhodecode'
349 385 form_data['extern_type'] = 'rhodecode'
350 386 new_user = self.create(form_data)
351 387
352 388 self.sa.add(new_user)
353 389 self.sa.flush()
354 390
355 391 user_data = new_user.get_dict()
356 392 kwargs = {
357 393 # use SQLALCHEMY safe dump of user data
358 394 'user': AttributeDict(user_data),
359 395 'date': datetime.datetime.now()
360 396 }
361 397 notification_type = EmailNotificationModel.TYPE_REGISTRATION
362 398 # pre-generate the subject for notification itself
363 399 (subject,
364 400 _h, _e, # we don't care about those
365 401 body_plaintext) = EmailNotificationModel().render_email(
366 402 notification_type, **kwargs)
367 403
368 404 # create notification objects, and emails
369 405 NotificationModel().create(
370 406 created_by=new_user,
371 407 notification_subject=subject,
372 408 notification_body=body_plaintext,
373 409 notification_type=notification_type,
374 410 recipients=None, # all admins
375 411 email_kwargs=kwargs,
376 412 )
377 413
378 414 return new_user
379 415 except Exception:
380 416 log.error(traceback.format_exc())
381 417 raise
382 418
383 419 def _handle_user_repos(self, username, repositories, handle_mode=None):
384 420 _superadmin = self.cls.get_first_super_admin()
385 421 left_overs = True
386 422
387 423 from rhodecode.model.repo import RepoModel
388 424
389 425 if handle_mode == 'detach':
390 426 for obj in repositories:
391 427 obj.user = _superadmin
392 428 # set description we know why we super admin now owns
393 429 # additional repositories that were orphaned !
394 430 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
395 431 self.sa.add(obj)
396 432 left_overs = False
397 433 elif handle_mode == 'delete':
398 434 for obj in repositories:
399 435 RepoModel().delete(obj, forks='detach')
400 436 left_overs = False
401 437
402 438 # if nothing is done we have left overs left
403 439 return left_overs
404 440
405 441 def _handle_user_repo_groups(self, username, repository_groups,
406 442 handle_mode=None):
407 443 _superadmin = self.cls.get_first_super_admin()
408 444 left_overs = True
409 445
410 446 from rhodecode.model.repo_group import RepoGroupModel
411 447
412 448 if handle_mode == 'detach':
413 449 for r in repository_groups:
414 450 r.user = _superadmin
415 451 # set description we know why we super admin now owns
416 452 # additional repositories that were orphaned !
417 453 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
418 454 self.sa.add(r)
419 455 left_overs = False
420 456 elif handle_mode == 'delete':
421 457 for r in repository_groups:
422 458 RepoGroupModel().delete(r)
423 459 left_overs = False
424 460
425 461 # if nothing is done we have left overs left
426 462 return left_overs
427 463
428 464 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
429 465 _superadmin = self.cls.get_first_super_admin()
430 466 left_overs = True
431 467
432 468 from rhodecode.model.user_group import UserGroupModel
433 469
434 470 if handle_mode == 'detach':
435 471 for r in user_groups:
436 472 for user_user_group_to_perm in r.user_user_group_to_perm:
437 473 if user_user_group_to_perm.user.username == username:
438 474 user_user_group_to_perm.user = _superadmin
439 475 r.user = _superadmin
440 476 # set description we know why we super admin now owns
441 477 # additional repositories that were orphaned !
442 478 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
443 479 self.sa.add(r)
444 480 left_overs = False
445 481 elif handle_mode == 'delete':
446 482 for r in user_groups:
447 483 UserGroupModel().delete(r)
448 484 left_overs = False
449 485
450 486 # if nothing is done we have left overs left
451 487 return left_overs
452 488
453 489 def delete(self, user, cur_user=None, handle_repos=None,
454 490 handle_repo_groups=None, handle_user_groups=None):
455 491 if not cur_user:
456 492 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
457 493 user = self._get_user(user)
458 494
459 495 try:
460 496 if user.username == User.DEFAULT_USER:
461 497 raise DefaultUserException(
462 498 _(u"You can't remove this user since it's"
463 499 u" crucial for entire application"))
464 500
465 501 left_overs = self._handle_user_repos(
466 502 user.username, user.repositories, handle_repos)
467 503 if left_overs and user.repositories:
468 504 repos = [x.repo_name for x in user.repositories]
469 505 raise UserOwnsReposException(
470 506 _(u'user "%s" still owns %s repositories and cannot be '
471 507 u'removed. Switch owners or remove those repositories:%s')
472 508 % (user.username, len(repos), ', '.join(repos)))
473 509
474 510 left_overs = self._handle_user_repo_groups(
475 511 user.username, user.repository_groups, handle_repo_groups)
476 512 if left_overs and user.repository_groups:
477 513 repo_groups = [x.group_name for x in user.repository_groups]
478 514 raise UserOwnsRepoGroupsException(
479 515 _(u'user "%s" still owns %s repository groups and cannot be '
480 516 u'removed. Switch owners or remove those repository groups:%s')
481 517 % (user.username, len(repo_groups), ', '.join(repo_groups)))
482 518
483 519 left_overs = self._handle_user_user_groups(
484 520 user.username, user.user_groups, handle_user_groups)
485 521 if left_overs and user.user_groups:
486 522 user_groups = [x.users_group_name for x in user.user_groups]
487 523 raise UserOwnsUserGroupsException(
488 524 _(u'user "%s" still owns %s user groups and cannot be '
489 525 u'removed. Switch owners or remove those user groups:%s')
490 526 % (user.username, len(user_groups), ', '.join(user_groups)))
491 527
492 528 # we might change the user data with detach/delete, make sure
493 529 # the object is marked as expired before actually deleting !
494 530 self.sa.expire(user)
495 531 self.sa.delete(user)
496 532 from rhodecode.lib.hooks_base import log_delete_user
497 533 log_delete_user(deleted_by=cur_user, **user.get_dict())
498 534 except Exception:
499 535 log.error(traceback.format_exc())
500 536 raise
501 537
502 538 def reset_password_link(self, data, pwd_reset_url):
503 539 from rhodecode.lib.celerylib import tasks, run_task
504 540 from rhodecode.model.notification import EmailNotificationModel
505 541 user_email = data['email']
506 542 try:
507 543 user = User.get_by_email(user_email)
508 544 if user:
509 545 log.debug('password reset user found %s', user)
510 546
511 547 email_kwargs = {
512 548 'password_reset_url': pwd_reset_url,
513 549 'user': user,
514 550 'email': user_email,
515 551 'date': datetime.datetime.now()
516 552 }
517 553
518 554 (subject, headers, email_body,
519 555 email_body_plaintext) = EmailNotificationModel().render_email(
520 556 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
521 557
522 558 recipients = [user_email]
523 559
524 560 action_logger_generic(
525 561 'sending password reset email to user: {}'.format(
526 562 user), namespace='security.password_reset')
527 563
528 564 run_task(tasks.send_email, recipients, subject,
529 565 email_body_plaintext, email_body)
530 566
531 567 else:
532 568 log.debug("password reset email %s not found", user_email)
533 569 except Exception:
534 570 log.error(traceback.format_exc())
535 571 return False
536 572
537 573 return True
538 574
539 575 def reset_password(self, data):
540 576 from rhodecode.lib.celerylib import tasks, run_task
541 577 from rhodecode.model.notification import EmailNotificationModel
542 578 from rhodecode.lib import auth
543 579 user_email = data['email']
544 580 pre_db = True
545 581 try:
546 582 user = User.get_by_email(user_email)
547 583 new_passwd = auth.PasswordGenerator().gen_password(
548 584 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
549 585 if user:
550 586 user.password = auth.get_crypt_password(new_passwd)
551 587 # also force this user to reset his password !
552 588 user.update_userdata(force_password_change=True)
553 589
554 590 Session().add(user)
555 591
556 592 # now delete the token in question
557 593 UserApiKeys = AuthTokenModel.cls
558 594 UserApiKeys().query().filter(
559 595 UserApiKeys.api_key == data['token']).delete()
560 596
561 597 Session().commit()
562 598 log.info('successfully reset password for `%s`', user_email)
563 599
564 600 if new_passwd is None:
565 601 raise Exception('unable to generate new password')
566 602
567 603 pre_db = False
568 604
569 605 email_kwargs = {
570 606 'new_password': new_passwd,
571 607 'user': user,
572 608 'email': user_email,
573 609 'date': datetime.datetime.now()
574 610 }
575 611
576 612 (subject, headers, email_body,
577 613 email_body_plaintext) = EmailNotificationModel().render_email(
578 614 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
579 615 **email_kwargs)
580 616
581 617 recipients = [user_email]
582 618
583 619 action_logger_generic(
584 620 'sent new password to user: {} with email: {}'.format(
585 621 user, user_email), namespace='security.password_reset')
586 622
587 623 run_task(tasks.send_email, recipients, subject,
588 624 email_body_plaintext, email_body)
589 625
590 626 except Exception:
591 627 log.error('Failed to update user password')
592 628 log.error(traceback.format_exc())
593 629 if pre_db:
594 630 # we rollback only if local db stuff fails. If it goes into
595 631 # run_task, we're pass rollback state this wouldn't work then
596 632 Session().rollback()
597 633
598 634 return True
599 635
600 636 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
601 637 """
602 638 Fetches auth_user by user_id,or api_key if present.
603 639 Fills auth_user attributes with those taken from database.
604 640 Additionally set's is_authenitated if lookup fails
605 641 present in database
606 642
607 643 :param auth_user: instance of user to set attributes
608 644 :param user_id: user id to fetch by
609 645 :param api_key: api key to fetch by
610 646 :param username: username to fetch by
611 647 """
612 648 if user_id is None and api_key is None and username is None:
613 649 raise Exception('You need to pass user_id, api_key or username')
614 650
615 651 log.debug(
616 652 'doing fill data based on: user_id:%s api_key:%s username:%s',
617 653 user_id, api_key, username)
618 654 try:
619 655 dbuser = None
620 656 if user_id:
621 657 dbuser = self.get(user_id)
622 658 elif api_key:
623 659 dbuser = self.get_by_auth_token(api_key)
624 660 elif username:
625 661 dbuser = self.get_by_username(username)
626 662
627 663 if not dbuser:
628 664 log.warning(
629 665 'Unable to lookup user by id:%s api_key:%s username:%s',
630 666 user_id, api_key, username)
631 667 return False
632 668 if not dbuser.active:
633 669 log.debug('User `%s` is inactive, skipping fill data', username)
634 670 return False
635 671
636 672 log.debug('filling user:%s data', dbuser)
637 673
638 674 # TODO: johbo: Think about this and find a clean solution
639 675 user_data = dbuser.get_dict()
640 676 user_data.update(dbuser.get_api_data(include_secrets=True))
641 677
642 678 for k, v in user_data.iteritems():
643 679 # properties of auth user we dont update
644 680 if k not in ['auth_tokens', 'permissions']:
645 681 setattr(auth_user, k, v)
646 682
647 683 # few extras
648 684 setattr(auth_user, 'feed_token', dbuser.feed_token)
649 685 except Exception:
650 686 log.error(traceback.format_exc())
651 687 auth_user.is_authenticated = False
652 688 return False
653 689
654 690 return True
655 691
656 692 def has_perm(self, user, perm):
657 693 perm = self._get_perm(perm)
658 694 user = self._get_user(user)
659 695
660 696 return UserToPerm.query().filter(UserToPerm.user == user)\
661 697 .filter(UserToPerm.permission == perm).scalar() is not None
662 698
663 699 def grant_perm(self, user, perm):
664 700 """
665 701 Grant user global permissions
666 702
667 703 :param user:
668 704 :param perm:
669 705 """
670 706 user = self._get_user(user)
671 707 perm = self._get_perm(perm)
672 708 # if this permission is already granted skip it
673 709 _perm = UserToPerm.query()\
674 710 .filter(UserToPerm.user == user)\
675 711 .filter(UserToPerm.permission == perm)\
676 712 .scalar()
677 713 if _perm:
678 714 return
679 715 new = UserToPerm()
680 716 new.user = user
681 717 new.permission = perm
682 718 self.sa.add(new)
683 719 return new
684 720
685 721 def revoke_perm(self, user, perm):
686 722 """
687 723 Revoke users global permissions
688 724
689 725 :param user:
690 726 :param perm:
691 727 """
692 728 user = self._get_user(user)
693 729 perm = self._get_perm(perm)
694 730
695 731 obj = UserToPerm.query()\
696 732 .filter(UserToPerm.user == user)\
697 733 .filter(UserToPerm.permission == perm)\
698 734 .scalar()
699 735 if obj:
700 736 self.sa.delete(obj)
701 737
702 738 def add_extra_email(self, user, email):
703 739 """
704 740 Adds email address to UserEmailMap
705 741
706 742 :param user:
707 743 :param email:
708 744 """
709 745 from rhodecode.model import forms
710 746 form = forms.UserExtraEmailForm()()
711 747 data = form.to_python({'email': email})
712 748 user = self._get_user(user)
713 749
714 750 obj = UserEmailMap()
715 751 obj.user = user
716 752 obj.email = data['email']
717 753 self.sa.add(obj)
718 754 return obj
719 755
720 756 def delete_extra_email(self, user, email_id):
721 757 """
722 758 Removes email address from UserEmailMap
723 759
724 760 :param user:
725 761 :param email_id:
726 762 """
727 763 user = self._get_user(user)
728 764 obj = UserEmailMap.query().get(email_id)
729 765 if obj:
730 766 self.sa.delete(obj)
731 767
732 768 def parse_ip_range(self, ip_range):
733 769 ip_list = []
734 770 def make_unique(value):
735 771 seen = []
736 772 return [c for c in value if not (c in seen or seen.append(c))]
737 773
738 774 # firsts split by commas
739 775 for ip_range in ip_range.split(','):
740 776 if not ip_range:
741 777 continue
742 778 ip_range = ip_range.strip()
743 779 if '-' in ip_range:
744 780 start_ip, end_ip = ip_range.split('-', 1)
745 781 start_ip = ipaddress.ip_address(start_ip.strip())
746 782 end_ip = ipaddress.ip_address(end_ip.strip())
747 783 parsed_ip_range = []
748 784
749 785 for index in xrange(int(start_ip), int(end_ip) + 1):
750 786 new_ip = ipaddress.ip_address(index)
751 787 parsed_ip_range.append(str(new_ip))
752 788 ip_list.extend(parsed_ip_range)
753 789 else:
754 790 ip_list.append(ip_range)
755 791
756 792 return make_unique(ip_list)
757 793
758 794 def add_extra_ip(self, user, ip, description=None):
759 795 """
760 796 Adds ip address to UserIpMap
761 797
762 798 :param user:
763 799 :param ip:
764 800 """
765 801 from rhodecode.model import forms
766 802 form = forms.UserExtraIpForm()()
767 803 data = form.to_python({'ip': ip})
768 804 user = self._get_user(user)
769 805
770 806 obj = UserIpMap()
771 807 obj.user = user
772 808 obj.ip_addr = data['ip']
773 809 obj.description = description
774 810 self.sa.add(obj)
775 811 return obj
776 812
777 813 def delete_extra_ip(self, user, ip_id):
778 814 """
779 815 Removes ip address from UserIpMap
780 816
781 817 :param user:
782 818 :param ip_id:
783 819 """
784 820 user = self._get_user(user)
785 821 obj = UserIpMap.query().get(ip_id)
786 822 if obj:
787 823 self.sa.delete(obj)
788 824
789 825 def get_accounts_in_creation_order(self, current_user=None):
790 826 """
791 827 Get accounts in order of creation for deactivation for license limits
792 828
793 829 pick currently logged in user, and append to the list in position 0
794 830 pick all super-admins in order of creation date and add it to the list
795 831 pick all other accounts in order of creation and add it to the list.
796 832
797 833 Based on that list, the last accounts can be disabled as they are
798 834 created at the end and don't include any of the super admins as well
799 835 as the current user.
800 836
801 837 :param current_user: optionally current user running this operation
802 838 """
803 839
804 840 if not current_user:
805 841 current_user = get_current_rhodecode_user()
806 842 active_super_admins = [
807 843 x.user_id for x in User.query()
808 844 .filter(User.user_id != current_user.user_id)
809 845 .filter(User.active == true())
810 846 .filter(User.admin == true())
811 847 .order_by(User.created_on.asc())]
812 848
813 849 active_regular_users = [
814 850 x.user_id for x in User.query()
815 851 .filter(User.user_id != current_user.user_id)
816 852 .filter(User.active == true())
817 853 .filter(User.admin == false())
818 854 .order_by(User.created_on.asc())]
819 855
820 856 list_of_accounts = [current_user.user_id]
821 857 list_of_accounts += active_super_admins
822 858 list_of_accounts += active_regular_users
823 859
824 860 return list_of_accounts
825 861
826 862 def deactivate_last_users(self, expected_users):
827 863 """
828 864 Deactivate accounts that are over the license limits.
829 865 Algorithm of which accounts to disabled is based on the formula:
830 866
831 867 Get current user, then super admins in creation order, then regular
832 868 active users in creation order.
833 869
834 870 Using that list we mark all accounts from the end of it as inactive.
835 871 This way we block only latest created accounts.
836 872
837 873 :param expected_users: list of users in special order, we deactivate
838 874 the end N ammoun of users from that list
839 875 """
840 876
841 877 list_of_accounts = self.get_accounts_in_creation_order()
842 878
843 879 for acc_id in list_of_accounts[expected_users + 1:]:
844 880 user = User.get(acc_id)
845 881 log.info('Deactivating account %s for license unlock', user)
846 882 user.active = False
847 883 Session().add(user)
848 884 Session().commit()
849 885
850 886 return
851 887
852 888 def get_user_log(self, user, filter_term):
853 889 user_log = UserLog.query()\
854 890 .filter(or_(UserLog.user_id == user.user_id,
855 891 UserLog.username == user.username))\
856 892 .options(joinedload(UserLog.user))\
857 893 .options(joinedload(UserLog.repository))\
858 894 .order_by(UserLog.action_date.desc())
859 895
860 896 user_log = user_log_filter(user_log, filter_term)
861 897 return user_log
@@ -1,252 +1,171 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import tempfile
22 22
23 23 import mock
24 24 import pytest
25 25
26 26 from rhodecode.lib.exceptions import AttachedForksError
27 27 from rhodecode.lib.utils import make_db_config
28 28 from rhodecode.model.db import Repository
29 29 from rhodecode.model.meta import Session
30 30 from rhodecode.model.repo import RepoModel
31 31 from rhodecode.model.scm import ScmModel
32 32
33 33
34 34 class TestRepoModel(object):
35 35
36 36 def test_remove_repo(self, backend):
37 37 repo = backend.create_repo()
38 38 Session().commit()
39 39 RepoModel().delete(repo=repo)
40 40 Session().commit()
41 41
42 42 repos = ScmModel().repo_scan()
43 43
44 44 assert Repository.get_by_repo_name(repo_name=backend.repo_name) is None
45 45 assert repo.repo_name not in repos
46 46
47 47 def test_remove_repo_raises_exc_when_attached_forks(self, backend):
48 48 repo = backend.create_repo()
49 49 Session().commit()
50 50 backend.create_fork()
51 51 Session().commit()
52 52
53 53 with pytest.raises(AttachedForksError):
54 54 RepoModel().delete(repo=repo)
55 55
56 56 def test_remove_repo_delete_forks(self, backend):
57 57 repo = backend.create_repo()
58 58 Session().commit()
59 59
60 60 fork = backend.create_fork()
61 61 Session().commit()
62 62
63 63 fork_of_fork = backend.create_fork()
64 64 Session().commit()
65 65
66 66 RepoModel().delete(repo=repo, forks='delete')
67 67 Session().commit()
68 68
69 69 assert Repository.get_by_repo_name(repo_name=repo.repo_name) is None
70 70 assert Repository.get_by_repo_name(repo_name=fork.repo_name) is None
71 71 assert (
72 72 Repository.get_by_repo_name(repo_name=fork_of_fork.repo_name)
73 73 is None)
74 74
75 75 def test_remove_repo_detach_forks(self, backend):
76 76 repo = backend.create_repo()
77 77 Session().commit()
78 78
79 79 fork = backend.create_fork()
80 80 Session().commit()
81 81
82 82 fork_of_fork = backend.create_fork()
83 83 Session().commit()
84 84
85 85 RepoModel().delete(repo=repo, forks='detach')
86 86 Session().commit()
87 87
88 88 assert Repository.get_by_repo_name(repo_name=repo.repo_name) is None
89 89 assert (
90 90 Repository.get_by_repo_name(repo_name=fork.repo_name) is not None)
91 91 assert (
92 92 Repository.get_by_repo_name(repo_name=fork_of_fork.repo_name)
93 93 is not None)
94 94
95 95 @pytest.mark.parametrize("filename, expected", [
96 96 ("README", True),
97 97 ("README.rst", False),
98 98 ])
99 99 def test_filenode_is_link(self, vcsbackend, filename, expected):
100 100 repo = vcsbackend.repo
101 101 assert repo.get_commit().is_link(filename) is expected
102 102
103 103 def test_get_commit(self, backend):
104 104 backend.repo.get_commit()
105 105
106 106 def test_get_changeset_is_deprecated(self, backend):
107 107 repo = backend.repo
108 108 pytest.deprecated_call(repo.get_changeset)
109 109
110 110 def test_clone_url_encrypted_value(self, backend):
111 111 repo = backend.create_repo()
112 112 Session().commit()
113 113
114 114 repo.clone_url = 'https://marcink:qweqwe@code.rhodecode.com'
115 115 Session().add(repo)
116 116 Session().commit()
117 117
118 118 assert repo.clone_url == 'https://marcink:qweqwe@code.rhodecode.com'
119 119
120 120 @pytest.mark.backends("git", "svn")
121 121 def test_create_filesystem_repo_installs_hooks(self, tmpdir, backend):
122 122 hook_methods = {
123 123 'git': 'install_git_hook',
124 124 'svn': 'install_svn_hooks'
125 125 }
126 126 repo = backend.create_repo()
127 127 repo_name = repo.repo_name
128 128 model = RepoModel()
129 129 repo_location = tempfile.mkdtemp()
130 130 model.repos_path = repo_location
131 131 method = hook_methods[backend.alias]
132 132 with mock.patch.object(ScmModel, method) as hooks_mock:
133 133 model._create_filesystem_repo(
134 134 repo_name, backend.alias, repo_group='', clone_uri=None)
135 135 assert hooks_mock.call_count == 1
136 136 hook_args, hook_kwargs = hooks_mock.call_args
137 137 assert hook_args[0].name == repo_name
138 138
139 139 @pytest.mark.parametrize("use_global_config, repo_name_passed", [
140 140 (True, False),
141 141 (False, True)
142 142 ])
143 143 def test_per_repo_config_is_generated_during_filesystem_repo_creation(
144 144 self, tmpdir, backend, use_global_config, repo_name_passed):
145 145 repo_name = 'test-{}-repo-{}'.format(backend.alias, use_global_config)
146 146 config = make_db_config()
147 147 model = RepoModel()
148 148 with mock.patch('rhodecode.model.repo.make_db_config') as config_mock:
149 149 config_mock.return_value = config
150 150 model._create_filesystem_repo(
151 151 repo_name, backend.alias, repo_group='', clone_uri=None,
152 152 use_global_config=use_global_config)
153 153 expected_repo_name = repo_name if repo_name_passed else None
154 154 expected_call = mock.call(clear_session=False, repo=expected_repo_name)
155 155 assert expected_call in config_mock.call_args_list
156 156
157 157 def test_update_commit_cache_with_config(serf, backend):
158 158 repo = backend.create_repo()
159 159 with mock.patch('rhodecode.model.db.Repository.scm_instance') as scm:
160 160 scm_instance = mock.Mock()
161 161 scm_instance.get_commit.return_value = {
162 162 'raw_id': 40*'0',
163 163 'revision': 1
164 164 }
165 165 scm.return_value = scm_instance
166 166 repo.update_commit_cache()
167 167 scm.assert_called_with(cache=False, config=None)
168 168 config = {'test': 'config'}
169 169 repo.update_commit_cache(config=config)
170 170 scm.assert_called_with(
171 171 cache=False, config=config)
172
173
174 class TestGetUsers(object):
175 def test_returns_active_users(self, backend, user_util):
176 for i in range(4):
177 is_active = i % 2 == 0
178 user_util.create_user(active=is_active, lastname='Fake user')
179
180 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
181 users = RepoModel().get_users()
182 fake_users = [u for u in users if u['last_name'] == 'Fake user']
183 assert len(fake_users) == 2
184
185 expected_keys = (
186 'id', 'first_name', 'last_name', 'username', 'icon_link',
187 'value_display', 'value', 'value_type')
188 for user in users:
189 assert user['value_type'] is 'user'
190 for key in expected_keys:
191 assert key in user
192
193 def test_returns_user_filtered_by_last_name(self, backend, user_util):
194 keywords = ('aBc', u'ΓΌnicode')
195 for keyword in keywords:
196 for i in range(2):
197 user_util.create_user(
198 active=True, lastname=u'Fake {} user'.format(keyword))
199
200 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
201 keyword = keywords[1].lower()
202 users = RepoModel().get_users(name_contains=keyword)
203
204 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
205 assert len(fake_users) == 2
206 for user in fake_users:
207 assert user['last_name'] == safe_unicode('Fake ΓΌnicode user')
208
209 def test_returns_user_filtered_by_first_name(self, backend, user_util):
210 created_users = []
211 keywords = ('aBc', u'ΓΌnicode')
212 for keyword in keywords:
213 for i in range(2):
214 created_users.append(user_util.create_user(
215 active=True, lastname='Fake user',
216 firstname=u'Fake {} user'.format(keyword)))
217
218 keyword = keywords[1].lower()
219 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
220 users = RepoModel().get_users(name_contains=keyword)
221
222 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
223 assert len(fake_users) == 2
224 for user in fake_users:
225 assert user['first_name'] == safe_unicode('Fake ΓΌnicode user')
226
227 def test_returns_user_filtered_by_username(self, backend, user_util):
228 created_users = []
229 for i in range(5):
230 created_users.append(user_util.create_user(
231 active=True, lastname='Fake user'))
232
233 user_filter = created_users[-1].username[-2:]
234 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
235 users = RepoModel().get_users(name_contains=user_filter)
236
237 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
238 assert len(fake_users) == 1
239 assert fake_users[0]['username'] == created_users[-1].username
240
241 def test_returns_limited_user_list(self, backend, user_util):
242 created_users = []
243 for i in range(5):
244 created_users.append(user_util.create_user(
245 active=True, lastname='Fake user'))
246
247 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
248 users = RepoModel().get_users(name_contains='Fake', limit=3)
249
250 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
251 assert len(fake_users) == 3
252
@@ -1,242 +1,323 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 from sqlalchemy.sql.expression import true
22 import mock
23 23
24 from rhodecode.model.db import User, UserGroup, UserGroupMember, UserEmailMap,\
25 Permission, UserIpMap
24 from rhodecode.lib.utils2 import safe_unicode
25 from rhodecode.model.db import (
26 true, User, UserGroup, UserGroupMember, UserEmailMap, Permission, UserIpMap)
26 27 from rhodecode.model.meta import Session
27 28 from rhodecode.model.user import UserModel
28 29 from rhodecode.model.user_group import UserGroupModel
29 30 from rhodecode.model.repo import RepoModel
30 31 from rhodecode.model.repo_group import RepoGroupModel
31 32 from rhodecode.tests.fixture import Fixture
32 33
33 34 fixture = Fixture()
34 35
35 36
37 class TestGetUsers(object):
38 def test_returns_active_users(self, backend, user_util):
39 for i in range(4):
40 is_active = i % 2 == 0
41 user_util.create_user(active=is_active, lastname='Fake user')
42
43 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
44 users = UserModel().get_users()
45 fake_users = [u for u in users if u['last_name'] == 'Fake user']
46 assert len(fake_users) == 2
47
48 expected_keys = (
49 'id', 'first_name', 'last_name', 'username', 'icon_link',
50 'value_display', 'value', 'value_type')
51 for user in users:
52 assert user['value_type'] is 'user'
53 for key in expected_keys:
54 assert key in user
55
56 def test_returns_user_filtered_by_last_name(self, backend, user_util):
57 keywords = ('aBc', u'ΓΌnicode')
58 for keyword in keywords:
59 for i in range(2):
60 user_util.create_user(
61 active=True, lastname=u'Fake {} user'.format(keyword))
62
63 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
64 keyword = keywords[1].lower()
65 users = UserModel().get_users(name_contains=keyword)
66
67 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
68 assert len(fake_users) == 2
69 for user in fake_users:
70 assert user['last_name'] == safe_unicode('Fake ΓΌnicode user')
71
72 def test_returns_user_filtered_by_first_name(self, backend, user_util):
73 created_users = []
74 keywords = ('aBc', u'ΓΌnicode')
75 for keyword in keywords:
76 for i in range(2):
77 created_users.append(user_util.create_user(
78 active=True, lastname='Fake user',
79 firstname=u'Fake {} user'.format(keyword)))
80
81 keyword = keywords[1].lower()
82 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
83 users = UserModel().get_users(name_contains=keyword)
84
85 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
86 assert len(fake_users) == 2
87 for user in fake_users:
88 assert user['first_name'] == safe_unicode('Fake ΓΌnicode user')
89
90 def test_returns_user_filtered_by_username(self, backend, user_util):
91 created_users = []
92 for i in range(5):
93 created_users.append(user_util.create_user(
94 active=True, lastname='Fake user'))
95
96 user_filter = created_users[-1].username[-2:]
97 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
98 users = UserModel().get_users(name_contains=user_filter)
99
100 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
101 assert len(fake_users) == 1
102 assert fake_users[0]['username'] == created_users[-1].username
103
104 def test_returns_limited_user_list(self, backend, user_util):
105 created_users = []
106 for i in range(5):
107 created_users.append(user_util.create_user(
108 active=True, lastname='Fake user'))
109
110 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
111 users = UserModel().get_users(name_contains='Fake', limit=3)
112
113 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
114 assert len(fake_users) == 3
115
116
36 117 @pytest.fixture
37 118 def test_user(request, pylonsapp):
38 119 usr = UserModel().create_or_update(
39 120 username=u'test_user',
40 121 password=u'qweqwe',
41 122 email=u'main_email@rhodecode.org',
42 123 firstname=u'u1', lastname=u'u1')
43 124 Session().commit()
44 125 assert User.get_by_username(u'test_user') == usr
45 126
46 127 @request.addfinalizer
47 128 def cleanup():
48 129 if UserModel().get_user(usr.user_id) is None:
49 130 return
50 131
51 132 perm = Permission.query().all()
52 133 for p in perm:
53 134 UserModel().revoke_perm(usr, p)
54 135
55 136 UserModel().delete(usr.user_id)
56 137 Session().commit()
57 138
58 139 return usr
59 140
60 141
61 142 def test_create_and_remove(test_user):
62 143 usr = test_user
63 144
64 145 # make user group
65 146 user_group = fixture.create_user_group('some_example_group')
66 147 Session().commit()
67 148
68 149 UserGroupModel().add_user_to_group(user_group, usr)
69 150 Session().commit()
70 151
71 152 assert UserGroup.get(user_group.users_group_id) == user_group
72 153 assert UserGroupMember.query().count() == 1
73 154 UserModel().delete(usr.user_id)
74 155 Session().commit()
75 156
76 157 assert UserGroupMember.query().all() == []
77 158
78 159
79 160 def test_additonal_email_as_main(test_user):
80 161 with pytest.raises(AttributeError):
81 162 m = UserEmailMap()
82 163 m.email = test_user.email
83 164 m.user = test_user
84 165 Session().add(m)
85 166 Session().commit()
86 167
87 168
88 169 def test_extra_email_map(test_user):
89 170
90 171 m = UserEmailMap()
91 172 m.email = u'main_email2@rhodecode.org'
92 173 m.user = test_user
93 174 Session().add(m)
94 175 Session().commit()
95 176
96 177 u = User.get_by_email(email='main_email@rhodecode.org')
97 178 assert test_user.user_id == u.user_id
98 179 assert test_user.username == u.username
99 180
100 181 u = User.get_by_email(email='main_email2@rhodecode.org')
101 182 assert test_user.user_id == u.user_id
102 183 assert test_user.username == u.username
103 184 u = User.get_by_email(email='main_email3@rhodecode.org')
104 185 assert u is None
105 186
106 187
107 188 def test_get_api_data_replaces_secret_data_by_default(test_user):
108 189 api_data = test_user.get_api_data()
109 190 api_key_length = 40
110 191 expected_replacement = '*' * api_key_length
111 192
112 193 for key in api_data['api_keys']:
113 194 assert key == expected_replacement
114 195
115 196
116 197 def test_get_api_data_includes_secret_data_if_activated(test_user):
117 198 api_data = test_user.get_api_data(include_secrets=True)
118 199 assert api_data['api_keys'] == test_user.auth_tokens
119 200
120 201
121 202 def test_add_perm(test_user):
122 203 perm = Permission.query().all()[0]
123 204 UserModel().grant_perm(test_user, perm)
124 205 Session().commit()
125 206 assert UserModel().has_perm(test_user, perm)
126 207
127 208
128 209 def test_has_perm(test_user):
129 210 perm = Permission.query().all()
130 211 for p in perm:
131 212 assert not UserModel().has_perm(test_user, p)
132 213
133 214
134 215 def test_revoke_perm(test_user):
135 216 perm = Permission.query().all()[0]
136 217 UserModel().grant_perm(test_user, perm)
137 218 Session().commit()
138 219 assert UserModel().has_perm(test_user, perm)
139 220
140 221 # revoke
141 222 UserModel().revoke_perm(test_user, perm)
142 223 Session().commit()
143 224 assert not UserModel().has_perm(test_user, perm)
144 225
145 226
146 227 @pytest.mark.parametrize("ip_range, expected, expect_errors", [
147 228 ('', [], False),
148 229 ('127.0.0.1', ['127.0.0.1'], False),
149 230 ('127.0.0.1,127.0.0.2', ['127.0.0.1', '127.0.0.2'], False),
150 231 ('127.0.0.1 , 127.0.0.2', ['127.0.0.1', '127.0.0.2'], False),
151 232 (
152 233 '127.0.0.1,172.172.172.0,127.0.0.2',
153 234 ['127.0.0.1', '172.172.172.0', '127.0.0.2'], False),
154 235 (
155 236 '127.0.0.1-127.0.0.5',
156 237 ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4', '127.0.0.5'],
157 238 False),
158 239 (
159 240 '127.0.0.1 - 127.0.0.5',
160 241 ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4', '127.0.0.5'],
161 242 False
162 243 ),
163 244 ('-', [], True),
164 245 ('127.0.0.1-32', [], True),
165 246 (
166 247 '127.0.0.1,127.0.0.1,127.0.0.1,127.0.0.1-127.0.0.2,127.0.0.2',
167 248 ['127.0.0.1', '127.0.0.2'], False),
168 249 (
169 250 '127.0.0.1-127.0.0.2,127.0.0.4-127.0.0.6,',
170 251 ['127.0.0.1', '127.0.0.2', '127.0.0.4', '127.0.0.5', '127.0.0.6'],
171 252 False
172 253 ),
173 254 (
174 255 '127.0.0.1-127.0.0.2,127.0.0.1-127.0.0.6,',
175 256 ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4', '127.0.0.5',
176 257 '127.0.0.6'],
177 258 False
178 259 ),
179 260 ])
180 261 def test_ip_range_generator(ip_range, expected, expect_errors):
181 262 func = UserModel().parse_ip_range
182 263 if expect_errors:
183 264 pytest.raises(Exception, func, ip_range)
184 265 else:
185 266 parsed_list = func(ip_range)
186 267 assert parsed_list == expected
187 268
188 269
189 270 def test_user_delete_cascades_ip_whitelist(test_user):
190 271 sample_ip = '1.1.1.1'
191 272 uid_map = UserIpMap(user_id=test_user.user_id, ip_addr=sample_ip)
192 273 Session().add(uid_map)
193 274 Session().delete(test_user)
194 275 try:
195 276 Session().flush()
196 277 finally:
197 278 Session().rollback()
198 279
199 280
200 281 def test_account_for_deactivation_generation(test_user):
201 282 accounts = UserModel().get_accounts_in_creation_order(
202 283 current_user=test_user)
203 284 # current user should be #1 in the list
204 285 assert accounts[0] == test_user.user_id
205 286 active_users = User.query().filter(User.active == true()).count()
206 287 assert active_users == len(accounts)
207 288
208 289
209 290 def test_user_delete_cascades_permissions_on_repo(backend, test_user):
210 291 test_repo = backend.create_repo()
211 292 RepoModel().grant_user_permission(
212 293 test_repo, test_user, 'repository.write')
213 294 Session().commit()
214 295
215 296 assert test_user.repo_to_perm
216 297
217 298 UserModel().delete(test_user)
218 299 Session().commit()
219 300
220 301
221 302 def test_user_delete_cascades_permissions_on_repo_group(
222 303 test_repo_group, test_user):
223 304 RepoGroupModel().grant_user_permission(
224 305 test_repo_group, test_user, 'group.write')
225 306 Session().commit()
226 307
227 308 assert test_user.repo_group_to_perm
228 309
229 310 Session().delete(test_user)
230 311 Session().commit()
231 312
232 313
233 314 def test_user_delete_cascades_permissions_on_user_group(
234 315 test_user_group, test_user):
235 316 UserGroupModel().grant_user_permission(
236 317 test_user_group, test_user, 'usergroup.write')
237 318 Session().commit()
238 319
239 320 assert test_user.user_group_to_perm
240 321
241 322 Session().delete(test_user)
242 323 Session().commit()
General Comments 0
You need to be logged in to leave comments. Login now