##// END OF EJS Templates
user-groups: moved get_user_groups from RepoModel to UserGroupModel.
marcink -
r1676:40da1231 default
parent child Browse files
Show More
@@ -1,306 +1,307 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 logging
22 22 import datetime
23 23
24 24 from pyramid.httpexceptions import HTTPFound
25 25 from pyramid.view import view_config
26 26 from sqlalchemy.sql.functions import coalesce
27 27
28 28 from rhodecode.lib.helpers import Page
29 29 from rhodecode_tools.lib.ext_json import json
30 30
31 31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 32 from rhodecode.lib.auth import (
33 33 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
34 34 from rhodecode.lib import helpers as h
35 35 from rhodecode.lib.utils import PartialRenderer
36 36 from rhodecode.lib.utils2 import safe_int, safe_unicode
37 37 from rhodecode.model.auth_token import AuthTokenModel
38 38 from rhodecode.model.user import UserModel
39 39 from rhodecode.model.user_group import UserGroupModel
40 40 from rhodecode.model.db import User, or_
41 41 from rhodecode.model.meta import Session
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45
46 46 class AdminUsersView(BaseAppView, DataGridAppView):
47 47 ALLOW_SCOPED_TOKENS = False
48 48 """
49 49 This view has alternative version inside EE, if modified please take a look
50 50 in there as well.
51 51 """
52 52
53 53 def load_default_context(self):
54 54 c = self._get_local_tmpl_context()
55 55 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
56 56 self._register_global_c(c)
57 57 return c
58 58
59 59 def _redirect_for_default_user(self, username):
60 60 _ = self.request.translate
61 61 if username == User.DEFAULT_USER:
62 62 h.flash(_("You can't edit this user"), category='warning')
63 63 # TODO(marcink): redirect to 'users' admin panel once this
64 64 # is a pyramid view
65 65 raise HTTPFound('/')
66 66
67 67 @HasPermissionAllDecorator('hg.admin')
68 68 @view_config(
69 69 route_name='users', request_method='GET',
70 70 renderer='rhodecode:templates/admin/users/users.mako')
71 71 def users_list(self):
72 72 c = self.load_default_context()
73 73 return self._get_template_context(c)
74 74
75 75 @HasPermissionAllDecorator('hg.admin')
76 76 @view_config(
77 77 # renderer defined below
78 78 route_name='users_data', request_method='GET',
79 79 renderer='json_ext', xhr=True)
80 80 def users_list_data(self):
81 81 draw, start, limit = self._extract_chunk(self.request)
82 82 search_q, order_by, order_dir = self._extract_ordering(self.request)
83 83
84 84 _render = PartialRenderer('data_table/_dt_elements.mako')
85 85
86 86 def user_actions(user_id, username):
87 87 return _render("user_actions", user_id, username)
88 88
89 89 users_data_total_count = User.query()\
90 90 .filter(User.username != User.DEFAULT_USER) \
91 91 .count()
92 92
93 93 # json generate
94 94 base_q = User.query().filter(User.username != User.DEFAULT_USER)
95 95
96 96 if search_q:
97 97 like_expression = u'%{}%'.format(safe_unicode(search_q))
98 98 base_q = base_q.filter(or_(
99 99 User.username.ilike(like_expression),
100 100 User._email.ilike(like_expression),
101 101 User.name.ilike(like_expression),
102 102 User.lastname.ilike(like_expression),
103 103 ))
104 104
105 105 users_data_total_filtered_count = base_q.count()
106 106
107 107 sort_col = getattr(User, order_by, None)
108 108 if sort_col:
109 109 if order_dir == 'asc':
110 110 # handle null values properly to order by NULL last
111 111 if order_by in ['last_activity']:
112 112 sort_col = coalesce(sort_col, datetime.date.max)
113 113 sort_col = sort_col.asc()
114 114 else:
115 115 # handle null values properly to order by NULL last
116 116 if order_by in ['last_activity']:
117 117 sort_col = coalesce(sort_col, datetime.date.min)
118 118 sort_col = sort_col.desc()
119 119
120 120 base_q = base_q.order_by(sort_col)
121 121 base_q = base_q.offset(start).limit(limit)
122 122
123 123 users_list = base_q.all()
124 124
125 125 users_data = []
126 126 for user in users_list:
127 127 users_data.append({
128 128 "username": h.gravatar_with_user(user.username),
129 129 "email": user.email,
130 130 "first_name": h.escape(user.name),
131 131 "last_name": h.escape(user.lastname),
132 132 "last_login": h.format_date(user.last_login),
133 133 "last_activity": h.format_date(user.last_activity),
134 134 "active": h.bool2icon(user.active),
135 135 "active_raw": user.active,
136 136 "admin": h.bool2icon(user.admin),
137 137 "extern_type": user.extern_type,
138 138 "extern_name": user.extern_name,
139 139 "action": user_actions(user.user_id, user.username),
140 140 })
141 141
142 142 data = ({
143 143 'draw': draw,
144 144 'data': users_data,
145 145 'recordsTotal': users_data_total_count,
146 146 'recordsFiltered': users_data_total_filtered_count,
147 147 })
148 148
149 149 return data
150 150
151 151 @LoginRequired()
152 152 @HasPermissionAllDecorator('hg.admin')
153 153 @view_config(
154 154 route_name='edit_user_auth_tokens', request_method='GET',
155 155 renderer='rhodecode:templates/admin/users/user_edit.mako')
156 156 def auth_tokens(self):
157 157 _ = self.request.translate
158 158 c = self.load_default_context()
159 159
160 160 user_id = self.request.matchdict.get('user_id')
161 161 c.user = User.get_or_404(user_id, pyramid_exc=True)
162 162 self._redirect_for_default_user(c.user.username)
163 163
164 164 c.active = 'auth_tokens'
165 165
166 166 c.lifetime_values = [
167 167 (str(-1), _('forever')),
168 168 (str(5), _('5 minutes')),
169 169 (str(60), _('1 hour')),
170 170 (str(60 * 24), _('1 day')),
171 171 (str(60 * 24 * 30), _('1 month')),
172 172 ]
173 173 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
174 174 c.role_values = [
175 175 (x, AuthTokenModel.cls._get_role_name(x))
176 176 for x in AuthTokenModel.cls.ROLES]
177 177 c.role_options = [(c.role_values, _("Role"))]
178 178 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
179 179 c.user.user_id, show_expired=True)
180 180 return self._get_template_context(c)
181 181
182 182 def maybe_attach_token_scope(self, token):
183 183 # implemented in EE edition
184 184 pass
185 185
186 186 @LoginRequired()
187 187 @HasPermissionAllDecorator('hg.admin')
188 188 @CSRFRequired()
189 189 @view_config(
190 190 route_name='edit_user_auth_tokens_add', request_method='POST')
191 191 def auth_tokens_add(self):
192 192 _ = self.request.translate
193 193 c = self.load_default_context()
194 194
195 195 user_id = self.request.matchdict.get('user_id')
196 196 c.user = User.get_or_404(user_id, pyramid_exc=True)
197 197 self._redirect_for_default_user(c.user.username)
198 198
199 199 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
200 200 description = self.request.POST.get('description')
201 201 role = self.request.POST.get('role')
202 202
203 203 token = AuthTokenModel().create(
204 204 c.user.user_id, description, lifetime, role)
205 205 self.maybe_attach_token_scope(token)
206 206 Session().commit()
207 207
208 208 h.flash(_("Auth token successfully created"), category='success')
209 209 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
210 210
211 211 @LoginRequired()
212 212 @HasPermissionAllDecorator('hg.admin')
213 213 @CSRFRequired()
214 214 @view_config(
215 215 route_name='edit_user_auth_tokens_delete', request_method='POST')
216 216 def auth_tokens_delete(self):
217 217 _ = self.request.translate
218 218 c = self.load_default_context()
219 219
220 220 user_id = self.request.matchdict.get('user_id')
221 221 c.user = User.get_or_404(user_id, pyramid_exc=True)
222 222 self._redirect_for_default_user(c.user.username)
223 223
224 224 del_auth_token = self.request.POST.get('del_auth_token')
225 225
226 226 if del_auth_token:
227 227 AuthTokenModel().delete(del_auth_token, c.user.user_id)
228 228 Session().commit()
229 229 h.flash(_("Auth token successfully deleted"), category='success')
230 230
231 231 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
232 232
233 233 @LoginRequired()
234 234 @HasPermissionAllDecorator('hg.admin')
235 235 @view_config(
236 236 route_name='edit_user_groups_management', request_method='GET',
237 237 renderer='rhodecode:templates/admin/users/user_edit.mako')
238 238 def groups_management(self):
239 239 c = self.load_default_context()
240 240
241 241 user_id = self.request.matchdict.get('user_id')
242 242 c.user = User.get_or_404(user_id, pyramid_exc=True)
243 243 c.data = c.user.group_member
244 244 self._redirect_for_default_user(c.user.username)
245 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group) for group in c.user.group_member]
245 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
246 for group in c.user.group_member]
246 247 c.groups = json.dumps(groups)
247 248 c.active = 'groups'
248 249
249 250 return self._get_template_context(c)
250 251
251 252 @LoginRequired()
252 253 @HasPermissionAllDecorator('hg.admin')
253 254 @view_config(
254 255 route_name='edit_user_groups_management_updates', request_method='POST')
255 256 def groups_management_updates(self):
256 257 _ = self.request.translate
257 258 c = self.load_default_context()
258 259
259 260 user_id = self.request.matchdict.get('user_id')
260 261 c.user = User.get_or_404(user_id, pyramid_exc=True)
261 262 self._redirect_for_default_user(c.user.username)
262 263
263 264 users_groups = set(self.request.POST.getall('users_group_id'))
264 265 users_groups_model = []
265 266
266 267 for ugid in users_groups:
267 268 users_groups_model.append(UserGroupModel().get_group(safe_int(ugid)))
268 269 user_group_model = UserGroupModel()
269 270 user_group_model.change_groups(c.user, users_groups_model)
270 271
271 272 Session().commit()
272 273 c.active = 'user_groups_management'
273 274 h.flash(_("Groups successfully changed"), category='success')
274 275
275 276 return HTTPFound(h.route_path(
276 277 'edit_user_groups_management', user_id=user_id))
277 278
278 279 @LoginRequired()
279 280 @HasPermissionAllDecorator('hg.admin')
280 281 @view_config(
281 282 route_name='edit_user_audit_logs', request_method='GET',
282 283 renderer='rhodecode:templates/admin/users/user_edit.mako')
283 284 def user_audit_logs(self):
284 285 _ = self.request.translate
285 286 c = self.load_default_context()
286 287
287 288 user_id = self.request.matchdict.get('user_id')
288 289 c.user = User.get_or_404(user_id, pyramid_exc=True)
289 290 self._redirect_for_default_user(c.user.username)
290 291 c.active = 'audit'
291 292
292 293 p = safe_int(self.request.GET.get('page', 1), 1)
293 294
294 295 filter_term = self.request.GET.get('filter')
295 296 c.user_log = UserModel().get_user_log(c.user, filter_term)
296 297
297 298 def url_generator(**kw):
298 299 if filter_term:
299 300 kw['filter'] = filter_term
300 301 return self.request.current_route_path(_query=kw)
301 302
302 303 c.user_log = Page(c.user_log, page=p, items_per_page=10,
303 304 url=url_generator)
304 305 c.filter_term = filter_term
305 306 return self._get_template_context(c)
306 307
@@ -1,237 +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
34 from rhodecode.model.user_group import UserGroupModel
35 35
36 36 log = logging.getLogger(__name__)
37 37
38 38
39 39 class HomeView(BaseAppView):
40 40
41 41 def load_default_context(self):
42 42 c = self._get_local_tmpl_context()
43 43 c.user = c.auth_user.get_instance()
44 44 self._register_global_c(c)
45 45 return c
46 46
47 47 @LoginRequired()
48 48 @view_config(
49 49 route_name='user_autocomplete_data', request_method='GET',
50 50 renderer='json_ext', xhr=True)
51 51 def user_autocomplete_data(self):
52 52 query = self.request.GET.get('query')
53 53 active = str2bool(self.request.GET.get('active') or True)
54 54 include_groups = str2bool(self.request.GET.get('user_groups'))
55 55
56 56 log.debug('generating user list, query:%s, active:%s, with_groups:%s',
57 57 query, active, include_groups)
58 58
59 59 repo_model = RepoModel()
60 60 _users = repo_model.get_users(
61 61 name_contains=query, only_active=active)
62 62
63 63 if include_groups:
64 64 # extend with user groups
65 _user_groups = repo_model.get_user_groups(
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 repo_model = RepoModel()
83 _user_groups = repo_model.get_user_groups(
82 _user_groups = UserGroupModel().get_user_groups(
84 83 name_contains=query, only_active=active)
85 84 _user_groups = _user_groups
86 85
87 86 return {'suggestions': _user_groups}
88 87
89 88 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
90 89 query = Repository.query()\
91 90 .order_by(func.length(Repository.repo_name))\
92 91 .order_by(Repository.repo_name)
93 92
94 93 if repo_type:
95 94 query = query.filter(Repository.repo_type == repo_type)
96 95
97 96 if name_contains:
98 97 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
99 98 query = query.filter(
100 99 Repository.repo_name.ilike(ilike_expression))
101 100 query = query.limit(limit)
102 101
103 102 all_repos = query.all()
104 103 # permission checks are inside this function
105 104 repo_iter = ScmModel().get_repos(all_repos)
106 105 return [
107 106 {
108 107 'id': obj['name'],
109 108 'text': obj['name'],
110 109 'type': 'repo',
111 110 'obj': obj['dbrepo'],
112 111 'url': h.url('summary_home', repo_name=obj['name'])
113 112 }
114 113 for obj in repo_iter]
115 114
116 115 def _get_repo_group_list(self, name_contains=None, limit=20):
117 116 query = RepoGroup.query()\
118 117 .order_by(func.length(RepoGroup.group_name))\
119 118 .order_by(RepoGroup.group_name)
120 119
121 120 if name_contains:
122 121 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
123 122 query = query.filter(
124 123 RepoGroup.group_name.ilike(ilike_expression))
125 124 query = query.limit(limit)
126 125
127 126 all_groups = query.all()
128 127 repo_groups_iter = ScmModel().get_repo_groups(all_groups)
129 128 return [
130 129 {
131 130 'id': obj.group_name,
132 131 'text': obj.group_name,
133 132 'type': 'group',
134 133 'obj': {},
135 134 'url': h.url('repo_group_home', group_name=obj.group_name)
136 135 }
137 136 for obj in repo_groups_iter]
138 137
139 138 def _get_hash_commit_list(self, auth_user, hash_starts_with=None):
140 139 if not hash_starts_with or len(hash_starts_with) < 3:
141 140 return []
142 141
143 142 commit_hashes = re.compile('([0-9a-f]{2,40})').findall(hash_starts_with)
144 143
145 144 if len(commit_hashes) != 1:
146 145 return []
147 146
148 147 commit_hash_prefix = commit_hashes[0]
149 148
150 149 searcher = searcher_from_config(self.request.registry.settings)
151 150 result = searcher.search(
152 151 'commit_id:%s*' % commit_hash_prefix, 'commit', auth_user,
153 152 raise_on_exc=False)
154 153
155 154 return [
156 155 {
157 156 'id': entry['commit_id'],
158 157 'text': entry['commit_id'],
159 158 'type': 'commit',
160 159 'obj': {'repo': entry['repository']},
161 160 'url': h.url('changeset_home',
162 161 repo_name=entry['repository'],
163 162 revision=entry['commit_id'])
164 163 }
165 164 for entry in result['results']]
166 165
167 166 @LoginRequired()
168 167 @view_config(
169 168 route_name='repo_list_data', request_method='GET',
170 169 renderer='json_ext', xhr=True)
171 170 def repo_list_data(self):
172 171 _ = self.request.translate
173 172
174 173 query = self.request.GET.get('query')
175 174 repo_type = self.request.GET.get('repo_type')
176 175 log.debug('generating repo list, query:%s, repo_type:%s',
177 176 query, repo_type)
178 177
179 178 res = []
180 179 repos = self._get_repo_list(query, repo_type=repo_type)
181 180 if repos:
182 181 res.append({
183 182 'text': _('Repositories'),
184 183 'children': repos
185 184 })
186 185
187 186 data = {
188 187 'more': False,
189 188 'results': res
190 189 }
191 190 return data
192 191
193 192 @LoginRequired()
194 193 @view_config(
195 194 route_name='goto_switcher_data', request_method='GET',
196 195 renderer='json_ext', xhr=True)
197 196 def goto_switcher_data(self):
198 197 c = self.load_default_context()
199 198
200 199 _ = self.request.translate
201 200
202 201 query = self.request.GET.get('query')
203 202 log.debug('generating goto switcher list, query %s', query)
204 203
205 204 res = []
206 205 repo_groups = self._get_repo_group_list(query)
207 206 if repo_groups:
208 207 res.append({
209 208 'text': _('Groups'),
210 209 'children': repo_groups
211 210 })
212 211
213 212 repos = self._get_repo_list(query)
214 213 if repos:
215 214 res.append({
216 215 'text': _('Repositories'),
217 216 'children': repos
218 217 })
219 218
220 219 commits = self._get_hash_commit_list(c.auth_user, query)
221 220 if commits:
222 221 unique_repos = {}
223 222 for commit in commits:
224 223 unique_repos.setdefault(commit['obj']['repo'], []
225 224 ).append(commit)
226 225
227 226 for repo in unique_repos:
228 227 res.append({
229 228 'text': _('Commits in %(repo)s') % {'repo': repo},
230 229 'children': unique_repos[repo]
231 230 })
232 231
233 232 data = {
234 233 'more': False,
235 234 'results': res
236 235 }
237 236 return data
@@ -1,1080 +1,1036 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 import func
34 33 from sqlalchemy.sql.expression import true, or_
35 34 from zope.cachedescriptors.property import Lazy as LazyProperty
36 35
37 36 from rhodecode import events
38 37 from rhodecode.lib import helpers as h
39 38 from rhodecode.lib.auth import HasUserGroupPermissionAny
40 39 from rhodecode.lib.caching_query import FromCache
41 40 from rhodecode.lib.exceptions import AttachedForksError
42 41 from rhodecode.lib.hooks_base import log_delete_repository
43 from rhodecode.lib.markup_renderer import MarkupRenderer
44 42 from rhodecode.lib.utils import make_db_config
45 43 from rhodecode.lib.utils2 import (
46 44 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
47 45 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
48 46 from rhodecode.lib.vcs.backends import get_backend
49 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
50 47 from rhodecode.model import BaseModel
51 48 from rhodecode.model.db import (
52 49 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
53 50 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
54 51 RepoGroup, RepositoryField)
55 from rhodecode.model.scm import UserGroupList
52
56 53 from rhodecode.model.settings import VcsSettingsModel
57 54
58 55
59 56 log = logging.getLogger(__name__)
60 57
61 58
62 59 class RepoModel(BaseModel):
63 60
64 61 cls = Repository
65 62
66 63 def _get_user_group(self, users_group):
67 64 return self._get_instance(UserGroup, users_group,
68 65 callback=UserGroup.get_by_group_name)
69 66
70 67 def _get_repo_group(self, repo_group):
71 68 return self._get_instance(RepoGroup, repo_group,
72 69 callback=RepoGroup.get_by_group_name)
73 70
74 71 def _create_default_perms(self, repository, private):
75 72 # create default permission
76 73 default = 'repository.read'
77 74 def_user = User.get_default_user()
78 75 for p in def_user.user_perms:
79 76 if p.permission.permission_name.startswith('repository.'):
80 77 default = p.permission.permission_name
81 78 break
82 79
83 80 default_perm = 'repository.none' if private else default
84 81
85 82 repo_to_perm = UserRepoToPerm()
86 83 repo_to_perm.permission = Permission.get_by_key(default_perm)
87 84
88 85 repo_to_perm.repository = repository
89 86 repo_to_perm.user_id = def_user.user_id
90 87
91 88 return repo_to_perm
92 89
93 90 @LazyProperty
94 91 def repos_path(self):
95 92 """
96 93 Gets the repositories root path from database
97 94 """
98 95 settings_model = VcsSettingsModel(sa=self.sa)
99 96 return settings_model.get_repos_location()
100 97
101 98 def get(self, repo_id, cache=False):
102 99 repo = self.sa.query(Repository) \
103 100 .filter(Repository.repo_id == repo_id)
104 101
105 102 if cache:
106 103 repo = repo.options(FromCache("sql_cache_short",
107 104 "get_repo_%s" % repo_id))
108 105 return repo.scalar()
109 106
110 107 def get_repo(self, repository):
111 108 return self._get_repo(repository)
112 109
113 110 def get_by_repo_name(self, repo_name, cache=False):
114 111 repo = self.sa.query(Repository) \
115 112 .filter(Repository.repo_name == repo_name)
116 113
117 114 if cache:
118 115 repo = repo.options(FromCache("sql_cache_short",
119 116 "get_repo_%s" % repo_name))
120 117 return repo.scalar()
121 118
122 119 def _extract_id_from_repo_name(self, repo_name):
123 120 if repo_name.startswith('/'):
124 121 repo_name = repo_name.lstrip('/')
125 122 by_id_match = re.match(r'^_(\d{1,})', repo_name)
126 123 if by_id_match:
127 124 return by_id_match.groups()[0]
128 125
129 126 def get_repo_by_id(self, repo_name):
130 127 """
131 128 Extracts repo_name by id from special urls.
132 129 Example url is _11/repo_name
133 130
134 131 :param repo_name:
135 132 :return: repo object if matched else None
136 133 """
137 134 try:
138 135 _repo_id = self._extract_id_from_repo_name(repo_name)
139 136 if _repo_id:
140 137 return self.get(_repo_id)
141 138 except Exception:
142 139 log.exception('Failed to extract repo_name from URL')
143 140
144 141 return None
145 142
146 143 def get_repos_for_root(self, root, traverse=False):
147 144 if traverse:
148 145 like_expression = u'{}%'.format(safe_unicode(root))
149 146 repos = Repository.query().filter(
150 147 Repository.repo_name.like(like_expression)).all()
151 148 else:
152 149 if root and not isinstance(root, RepoGroup):
153 150 raise ValueError(
154 151 'Root must be an instance '
155 152 'of RepoGroup, got:{} instead'.format(type(root)))
156 153 repos = Repository.query().filter(Repository.group == root).all()
157 154 return repos
158 155
159 156 def get_url(self, repo):
160 157 return h.url('summary_home', repo_name=safe_str(repo.repo_name),
161 158 qualified=True)
162 159
163 160 def get_users(self, name_contains=None, limit=20, only_active=True):
164 161
165 162 # TODO: mikhail: move this method to the UserModel.
166 163 query = self.sa.query(User)
167 164 if only_active:
168 165 query = query.filter(User.active == true())
169 166
170 167 if name_contains:
171 168 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
172 169 query = query.filter(
173 170 or_(
174 171 User.name.ilike(ilike_expression),
175 172 User.lastname.ilike(ilike_expression),
176 173 User.username.ilike(ilike_expression)
177 174 )
178 175 )
179 176 query = query.limit(limit)
180 177 users = query.all()
181 178
182 179 _users = [
183 180 {
184 181 'id': user.user_id,
185 182 'first_name': user.name,
186 183 'last_name': user.lastname,
187 184 'username': user.username,
188 185 'email': user.email,
189 186 'icon_link': h.gravatar_url(user.email, 30),
190 187 'value_display': h.person(user),
191 188 'value': user.username,
192 189 'value_type': 'user',
193 190 'active': user.active,
194 191 }
195 192 for user in users
196 193 ]
197 194 return _users
198 195
199 def get_user_groups(self, name_contains=None, limit=20, only_active=True):
200
201 # TODO: mikhail: move this method to the UserGroupModel.
202 query = self.sa.query(UserGroup)
203 if only_active:
204 query = query.filter(UserGroup.users_group_active == true())
205
206 if name_contains:
207 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
208 query = query.filter(
209 UserGroup.users_group_name.ilike(ilike_expression))\
210 .order_by(func.length(UserGroup.users_group_name))\
211 .order_by(UserGroup.users_group_name)
212
213 query = query.limit(limit)
214 user_groups = query.all()
215 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
216 user_groups = UserGroupList(user_groups, perm_set=perm_set)
217
218 _groups = [
219 {
220 'id': group.users_group_id,
221 # TODO: marcink figure out a way to generate the url for the
222 # icon
223 'icon_link': '',
224 'value_display': 'Group: %s (%d members)' % (
225 group.users_group_name, len(group.members),),
226 'value': group.users_group_name,
227 'description': group.user_group_description,
228 'owner': group.user.username,
229
230 'owner_icon': h.gravatar_url(group.user.email, 30),
231 'value_display_owner': h.person(group.user.email),
232
233 'value_type': 'user_group',
234 'active': group.users_group_active,
235 }
236 for group in user_groups
237 ]
238 return _groups
239
240 196 @classmethod
241 197 def update_repoinfo(cls, repositories=None):
242 198 if not repositories:
243 199 repositories = Repository.getAll()
244 200 for repo in repositories:
245 201 repo.update_commit_cache()
246 202
247 203 def get_repos_as_dict(self, repo_list=None, admin=False,
248 204 super_user_actions=False):
249 205
250 206 from rhodecode.lib.utils import PartialRenderer
251 207 _render = PartialRenderer('data_table/_dt_elements.mako')
252 208 c = _render.c
253 209
254 210 def quick_menu(repo_name):
255 211 return _render('quick_menu', repo_name)
256 212
257 213 def repo_lnk(name, rtype, rstate, private, fork_of):
258 214 return _render('repo_name', name, rtype, rstate, private, fork_of,
259 215 short_name=not admin, admin=False)
260 216
261 217 def last_change(last_change):
262 218 if admin and isinstance(last_change, datetime) and not last_change.tzinfo:
263 219 last_change = last_change + timedelta(seconds=
264 220 (datetime.now() - datetime.utcnow()).seconds)
265 221 return _render("last_change", last_change)
266 222
267 223 def rss_lnk(repo_name):
268 224 return _render("rss", repo_name)
269 225
270 226 def atom_lnk(repo_name):
271 227 return _render("atom", repo_name)
272 228
273 229 def last_rev(repo_name, cs_cache):
274 230 return _render('revision', repo_name, cs_cache.get('revision'),
275 231 cs_cache.get('raw_id'), cs_cache.get('author'),
276 232 cs_cache.get('message'))
277 233
278 234 def desc(desc):
279 235 if c.visual.stylify_metatags:
280 236 desc = h.urlify_text(h.escaped_stylize(desc))
281 237 else:
282 238 desc = h.urlify_text(h.html_escape(desc))
283 239
284 240 return _render('repo_desc', desc)
285 241
286 242 def state(repo_state):
287 243 return _render("repo_state", repo_state)
288 244
289 245 def repo_actions(repo_name):
290 246 return _render('repo_actions', repo_name, super_user_actions)
291 247
292 248 def user_profile(username):
293 249 return _render('user_profile', username)
294 250
295 251 repos_data = []
296 252 for repo in repo_list:
297 253 cs_cache = repo.changeset_cache
298 254 row = {
299 255 "menu": quick_menu(repo.repo_name),
300 256
301 257 "name": repo_lnk(repo.repo_name, repo.repo_type,
302 258 repo.repo_state, repo.private, repo.fork),
303 259 "name_raw": repo.repo_name.lower(),
304 260
305 261 "last_change": last_change(repo.last_db_change),
306 262 "last_change_raw": datetime_to_time(repo.last_db_change),
307 263
308 264 "last_changeset": last_rev(repo.repo_name, cs_cache),
309 265 "last_changeset_raw": cs_cache.get('revision'),
310 266
311 267 "desc": desc(repo.description),
312 268 "owner": user_profile(repo.user.username),
313 269
314 270 "state": state(repo.repo_state),
315 271 "rss": rss_lnk(repo.repo_name),
316 272
317 273 "atom": atom_lnk(repo.repo_name),
318 274 }
319 275 if admin:
320 276 row.update({
321 277 "action": repo_actions(repo.repo_name),
322 278 })
323 279 repos_data.append(row)
324 280
325 281 return repos_data
326 282
327 283 def _get_defaults(self, repo_name):
328 284 """
329 285 Gets information about repository, and returns a dict for
330 286 usage in forms
331 287
332 288 :param repo_name:
333 289 """
334 290
335 291 repo_info = Repository.get_by_repo_name(repo_name)
336 292
337 293 if repo_info is None:
338 294 return None
339 295
340 296 defaults = repo_info.get_dict()
341 297 defaults['repo_name'] = repo_info.just_name
342 298
343 299 groups = repo_info.groups_with_parents
344 300 parent_group = groups[-1] if groups else None
345 301
346 302 # we use -1 as this is how in HTML, we mark an empty group
347 303 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
348 304
349 305 keys_to_process = (
350 306 {'k': 'repo_type', 'strip': False},
351 307 {'k': 'repo_enable_downloads', 'strip': True},
352 308 {'k': 'repo_description', 'strip': True},
353 309 {'k': 'repo_enable_locking', 'strip': True},
354 310 {'k': 'repo_landing_rev', 'strip': True},
355 311 {'k': 'clone_uri', 'strip': False},
356 312 {'k': 'repo_private', 'strip': True},
357 313 {'k': 'repo_enable_statistics', 'strip': True}
358 314 )
359 315
360 316 for item in keys_to_process:
361 317 attr = item['k']
362 318 if item['strip']:
363 319 attr = remove_prefix(item['k'], 'repo_')
364 320
365 321 val = defaults[attr]
366 322 if item['k'] == 'repo_landing_rev':
367 323 val = ':'.join(defaults[attr])
368 324 defaults[item['k']] = val
369 325 if item['k'] == 'clone_uri':
370 326 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
371 327
372 328 # fill owner
373 329 if repo_info.user:
374 330 defaults.update({'user': repo_info.user.username})
375 331 else:
376 332 replacement_user = User.get_first_super_admin().username
377 333 defaults.update({'user': replacement_user})
378 334
379 335 # fill repository users
380 336 for p in repo_info.repo_to_perm:
381 337 defaults.update({'u_perm_%s' % p.user.user_id:
382 338 p.permission.permission_name})
383 339
384 340 # fill repository groups
385 341 for p in repo_info.users_group_to_perm:
386 342 defaults.update({'g_perm_%s' % p.users_group.users_group_id:
387 343 p.permission.permission_name})
388 344
389 345 return defaults
390 346
391 347 def update(self, repo, **kwargs):
392 348 try:
393 349 cur_repo = self._get_repo(repo)
394 350 source_repo_name = cur_repo.repo_name
395 351 if 'user' in kwargs:
396 352 cur_repo.user = User.get_by_username(kwargs['user'])
397 353
398 354 if 'repo_group' in kwargs:
399 355 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
400 356 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
401 357
402 358 update_keys = [
403 359 (1, 'repo_description'),
404 360 (1, 'repo_landing_rev'),
405 361 (1, 'repo_private'),
406 362 (1, 'repo_enable_downloads'),
407 363 (1, 'repo_enable_locking'),
408 364 (1, 'repo_enable_statistics'),
409 365 (0, 'clone_uri'),
410 366 (0, 'fork_id')
411 367 ]
412 368 for strip, k in update_keys:
413 369 if k in kwargs:
414 370 val = kwargs[k]
415 371 if strip:
416 372 k = remove_prefix(k, 'repo_')
417 373 if k == 'clone_uri':
418 374 from rhodecode.model.validators import Missing
419 375 _change = kwargs.get('clone_uri_change')
420 376 if _change in [Missing, 'OLD']:
421 377 # we don't change the value, so use original one
422 378 val = cur_repo.clone_uri
423 379
424 380 setattr(cur_repo, k, val)
425 381
426 382 new_name = cur_repo.get_new_name(kwargs['repo_name'])
427 383 cur_repo.repo_name = new_name
428 384
429 385 # if private flag is set, reset default permission to NONE
430 386 if kwargs.get('repo_private'):
431 387 EMPTY_PERM = 'repository.none'
432 388 RepoModel().grant_user_permission(
433 389 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
434 390 )
435 391
436 392 # handle extra fields
437 393 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
438 394 kwargs):
439 395 k = RepositoryField.un_prefix_key(field)
440 396 ex_field = RepositoryField.get_by_key_name(
441 397 key=k, repo=cur_repo)
442 398 if ex_field:
443 399 ex_field.field_value = kwargs[field]
444 400 self.sa.add(ex_field)
445 401 self.sa.add(cur_repo)
446 402
447 403 if source_repo_name != new_name:
448 404 # rename repository
449 405 self._rename_filesystem_repo(
450 406 old=source_repo_name, new=new_name)
451 407
452 408 return cur_repo
453 409 except Exception:
454 410 log.error(traceback.format_exc())
455 411 raise
456 412
457 413 def _create_repo(self, repo_name, repo_type, description, owner,
458 414 private=False, clone_uri=None, repo_group=None,
459 415 landing_rev='rev:tip', fork_of=None,
460 416 copy_fork_permissions=False, enable_statistics=False,
461 417 enable_locking=False, enable_downloads=False,
462 418 copy_group_permissions=False,
463 419 state=Repository.STATE_PENDING):
464 420 """
465 421 Create repository inside database with PENDING state, this should be
466 422 only executed by create() repo. With exception of importing existing
467 423 repos
468 424 """
469 425 from rhodecode.model.scm import ScmModel
470 426
471 427 owner = self._get_user(owner)
472 428 fork_of = self._get_repo(fork_of)
473 429 repo_group = self._get_repo_group(safe_int(repo_group))
474 430
475 431 try:
476 432 repo_name = safe_unicode(repo_name)
477 433 description = safe_unicode(description)
478 434 # repo name is just a name of repository
479 435 # while repo_name_full is a full qualified name that is combined
480 436 # with name and path of group
481 437 repo_name_full = repo_name
482 438 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
483 439
484 440 new_repo = Repository()
485 441 new_repo.repo_state = state
486 442 new_repo.enable_statistics = False
487 443 new_repo.repo_name = repo_name_full
488 444 new_repo.repo_type = repo_type
489 445 new_repo.user = owner
490 446 new_repo.group = repo_group
491 447 new_repo.description = description or repo_name
492 448 new_repo.private = private
493 449 new_repo.clone_uri = clone_uri
494 450 new_repo.landing_rev = landing_rev
495 451
496 452 new_repo.enable_statistics = enable_statistics
497 453 new_repo.enable_locking = enable_locking
498 454 new_repo.enable_downloads = enable_downloads
499 455
500 456 if repo_group:
501 457 new_repo.enable_locking = repo_group.enable_locking
502 458
503 459 if fork_of:
504 460 parent_repo = fork_of
505 461 new_repo.fork = parent_repo
506 462
507 463 events.trigger(events.RepoPreCreateEvent(new_repo))
508 464
509 465 self.sa.add(new_repo)
510 466
511 467 EMPTY_PERM = 'repository.none'
512 468 if fork_of and copy_fork_permissions:
513 469 repo = fork_of
514 470 user_perms = UserRepoToPerm.query() \
515 471 .filter(UserRepoToPerm.repository == repo).all()
516 472 group_perms = UserGroupRepoToPerm.query() \
517 473 .filter(UserGroupRepoToPerm.repository == repo).all()
518 474
519 475 for perm in user_perms:
520 476 UserRepoToPerm.create(
521 477 perm.user, new_repo, perm.permission)
522 478
523 479 for perm in group_perms:
524 480 UserGroupRepoToPerm.create(
525 481 perm.users_group, new_repo, perm.permission)
526 482 # in case we copy permissions and also set this repo to private
527 483 # override the default user permission to make it a private
528 484 # repo
529 485 if private:
530 486 RepoModel(self.sa).grant_user_permission(
531 487 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
532 488
533 489 elif repo_group and copy_group_permissions:
534 490 user_perms = UserRepoGroupToPerm.query() \
535 491 .filter(UserRepoGroupToPerm.group == repo_group).all()
536 492
537 493 group_perms = UserGroupRepoGroupToPerm.query() \
538 494 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
539 495
540 496 for perm in user_perms:
541 497 perm_name = perm.permission.permission_name.replace(
542 498 'group.', 'repository.')
543 499 perm_obj = Permission.get_by_key(perm_name)
544 500 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
545 501
546 502 for perm in group_perms:
547 503 perm_name = perm.permission.permission_name.replace(
548 504 'group.', 'repository.')
549 505 perm_obj = Permission.get_by_key(perm_name)
550 506 UserGroupRepoToPerm.create(
551 507 perm.users_group, new_repo, perm_obj)
552 508
553 509 if private:
554 510 RepoModel(self.sa).grant_user_permission(
555 511 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
556 512
557 513 else:
558 514 perm_obj = self._create_default_perms(new_repo, private)
559 515 self.sa.add(perm_obj)
560 516
561 517 # now automatically start following this repository as owner
562 518 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
563 519 owner.user_id)
564 520
565 521 # we need to flush here, in order to check if database won't
566 522 # throw any exceptions, create filesystem dirs at the very end
567 523 self.sa.flush()
568 524 events.trigger(events.RepoCreateEvent(new_repo))
569 525 return new_repo
570 526
571 527 except Exception:
572 528 log.error(traceback.format_exc())
573 529 raise
574 530
575 531 def create(self, form_data, cur_user):
576 532 """
577 533 Create repository using celery tasks
578 534
579 535 :param form_data:
580 536 :param cur_user:
581 537 """
582 538 from rhodecode.lib.celerylib import tasks, run_task
583 539 return run_task(tasks.create_repo, form_data, cur_user)
584 540
585 541 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
586 542 perm_deletions=None, check_perms=True,
587 543 cur_user=None):
588 544 if not perm_additions:
589 545 perm_additions = []
590 546 if not perm_updates:
591 547 perm_updates = []
592 548 if not perm_deletions:
593 549 perm_deletions = []
594 550
595 551 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
596 552
597 553 # update permissions
598 554 for member_id, perm, member_type in perm_updates:
599 555 member_id = int(member_id)
600 556 if member_type == 'user':
601 557 # this updates also current one if found
602 558 self.grant_user_permission(
603 559 repo=repo, user=member_id, perm=perm)
604 560 else: # set for user group
605 561 # check if we have permissions to alter this usergroup
606 562 member_name = UserGroup.get(member_id).users_group_name
607 563 if not check_perms or HasUserGroupPermissionAny(
608 564 *req_perms)(member_name, user=cur_user):
609 565 self.grant_user_group_permission(
610 566 repo=repo, group_name=member_id, perm=perm)
611 567
612 568 # set new permissions
613 569 for member_id, perm, member_type in perm_additions:
614 570 member_id = int(member_id)
615 571 if member_type == 'user':
616 572 self.grant_user_permission(
617 573 repo=repo, user=member_id, perm=perm)
618 574 else: # set for user group
619 575 # check if we have permissions to alter this usergroup
620 576 member_name = UserGroup.get(member_id).users_group_name
621 577 if not check_perms or HasUserGroupPermissionAny(
622 578 *req_perms)(member_name, user=cur_user):
623 579 self.grant_user_group_permission(
624 580 repo=repo, group_name=member_id, perm=perm)
625 581
626 582 # delete permissions
627 583 for member_id, perm, member_type in perm_deletions:
628 584 member_id = int(member_id)
629 585 if member_type == 'user':
630 586 self.revoke_user_permission(repo=repo, user=member_id)
631 587 else: # set for user group
632 588 # check if we have permissions to alter this usergroup
633 589 member_name = UserGroup.get(member_id).users_group_name
634 590 if not check_perms or HasUserGroupPermissionAny(
635 591 *req_perms)(member_name, user=cur_user):
636 592 self.revoke_user_group_permission(
637 593 repo=repo, group_name=member_id)
638 594
639 595 def create_fork(self, form_data, cur_user):
640 596 """
641 597 Simple wrapper into executing celery task for fork creation
642 598
643 599 :param form_data:
644 600 :param cur_user:
645 601 """
646 602 from rhodecode.lib.celerylib import tasks, run_task
647 603 return run_task(tasks.create_repo_fork, form_data, cur_user)
648 604
649 605 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
650 606 """
651 607 Delete given repository, forks parameter defines what do do with
652 608 attached forks. Throws AttachedForksError if deleted repo has attached
653 609 forks
654 610
655 611 :param repo:
656 612 :param forks: str 'delete' or 'detach'
657 613 :param fs_remove: remove(archive) repo from filesystem
658 614 """
659 615 if not cur_user:
660 616 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
661 617 repo = self._get_repo(repo)
662 618 if repo:
663 619 if forks == 'detach':
664 620 for r in repo.forks:
665 621 r.fork = None
666 622 self.sa.add(r)
667 623 elif forks == 'delete':
668 624 for r in repo.forks:
669 625 self.delete(r, forks='delete')
670 626 elif [f for f in repo.forks]:
671 627 raise AttachedForksError()
672 628
673 629 old_repo_dict = repo.get_dict()
674 630 events.trigger(events.RepoPreDeleteEvent(repo))
675 631 try:
676 632 self.sa.delete(repo)
677 633 if fs_remove:
678 634 self._delete_filesystem_repo(repo)
679 635 else:
680 636 log.debug('skipping removal from filesystem')
681 637 old_repo_dict.update({
682 638 'deleted_by': cur_user,
683 639 'deleted_on': time.time(),
684 640 })
685 641 log_delete_repository(**old_repo_dict)
686 642 events.trigger(events.RepoDeleteEvent(repo))
687 643 except Exception:
688 644 log.error(traceback.format_exc())
689 645 raise
690 646
691 647 def grant_user_permission(self, repo, user, perm):
692 648 """
693 649 Grant permission for user on given repository, or update existing one
694 650 if found
695 651
696 652 :param repo: Instance of Repository, repository_id, or repository name
697 653 :param user: Instance of User, user_id or username
698 654 :param perm: Instance of Permission, or permission_name
699 655 """
700 656 user = self._get_user(user)
701 657 repo = self._get_repo(repo)
702 658 permission = self._get_perm(perm)
703 659
704 660 # check if we have that permission already
705 661 obj = self.sa.query(UserRepoToPerm) \
706 662 .filter(UserRepoToPerm.user == user) \
707 663 .filter(UserRepoToPerm.repository == repo) \
708 664 .scalar()
709 665 if obj is None:
710 666 # create new !
711 667 obj = UserRepoToPerm()
712 668 obj.repository = repo
713 669 obj.user = user
714 670 obj.permission = permission
715 671 self.sa.add(obj)
716 672 log.debug('Granted perm %s to %s on %s', perm, user, repo)
717 673 action_logger_generic(
718 674 'granted permission: {} to user: {} on repo: {}'.format(
719 675 perm, user, repo), namespace='security.repo')
720 676 return obj
721 677
722 678 def revoke_user_permission(self, repo, user):
723 679 """
724 680 Revoke permission for user on given repository
725 681
726 682 :param repo: Instance of Repository, repository_id, or repository name
727 683 :param user: Instance of User, user_id or username
728 684 """
729 685
730 686 user = self._get_user(user)
731 687 repo = self._get_repo(repo)
732 688
733 689 obj = self.sa.query(UserRepoToPerm) \
734 690 .filter(UserRepoToPerm.repository == repo) \
735 691 .filter(UserRepoToPerm.user == user) \
736 692 .scalar()
737 693 if obj:
738 694 self.sa.delete(obj)
739 695 log.debug('Revoked perm on %s on %s', repo, user)
740 696 action_logger_generic(
741 697 'revoked permission from user: {} on repo: {}'.format(
742 698 user, repo), namespace='security.repo')
743 699
744 700 def grant_user_group_permission(self, repo, group_name, perm):
745 701 """
746 702 Grant permission for user group on given repository, or update
747 703 existing one if found
748 704
749 705 :param repo: Instance of Repository, repository_id, or repository name
750 706 :param group_name: Instance of UserGroup, users_group_id,
751 707 or user group name
752 708 :param perm: Instance of Permission, or permission_name
753 709 """
754 710 repo = self._get_repo(repo)
755 711 group_name = self._get_user_group(group_name)
756 712 permission = self._get_perm(perm)
757 713
758 714 # check if we have that permission already
759 715 obj = self.sa.query(UserGroupRepoToPerm) \
760 716 .filter(UserGroupRepoToPerm.users_group == group_name) \
761 717 .filter(UserGroupRepoToPerm.repository == repo) \
762 718 .scalar()
763 719
764 720 if obj is None:
765 721 # create new
766 722 obj = UserGroupRepoToPerm()
767 723
768 724 obj.repository = repo
769 725 obj.users_group = group_name
770 726 obj.permission = permission
771 727 self.sa.add(obj)
772 728 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
773 729 action_logger_generic(
774 730 'granted permission: {} to usergroup: {} on repo: {}'.format(
775 731 perm, group_name, repo), namespace='security.repo')
776 732
777 733 return obj
778 734
779 735 def revoke_user_group_permission(self, repo, group_name):
780 736 """
781 737 Revoke permission for user group on given repository
782 738
783 739 :param repo: Instance of Repository, repository_id, or repository name
784 740 :param group_name: Instance of UserGroup, users_group_id,
785 741 or user group name
786 742 """
787 743 repo = self._get_repo(repo)
788 744 group_name = self._get_user_group(group_name)
789 745
790 746 obj = self.sa.query(UserGroupRepoToPerm) \
791 747 .filter(UserGroupRepoToPerm.repository == repo) \
792 748 .filter(UserGroupRepoToPerm.users_group == group_name) \
793 749 .scalar()
794 750 if obj:
795 751 self.sa.delete(obj)
796 752 log.debug('Revoked perm to %s on %s', repo, group_name)
797 753 action_logger_generic(
798 754 'revoked permission from usergroup: {} on repo: {}'.format(
799 755 group_name, repo), namespace='security.repo')
800 756
801 757 def delete_stats(self, repo_name):
802 758 """
803 759 removes stats for given repo
804 760
805 761 :param repo_name:
806 762 """
807 763 repo = self._get_repo(repo_name)
808 764 try:
809 765 obj = self.sa.query(Statistics) \
810 766 .filter(Statistics.repository == repo).scalar()
811 767 if obj:
812 768 self.sa.delete(obj)
813 769 except Exception:
814 770 log.error(traceback.format_exc())
815 771 raise
816 772
817 773 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
818 774 field_type='str', field_desc=''):
819 775
820 776 repo = self._get_repo(repo_name)
821 777
822 778 new_field = RepositoryField()
823 779 new_field.repository = repo
824 780 new_field.field_key = field_key
825 781 new_field.field_type = field_type # python type
826 782 new_field.field_value = field_value
827 783 new_field.field_desc = field_desc
828 784 new_field.field_label = field_label
829 785 self.sa.add(new_field)
830 786 return new_field
831 787
832 788 def delete_repo_field(self, repo_name, field_key):
833 789 repo = self._get_repo(repo_name)
834 790 field = RepositoryField.get_by_key_name(field_key, repo)
835 791 if field:
836 792 self.sa.delete(field)
837 793
838 794 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
839 795 clone_uri=None, repo_store_location=None,
840 796 use_global_config=False):
841 797 """
842 798 makes repository on filesystem. It's group aware means it'll create
843 799 a repository within a group, and alter the paths accordingly of
844 800 group location
845 801
846 802 :param repo_name:
847 803 :param alias:
848 804 :param parent:
849 805 :param clone_uri:
850 806 :param repo_store_location:
851 807 """
852 808 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
853 809 from rhodecode.model.scm import ScmModel
854 810
855 811 if Repository.NAME_SEP in repo_name:
856 812 raise ValueError(
857 813 'repo_name must not contain groups got `%s`' % repo_name)
858 814
859 815 if isinstance(repo_group, RepoGroup):
860 816 new_parent_path = os.sep.join(repo_group.full_path_splitted)
861 817 else:
862 818 new_parent_path = repo_group or ''
863 819
864 820 if repo_store_location:
865 821 _paths = [repo_store_location]
866 822 else:
867 823 _paths = [self.repos_path, new_parent_path, repo_name]
868 824 # we need to make it str for mercurial
869 825 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
870 826
871 827 # check if this path is not a repository
872 828 if is_valid_repo(repo_path, self.repos_path):
873 829 raise Exception('This path %s is a valid repository' % repo_path)
874 830
875 831 # check if this path is a group
876 832 if is_valid_repo_group(repo_path, self.repos_path):
877 833 raise Exception('This path %s is a valid group' % repo_path)
878 834
879 835 log.info('creating repo %s in %s from url: `%s`',
880 836 repo_name, safe_unicode(repo_path),
881 837 obfuscate_url_pw(clone_uri))
882 838
883 839 backend = get_backend(repo_type)
884 840
885 841 config_repo = None if use_global_config else repo_name
886 842 if config_repo and new_parent_path:
887 843 config_repo = Repository.NAME_SEP.join(
888 844 (new_parent_path, config_repo))
889 845 config = make_db_config(clear_session=False, repo=config_repo)
890 846 config.set('extensions', 'largefiles', '')
891 847
892 848 # patch and reset hooks section of UI config to not run any
893 849 # hooks on creating remote repo
894 850 config.clear_section('hooks')
895 851
896 852 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
897 853 if repo_type == 'git':
898 854 repo = backend(
899 855 repo_path, config=config, create=True, src_url=clone_uri,
900 856 bare=True)
901 857 else:
902 858 repo = backend(
903 859 repo_path, config=config, create=True, src_url=clone_uri)
904 860
905 861 ScmModel().install_hooks(repo, repo_type=repo_type)
906 862
907 863 log.debug('Created repo %s with %s backend',
908 864 safe_unicode(repo_name), safe_unicode(repo_type))
909 865 return repo
910 866
911 867 def _rename_filesystem_repo(self, old, new):
912 868 """
913 869 renames repository on filesystem
914 870
915 871 :param old: old name
916 872 :param new: new name
917 873 """
918 874 log.info('renaming repo from %s to %s', old, new)
919 875
920 876 old_path = os.path.join(self.repos_path, old)
921 877 new_path = os.path.join(self.repos_path, new)
922 878 if os.path.isdir(new_path):
923 879 raise Exception(
924 880 'Was trying to rename to already existing dir %s' % new_path
925 881 )
926 882 shutil.move(old_path, new_path)
927 883
928 884 def _delete_filesystem_repo(self, repo):
929 885 """
930 886 removes repo from filesystem, the removal is acctually made by
931 887 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
932 888 repository is no longer valid for rhodecode, can be undeleted later on
933 889 by reverting the renames on this repository
934 890
935 891 :param repo: repo object
936 892 """
937 893 rm_path = os.path.join(self.repos_path, repo.repo_name)
938 894 repo_group = repo.group
939 895 log.info("Removing repository %s", rm_path)
940 896 # disable hg/git internal that it doesn't get detected as repo
941 897 alias = repo.repo_type
942 898
943 899 config = make_db_config(clear_session=False)
944 900 config.set('extensions', 'largefiles', '')
945 901 bare = getattr(repo.scm_instance(config=config), 'bare', False)
946 902
947 903 # skip this for bare git repos
948 904 if not bare:
949 905 # disable VCS repo
950 906 vcs_path = os.path.join(rm_path, '.%s' % alias)
951 907 if os.path.exists(vcs_path):
952 908 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
953 909
954 910 _now = datetime.now()
955 911 _ms = str(_now.microsecond).rjust(6, '0')
956 912 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
957 913 repo.just_name)
958 914 if repo_group:
959 915 # if repository is in group, prefix the removal path with the group
960 916 args = repo_group.full_path_splitted + [_d]
961 917 _d = os.path.join(*args)
962 918
963 919 if os.path.isdir(rm_path):
964 920 shutil.move(rm_path, os.path.join(self.repos_path, _d))
965 921
966 922
967 923 class ReadmeFinder:
968 924 """
969 925 Utility which knows how to find a readme for a specific commit.
970 926
971 927 The main idea is that this is a configurable algorithm. When creating an
972 928 instance you can define parameters, currently only the `default_renderer`.
973 929 Based on this configuration the method :meth:`search` behaves slightly
974 930 different.
975 931 """
976 932
977 933 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
978 934 path_re = re.compile(r'^docs?', re.IGNORECASE)
979 935
980 936 default_priorities = {
981 937 None: 0,
982 938 '.text': 2,
983 939 '.txt': 3,
984 940 '.rst': 1,
985 941 '.rest': 2,
986 942 '.md': 1,
987 943 '.mkdn': 2,
988 944 '.mdown': 3,
989 945 '.markdown': 4,
990 946 }
991 947
992 948 path_priority = {
993 949 'doc': 0,
994 950 'docs': 1,
995 951 }
996 952
997 953 FALLBACK_PRIORITY = 99
998 954
999 955 RENDERER_TO_EXTENSION = {
1000 956 'rst': ['.rst', '.rest'],
1001 957 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
1002 958 }
1003 959
1004 960 def __init__(self, default_renderer=None):
1005 961 self._default_renderer = default_renderer
1006 962 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
1007 963 default_renderer, [])
1008 964
1009 965 def search(self, commit, path='/'):
1010 966 """
1011 967 Find a readme in the given `commit`.
1012 968 """
1013 969 nodes = commit.get_nodes(path)
1014 970 matches = self._match_readmes(nodes)
1015 971 matches = self._sort_according_to_priority(matches)
1016 972 if matches:
1017 973 return matches[0].node
1018 974
1019 975 paths = self._match_paths(nodes)
1020 976 paths = self._sort_paths_according_to_priority(paths)
1021 977 for path in paths:
1022 978 match = self.search(commit, path=path)
1023 979 if match:
1024 980 return match
1025 981
1026 982 return None
1027 983
1028 984 def _match_readmes(self, nodes):
1029 985 for node in nodes:
1030 986 if not node.is_file():
1031 987 continue
1032 988 path = node.path.rsplit('/', 1)[-1]
1033 989 match = self.readme_re.match(path)
1034 990 if match:
1035 991 extension = match.group(1)
1036 992 yield ReadmeMatch(node, match, self._priority(extension))
1037 993
1038 994 def _match_paths(self, nodes):
1039 995 for node in nodes:
1040 996 if not node.is_dir():
1041 997 continue
1042 998 match = self.path_re.match(node.path)
1043 999 if match:
1044 1000 yield node.path
1045 1001
1046 1002 def _priority(self, extension):
1047 1003 renderer_priority = (
1048 1004 0 if extension in self._renderer_extensions else 1)
1049 1005 extension_priority = self.default_priorities.get(
1050 1006 extension, self.FALLBACK_PRIORITY)
1051 1007 return (renderer_priority, extension_priority)
1052 1008
1053 1009 def _sort_according_to_priority(self, matches):
1054 1010
1055 1011 def priority_and_path(match):
1056 1012 return (match.priority, match.path)
1057 1013
1058 1014 return sorted(matches, key=priority_and_path)
1059 1015
1060 1016 def _sort_paths_according_to_priority(self, paths):
1061 1017
1062 1018 def priority_and_path(path):
1063 1019 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1064 1020
1065 1021 return sorted(paths, key=priority_and_path)
1066 1022
1067 1023
1068 1024 class ReadmeMatch:
1069 1025
1070 1026 def __init__(self, node, match, priority):
1071 1027 self.node = node
1072 1028 self._match = match
1073 1029 self.priority = priority
1074 1030
1075 1031 @property
1076 1032 def path(self):
1077 1033 return self.node.path
1078 1034
1079 1035 def __repr__(self):
1080 1036 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
@@ -1,559 +1,604 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-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 """
23 23 user group model for RhodeCode
24 24 """
25 25
26 26
27 27 import logging
28 28 import traceback
29 29
30 from rhodecode.lib.utils2 import safe_str
30 from rhodecode.lib.utils2 import safe_str, safe_unicode
31 31 from rhodecode.model import BaseModel
32 from rhodecode.model.db import UserGroupMember, UserGroup,\
32 from rhodecode.model.scm import UserGroupList
33 from rhodecode.model.db import true, func, UserGroupMember, UserGroup,\
33 34 UserGroupRepoToPerm, Permission, UserGroupToPerm, User, UserUserGroupToPerm,\
34 35 UserGroupUserGroupToPerm, UserGroupRepoGroupToPerm
35 36 from rhodecode.lib.exceptions import UserGroupAssignedException,\
36 37 RepoGroupAssignmentError
37 38 from rhodecode.lib.utils2 import get_current_rhodecode_user, action_logger_generic
38 39
39 40 log = logging.getLogger(__name__)
40 41
41 42
42 43 class UserGroupModel(BaseModel):
43 44
44 45 cls = UserGroup
45 46
46 47 def _get_user_group(self, user_group):
47 48 return self._get_instance(UserGroup, user_group,
48 49 callback=UserGroup.get_by_group_name)
49 50
50 51 def _create_default_perms(self, user_group):
51 52 # create default permission
52 53 default_perm = 'usergroup.read'
53 54 def_user = User.get_default_user()
54 55 for p in def_user.user_perms:
55 56 if p.permission.permission_name.startswith('usergroup.'):
56 57 default_perm = p.permission.permission_name
57 58 break
58 59
59 60 user_group_to_perm = UserUserGroupToPerm()
60 61 user_group_to_perm.permission = Permission.get_by_key(default_perm)
61 62
62 63 user_group_to_perm.user_group = user_group
63 64 user_group_to_perm.user_id = def_user.user_id
64 65 return user_group_to_perm
65 66
66 67 def update_permissions(self, user_group, perm_additions=None, perm_updates=None,
67 68 perm_deletions=None, check_perms=True, cur_user=None):
68 69 from rhodecode.lib.auth import HasUserGroupPermissionAny
69 70 if not perm_additions:
70 71 perm_additions = []
71 72 if not perm_updates:
72 73 perm_updates = []
73 74 if not perm_deletions:
74 75 perm_deletions = []
75 76
76 77 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
77 78
78 79 # update permissions
79 80 for member_id, perm, member_type in perm_updates:
80 81 member_id = int(member_id)
81 82 if member_type == 'user':
82 83 # this updates existing one
83 84 self.grant_user_permission(
84 85 user_group=user_group, user=member_id, perm=perm
85 86 )
86 87 else:
87 88 # check if we have permissions to alter this usergroup
88 89 member_name = UserGroup.get(member_id).users_group_name
89 90 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
90 91 self.grant_user_group_permission(
91 92 target_user_group=user_group, user_group=member_id, perm=perm
92 93 )
93 94
94 95 # set new permissions
95 96 for member_id, perm, member_type in perm_additions:
96 97 member_id = int(member_id)
97 98 if member_type == 'user':
98 99 self.grant_user_permission(
99 100 user_group=user_group, user=member_id, perm=perm
100 101 )
101 102 else:
102 103 # check if we have permissions to alter this usergroup
103 104 member_name = UserGroup.get(member_id).users_group_name
104 105 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
105 106 self.grant_user_group_permission(
106 107 target_user_group=user_group, user_group=member_id, perm=perm
107 108 )
108 109
109 110 # delete permissions
110 111 for member_id, perm, member_type in perm_deletions:
111 112 member_id = int(member_id)
112 113 if member_type == 'user':
113 114 self.revoke_user_permission(user_group=user_group, user=member_id)
114 115 else:
115 116 #check if we have permissions to alter this usergroup
116 117 member_name = UserGroup.get(member_id).users_group_name
117 118 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
118 119 self.revoke_user_group_permission(
119 120 target_user_group=user_group, user_group=member_id
120 121 )
121 122
122 123 def get(self, user_group_id, cache=False):
123 124 return UserGroup.get(user_group_id)
124 125
125 126 def get_group(self, user_group):
126 127 return self._get_user_group(user_group)
127 128
128 129 def get_by_name(self, name, cache=False, case_insensitive=False):
129 130 return UserGroup.get_by_group_name(name, cache, case_insensitive)
130 131
131 132 def create(self, name, description, owner, active=True, group_data=None):
132 133 try:
133 134 new_user_group = UserGroup()
134 135 new_user_group.user = self._get_user(owner)
135 136 new_user_group.users_group_name = name
136 137 new_user_group.user_group_description = description
137 138 new_user_group.users_group_active = active
138 139 if group_data:
139 140 new_user_group.group_data = group_data
140 141 self.sa.add(new_user_group)
141 142 perm_obj = self._create_default_perms(new_user_group)
142 143 self.sa.add(perm_obj)
143 144
144 145 self.grant_user_permission(user_group=new_user_group,
145 146 user=owner, perm='usergroup.admin')
146 147
147 148 return new_user_group
148 149 except Exception:
149 150 log.error(traceback.format_exc())
150 151 raise
151 152
152 153 def _get_memberships_for_user_ids(self, user_group, user_id_list):
153 154 members = []
154 155 for user_id in user_id_list:
155 156 member = self._get_membership(user_group.users_group_id, user_id)
156 157 members.append(member)
157 158 return members
158 159
159 160 def _get_added_and_removed_user_ids(self, user_group, user_id_list):
160 161 current_members = user_group.members or []
161 162 current_members_ids = [m.user.user_id for m in current_members]
162 163
163 164 added_members = [
164 165 user_id for user_id in user_id_list
165 166 if user_id not in current_members_ids]
166 167 if user_id_list == []:
167 168 # all members were deleted
168 169 deleted_members = current_members_ids
169 170 else:
170 171 deleted_members = [
171 172 user_id for user_id in current_members_ids
172 173 if user_id not in user_id_list]
173 174
174 175 return (added_members, deleted_members)
175 176
176 177 def _set_users_as_members(self, user_group, user_ids):
177 178 user_group.members = []
178 179 self.sa.flush()
179 180 members = self._get_memberships_for_user_ids(
180 181 user_group, user_ids)
181 182 user_group.members = members
182 183 self.sa.add(user_group)
183 184
184 185 def _update_members_from_user_ids(self, user_group, user_ids):
185 186 added, removed = self._get_added_and_removed_user_ids(
186 187 user_group, user_ids)
187 188 self._set_users_as_members(user_group, user_ids)
188 189 self._log_user_changes('added to', user_group, added)
189 190 self._log_user_changes('removed from', user_group, removed)
190 191
191 192 def _clean_members_data(self, members_data):
192 193 if not members_data:
193 194 members_data = []
194 195
195 196 members = []
196 197 for user in members_data:
197 198 uid = int(user['member_user_id'])
198 199 if uid not in members and user['type'] in ['new', 'existing']:
199 200 members.append(uid)
200 201 return members
201 202
202 203 def update(self, user_group, form_data):
203 204 user_group = self._get_user_group(user_group)
204 205 if 'users_group_name' in form_data:
205 206 user_group.users_group_name = form_data['users_group_name']
206 207 if 'users_group_active' in form_data:
207 208 user_group.users_group_active = form_data['users_group_active']
208 209 if 'user_group_description' in form_data:
209 210 user_group.user_group_description = form_data[
210 211 'user_group_description']
211 212
212 213 # handle owner change
213 214 if 'user' in form_data:
214 215 owner = form_data['user']
215 216 if isinstance(owner, basestring):
216 217 owner = User.get_by_username(form_data['user'])
217 218
218 219 if not isinstance(owner, User):
219 220 raise ValueError(
220 221 'invalid owner for user group: %s' % form_data['user'])
221 222
222 223 user_group.user = owner
223 224
224 225 if 'users_group_members' in form_data:
225 226 members_id_list = self._clean_members_data(
226 227 form_data['users_group_members'])
227 228 self._update_members_from_user_ids(user_group, members_id_list)
228 229
229 230 self.sa.add(user_group)
230 231
231 232 def delete(self, user_group, force=False):
232 233 """
233 234 Deletes repository group, unless force flag is used
234 235 raises exception if there are members in that group, else deletes
235 236 group and users
236 237
237 238 :param user_group:
238 239 :param force:
239 240 """
240 241 user_group = self._get_user_group(user_group)
241 242 try:
242 243 # check if this group is not assigned to repo
243 244 assigned_to_repo = [x.repository for x in UserGroupRepoToPerm.query()\
244 245 .filter(UserGroupRepoToPerm.users_group == user_group).all()]
245 246 # check if this group is not assigned to repo
246 247 assigned_to_repo_group = [x.group for x in UserGroupRepoGroupToPerm.query()\
247 248 .filter(UserGroupRepoGroupToPerm.users_group == user_group).all()]
248 249
249 250 if (assigned_to_repo or assigned_to_repo_group) and not force:
250 251 assigned = ','.join(map(safe_str,
251 252 assigned_to_repo+assigned_to_repo_group))
252 253
253 254 raise UserGroupAssignedException(
254 255 'UserGroup assigned to %s' % (assigned,))
255 256 self.sa.delete(user_group)
256 257 except Exception:
257 258 log.error(traceback.format_exc())
258 259 raise
259 260
260 261 def _log_user_changes(self, action, user_group, user_or_users):
261 262 users = user_or_users
262 263 if not isinstance(users, (list, tuple)):
263 264 users = [users]
264 265 rhodecode_user = get_current_rhodecode_user()
265 266 ipaddr = getattr(rhodecode_user, 'ip_addr', '')
266 267 group_name = user_group.users_group_name
267 268
268 269 for user_or_user_id in users:
269 270 user = self._get_user(user_or_user_id)
270 271 log_text = 'User {user} {action} {group}'.format(
271 272 action=action, user=user.username, group=group_name)
272 273 log.info('Logging action: {0} by {1} ip:{2}'.format(
273 274 log_text, rhodecode_user, ipaddr))
274 275
275 276 def _find_user_in_group(self, user, user_group):
276 277 user_group_member = None
277 278 for m in user_group.members:
278 279 if m.user_id == user.user_id:
279 280 # Found this user's membership row
280 281 user_group_member = m
281 282 break
282 283
283 284 return user_group_member
284 285
285 286 def _get_membership(self, user_group_id, user_id):
286 287 user_group_member = UserGroupMember(user_group_id, user_id)
287 288 return user_group_member
288 289
289 290 def add_user_to_group(self, user_group, user):
290 291 user_group = self._get_user_group(user_group)
291 292 user = self._get_user(user)
292 293 user_member = self._find_user_in_group(user, user_group)
293 294 if user_member:
294 295 # user already in the group, skip
295 296 return True
296 297
297 298 member = self._get_membership(
298 299 user_group.users_group_id, user.user_id)
299 300 user_group.members.append(member)
300 301
301 302 try:
302 303 self.sa.add(member)
303 304 except Exception:
304 305 # what could go wrong here?
305 306 log.error(traceback.format_exc())
306 307 raise
307 308
308 309 self._log_user_changes('added to', user_group, user)
309 310 return member
310 311
311 312 def remove_user_from_group(self, user_group, user):
312 313 user_group = self._get_user_group(user_group)
313 314 user = self._get_user(user)
314 315 user_group_member = self._find_user_in_group(user, user_group)
315 316
316 317 if not user_group_member:
317 318 # User isn't in that group
318 319 return False
319 320
320 321 try:
321 322 self.sa.delete(user_group_member)
322 323 except Exception:
323 324 log.error(traceback.format_exc())
324 325 raise
325 326
326 327 self._log_user_changes('removed from', user_group, user)
327 328 return True
328 329
329 330 def has_perm(self, user_group, perm):
330 331 user_group = self._get_user_group(user_group)
331 332 perm = self._get_perm(perm)
332 333
333 334 return UserGroupToPerm.query()\
334 335 .filter(UserGroupToPerm.users_group == user_group)\
335 336 .filter(UserGroupToPerm.permission == perm).scalar() is not None
336 337
337 338 def grant_perm(self, user_group, perm):
338 339 user_group = self._get_user_group(user_group)
339 340 perm = self._get_perm(perm)
340 341
341 342 # if this permission is already granted skip it
342 343 _perm = UserGroupToPerm.query()\
343 344 .filter(UserGroupToPerm.users_group == user_group)\
344 345 .filter(UserGroupToPerm.permission == perm)\
345 346 .scalar()
346 347 if _perm:
347 348 return
348 349
349 350 new = UserGroupToPerm()
350 351 new.users_group = user_group
351 352 new.permission = perm
352 353 self.sa.add(new)
353 354 return new
354 355
355 356 def revoke_perm(self, user_group, perm):
356 357 user_group = self._get_user_group(user_group)
357 358 perm = self._get_perm(perm)
358 359
359 360 obj = UserGroupToPerm.query()\
360 361 .filter(UserGroupToPerm.users_group == user_group)\
361 362 .filter(UserGroupToPerm.permission == perm).scalar()
362 363 if obj:
363 364 self.sa.delete(obj)
364 365
365 366 def grant_user_permission(self, user_group, user, perm):
366 367 """
367 368 Grant permission for user on given user group, or update
368 369 existing one if found
369 370
370 371 :param user_group: Instance of UserGroup, users_group_id,
371 372 or users_group_name
372 373 :param user: Instance of User, user_id or username
373 374 :param perm: Instance of Permission, or permission_name
374 375 """
375 376
376 377 user_group = self._get_user_group(user_group)
377 378 user = self._get_user(user)
378 379 permission = self._get_perm(perm)
379 380
380 381 # check if we have that permission already
381 382 obj = self.sa.query(UserUserGroupToPerm)\
382 383 .filter(UserUserGroupToPerm.user == user)\
383 384 .filter(UserUserGroupToPerm.user_group == user_group)\
384 385 .scalar()
385 386 if obj is None:
386 387 # create new !
387 388 obj = UserUserGroupToPerm()
388 389 obj.user_group = user_group
389 390 obj.user = user
390 391 obj.permission = permission
391 392 self.sa.add(obj)
392 393 log.debug('Granted perm %s to %s on %s', perm, user, user_group)
393 394 action_logger_generic(
394 395 'granted permission: {} to user: {} on usergroup: {}'.format(
395 396 perm, user, user_group), namespace='security.usergroup')
396 397
397 398 return obj
398 399
399 400 def revoke_user_permission(self, user_group, user):
400 401 """
401 402 Revoke permission for user on given user group
402 403
403 404 :param user_group: Instance of UserGroup, users_group_id,
404 405 or users_group name
405 406 :param user: Instance of User, user_id or username
406 407 """
407 408
408 409 user_group = self._get_user_group(user_group)
409 410 user = self._get_user(user)
410 411
411 412 obj = self.sa.query(UserUserGroupToPerm)\
412 413 .filter(UserUserGroupToPerm.user == user)\
413 414 .filter(UserUserGroupToPerm.user_group == user_group)\
414 415 .scalar()
415 416 if obj:
416 417 self.sa.delete(obj)
417 418 log.debug('Revoked perm on %s on %s', user_group, user)
418 419 action_logger_generic(
419 420 'revoked permission from user: {} on usergroup: {}'.format(
420 421 user, user_group), namespace='security.usergroup')
421 422
422 423 def grant_user_group_permission(self, target_user_group, user_group, perm):
423 424 """
424 425 Grant user group permission for given target_user_group
425 426
426 427 :param target_user_group:
427 428 :param user_group:
428 429 :param perm:
429 430 """
430 431 target_user_group = self._get_user_group(target_user_group)
431 432 user_group = self._get_user_group(user_group)
432 433 permission = self._get_perm(perm)
433 434 # forbid assigning same user group to itself
434 435 if target_user_group == user_group:
435 436 raise RepoGroupAssignmentError('target repo:%s cannot be '
436 437 'assigned to itself' % target_user_group)
437 438
438 439 # check if we have that permission already
439 440 obj = self.sa.query(UserGroupUserGroupToPerm)\
440 441 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
441 442 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
442 443 .scalar()
443 444 if obj is None:
444 445 # create new !
445 446 obj = UserGroupUserGroupToPerm()
446 447 obj.user_group = user_group
447 448 obj.target_user_group = target_user_group
448 449 obj.permission = permission
449 450 self.sa.add(obj)
450 451 log.debug(
451 452 'Granted perm %s to %s on %s', perm, target_user_group, user_group)
452 453 action_logger_generic(
453 454 'granted permission: {} to usergroup: {} on usergroup: {}'.format(
454 455 perm, user_group, target_user_group),
455 456 namespace='security.usergroup')
456 457
457 458 return obj
458 459
459 460 def revoke_user_group_permission(self, target_user_group, user_group):
460 461 """
461 462 Revoke user group permission for given target_user_group
462 463
463 464 :param target_user_group:
464 465 :param user_group:
465 466 """
466 467 target_user_group = self._get_user_group(target_user_group)
467 468 user_group = self._get_user_group(user_group)
468 469
469 470 obj = self.sa.query(UserGroupUserGroupToPerm)\
470 471 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
471 472 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
472 473 .scalar()
473 474 if obj:
474 475 self.sa.delete(obj)
475 476 log.debug(
476 477 'Revoked perm on %s on %s', target_user_group, user_group)
477 478 action_logger_generic(
478 479 'revoked permission from usergroup: {} on usergroup: {}'.format(
479 480 user_group, target_user_group),
480 481 namespace='security.repogroup')
481 482
482 483 def enforce_groups(self, user, groups, extern_type=None):
483 484 user = self._get_user(user)
484 485 log.debug('Enforcing groups %s on user %s', groups, user)
485 486 current_groups = user.group_member
486 487 # find the external created groups
487 488 externals = [x.users_group for x in current_groups
488 489 if 'extern_type' in x.users_group.group_data]
489 490
490 491 # calculate from what groups user should be removed
491 492 # externals that are not in groups
492 493 for gr in externals:
493 494 if gr.users_group_name not in groups:
494 495 log.debug('Removing user %s from user group %s', user, gr)
495 496 self.remove_user_from_group(gr, user)
496 497
497 498 # now we calculate in which groups user should be == groups params
498 499 owner = User.get_first_super_admin().username
499 500 for gr in set(groups):
500 501 existing_group = UserGroup.get_by_group_name(gr)
501 502 if not existing_group:
502 503 desc = 'Automatically created from plugin:%s' % extern_type
503 504 # we use first admin account to set the owner of the group
504 505 existing_group = UserGroupModel().create(
505 506 gr, desc, owner, group_data={'extern_type': extern_type})
506 507
507 508 # we can only add users to special groups created via plugins
508 509 managed = 'extern_type' in existing_group.group_data
509 510 if managed:
510 511 log.debug('Adding user %s to user group %s', user, gr)
511 512 UserGroupModel().add_user_to_group(existing_group, user)
512 513 else:
513 514 log.debug('Skipping addition to group %s since it is '
514 515 'not set to be automatically synchronized' % gr)
515 516
516 517 def change_groups(self, user, groups):
517 518 """
518 519 This method changes user group assignment
519 520 :param user: User
520 521 :param groups: array of UserGroupModel
521 522 :return:
522 523 """
523 524 user = self._get_user(user)
524 525 log.debug('Changing user(%s) assignment to groups(%s)', user, groups)
525 526 current_groups = user.group_member
526 527 current_groups = [x.users_group for x in current_groups]
527 528
528 529 # calculate from what groups user should be removed/add
529 530 groups = set(groups)
530 531 current_groups = set(current_groups)
531 532
532 533 groups_to_remove = current_groups - groups
533 534 groups_to_add = groups - current_groups
534 535
535 536 for gr in groups_to_remove:
536 537 log.debug('Removing user %s from user group %s', user.username, gr.users_group_name)
537 538 self.remove_user_from_group(gr.users_group_name, user.username)
538 539 for gr in groups_to_add:
539 540 log.debug('Adding user %s to user group %s', user.username, gr.users_group_name)
540 541 UserGroupModel().add_user_to_group(gr.users_group_name, user.username)
541 542
543 def get_user_groups(self, name_contains=None, limit=20, only_active=True,
544 expand_groups=False):
545 import rhodecode.lib.helpers as h
546
547 query = self.sa.query(UserGroup)
548 if only_active:
549 query = query.filter(UserGroup.users_group_active == true())
550
551 if name_contains:
552 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
553 query = query.filter(
554 UserGroup.users_group_name.ilike(ilike_expression))\
555 .order_by(func.length(UserGroup.users_group_name))\
556 .order_by(UserGroup.users_group_name)
557
558 query = query.limit(limit)
559 user_groups = query.all()
560 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
561 user_groups = UserGroupList(user_groups, perm_set=perm_set)
562
563 _groups = [
564 {
565 'id': group.users_group_id,
566 # TODO: marcink figure out a way to generate the url for the
567 # icon
568 'icon_link': '',
569 'value_display': 'Group: %s (%d members)' % (
570 group.users_group_name, len(group.members),),
571 'value': group.users_group_name,
572 'description': group.user_group_description,
573 'owner': group.user.username,
574
575 'owner_icon': h.gravatar_url(group.user.email, 30),
576 'value_display_owner': h.person(group.user.email),
577
578 'value_type': 'user_group',
579 'active': group.users_group_active,
580 }
581 for group in user_groups
582 ]
583 return _groups
584
542 585 @staticmethod
543 586 def get_user_groups_as_dict(user_group):
544 587 import rhodecode.lib.helpers as h
545 588
546 589 data = {
547 590 'users_group_id': user_group.users_group_id,
548 591 'group_name': user_group.users_group_name,
549 592 'group_description': user_group.user_group_description,
550 593 'active': user_group.users_group_active,
551 594 "owner": user_group.user.username,
552 595 'owner_icon': h.gravatar_url(user_group.user.email, 30),
553 "owner_data": {'owner': user_group.user.username, 'owner_icon': h.gravatar_url(user_group.user.email, 30)}
596 "owner_data": {
597 'owner': user_group.user.username,
598 'owner_icon': h.gravatar_url(user_group.user.email, 30)}
554 599 }
555 600 return data
556 601
557 602
558 603
559 604
@@ -1,310 +1,252 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 from rhodecode.lib.utils2 import safe_unicode
33 32
34 33
35 class TestRepoModel:
34 class TestRepoModel(object):
36 35
37 36 def test_remove_repo(self, backend):
38 37 repo = backend.create_repo()
39 38 Session().commit()
40 39 RepoModel().delete(repo=repo)
41 40 Session().commit()
42 41
43 42 repos = ScmModel().repo_scan()
44 43
45 44 assert Repository.get_by_repo_name(repo_name=backend.repo_name) is None
46 45 assert repo.repo_name not in repos
47 46
48 47 def test_remove_repo_raises_exc_when_attached_forks(self, backend):
49 48 repo = backend.create_repo()
50 49 Session().commit()
51 50 backend.create_fork()
52 51 Session().commit()
53 52
54 53 with pytest.raises(AttachedForksError):
55 54 RepoModel().delete(repo=repo)
56 55
57 56 def test_remove_repo_delete_forks(self, backend):
58 57 repo = backend.create_repo()
59 58 Session().commit()
60 59
61 60 fork = backend.create_fork()
62 61 Session().commit()
63 62
64 63 fork_of_fork = backend.create_fork()
65 64 Session().commit()
66 65
67 66 RepoModel().delete(repo=repo, forks='delete')
68 67 Session().commit()
69 68
70 69 assert Repository.get_by_repo_name(repo_name=repo.repo_name) is None
71 70 assert Repository.get_by_repo_name(repo_name=fork.repo_name) is None
72 71 assert (
73 72 Repository.get_by_repo_name(repo_name=fork_of_fork.repo_name)
74 73 is None)
75 74
76 75 def test_remove_repo_detach_forks(self, backend):
77 76 repo = backend.create_repo()
78 77 Session().commit()
79 78
80 79 fork = backend.create_fork()
81 80 Session().commit()
82 81
83 82 fork_of_fork = backend.create_fork()
84 83 Session().commit()
85 84
86 85 RepoModel().delete(repo=repo, forks='detach')
87 86 Session().commit()
88 87
89 88 assert Repository.get_by_repo_name(repo_name=repo.repo_name) is None
90 89 assert (
91 90 Repository.get_by_repo_name(repo_name=fork.repo_name) is not None)
92 91 assert (
93 92 Repository.get_by_repo_name(repo_name=fork_of_fork.repo_name)
94 93 is not None)
95 94
96 95 @pytest.mark.parametrize("filename, expected", [
97 96 ("README", True),
98 97 ("README.rst", False),
99 98 ])
100 99 def test_filenode_is_link(self, vcsbackend, filename, expected):
101 100 repo = vcsbackend.repo
102 101 assert repo.get_commit().is_link(filename) is expected
103 102
104 103 def test_get_commit(self, backend):
105 104 backend.repo.get_commit()
106 105
107 106 def test_get_changeset_is_deprecated(self, backend):
108 107 repo = backend.repo
109 108 pytest.deprecated_call(repo.get_changeset)
110 109
111 110 def test_clone_url_encrypted_value(self, backend):
112 111 repo = backend.create_repo()
113 112 Session().commit()
114 113
115 114 repo.clone_url = 'https://marcink:qweqwe@code.rhodecode.com'
116 115 Session().add(repo)
117 116 Session().commit()
118 117
119 118 assert repo.clone_url == 'https://marcink:qweqwe@code.rhodecode.com'
120 119
121 120 @pytest.mark.backends("git", "svn")
122 121 def test_create_filesystem_repo_installs_hooks(self, tmpdir, backend):
123 122 hook_methods = {
124 123 'git': 'install_git_hook',
125 124 'svn': 'install_svn_hooks'
126 125 }
127 126 repo = backend.create_repo()
128 127 repo_name = repo.repo_name
129 128 model = RepoModel()
130 129 repo_location = tempfile.mkdtemp()
131 130 model.repos_path = repo_location
132 131 method = hook_methods[backend.alias]
133 132 with mock.patch.object(ScmModel, method) as hooks_mock:
134 133 model._create_filesystem_repo(
135 134 repo_name, backend.alias, repo_group='', clone_uri=None)
136 135 assert hooks_mock.call_count == 1
137 136 hook_args, hook_kwargs = hooks_mock.call_args
138 137 assert hook_args[0].name == repo_name
139 138
140 139 @pytest.mark.parametrize("use_global_config, repo_name_passed", [
141 140 (True, False),
142 141 (False, True)
143 142 ])
144 143 def test_per_repo_config_is_generated_during_filesystem_repo_creation(
145 144 self, tmpdir, backend, use_global_config, repo_name_passed):
146 145 repo_name = 'test-{}-repo-{}'.format(backend.alias, use_global_config)
147 146 config = make_db_config()
148 147 model = RepoModel()
149 148 with mock.patch('rhodecode.model.repo.make_db_config') as config_mock:
150 149 config_mock.return_value = config
151 150 model._create_filesystem_repo(
152 151 repo_name, backend.alias, repo_group='', clone_uri=None,
153 152 use_global_config=use_global_config)
154 153 expected_repo_name = repo_name if repo_name_passed else None
155 154 expected_call = mock.call(clear_session=False, repo=expected_repo_name)
156 155 assert expected_call in config_mock.call_args_list
157 156
158 157 def test_update_commit_cache_with_config(serf, backend):
159 158 repo = backend.create_repo()
160 159 with mock.patch('rhodecode.model.db.Repository.scm_instance') as scm:
161 160 scm_instance = mock.Mock()
162 161 scm_instance.get_commit.return_value = {
163 162 'raw_id': 40*'0',
164 163 'revision': 1
165 164 }
166 165 scm.return_value = scm_instance
167 166 repo.update_commit_cache()
168 167 scm.assert_called_with(cache=False, config=None)
169 168 config = {'test': 'config'}
170 169 repo.update_commit_cache(config=config)
171 170 scm.assert_called_with(
172 171 cache=False, config=config)
173 172
174 173
175 174 class TestGetUsers(object):
176 175 def test_returns_active_users(self, backend, user_util):
177 176 for i in range(4):
178 177 is_active = i % 2 == 0
179 178 user_util.create_user(active=is_active, lastname='Fake user')
180 179
181 180 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
182 181 users = RepoModel().get_users()
183 182 fake_users = [u for u in users if u['last_name'] == 'Fake user']
184 183 assert len(fake_users) == 2
185 184
186 185 expected_keys = (
187 186 'id', 'first_name', 'last_name', 'username', 'icon_link',
188 187 'value_display', 'value', 'value_type')
189 188 for user in users:
190 189 assert user['value_type'] is 'user'
191 190 for key in expected_keys:
192 191 assert key in user
193 192
194 193 def test_returns_user_filtered_by_last_name(self, backend, user_util):
195 194 keywords = ('aBc', u'ΓΌnicode')
196 195 for keyword in keywords:
197 196 for i in range(2):
198 197 user_util.create_user(
199 198 active=True, lastname=u'Fake {} user'.format(keyword))
200 199
201 200 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
202 201 keyword = keywords[1].lower()
203 202 users = RepoModel().get_users(name_contains=keyword)
204 203
205 204 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
206 205 assert len(fake_users) == 2
207 206 for user in fake_users:
208 207 assert user['last_name'] == safe_unicode('Fake ΓΌnicode user')
209 208
210 209 def test_returns_user_filtered_by_first_name(self, backend, user_util):
211 210 created_users = []
212 211 keywords = ('aBc', u'ΓΌnicode')
213 212 for keyword in keywords:
214 213 for i in range(2):
215 214 created_users.append(user_util.create_user(
216 215 active=True, lastname='Fake user',
217 216 firstname=u'Fake {} user'.format(keyword)))
218 217
219 218 keyword = keywords[1].lower()
220 219 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
221 220 users = RepoModel().get_users(name_contains=keyword)
222 221
223 222 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
224 223 assert len(fake_users) == 2
225 224 for user in fake_users:
226 225 assert user['first_name'] == safe_unicode('Fake ΓΌnicode user')
227 226
228 227 def test_returns_user_filtered_by_username(self, backend, user_util):
229 228 created_users = []
230 229 for i in range(5):
231 230 created_users.append(user_util.create_user(
232 231 active=True, lastname='Fake user'))
233 232
234 233 user_filter = created_users[-1].username[-2:]
235 234 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
236 235 users = RepoModel().get_users(name_contains=user_filter)
237 236
238 237 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
239 238 assert len(fake_users) == 1
240 239 assert fake_users[0]['username'] == created_users[-1].username
241 240
242 241 def test_returns_limited_user_list(self, backend, user_util):
243 242 created_users = []
244 243 for i in range(5):
245 244 created_users.append(user_util.create_user(
246 245 active=True, lastname='Fake user'))
247 246
248 247 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
249 248 users = RepoModel().get_users(name_contains='Fake', limit=3)
250 249
251 250 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
252 251 assert len(fake_users) == 3
253 252
254
255 class TestGetUserGroups(object):
256 def test_returns_filtered_list(self, backend, user_util):
257 created_groups = []
258 for i in range(4):
259 created_groups.append(
260 user_util.create_user_group(users_group_active=True))
261
262 group_filter = created_groups[-1].users_group_name[-2:]
263 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
264 with self._patch_user_group_list():
265 groups = RepoModel().get_user_groups(group_filter)
266
267 fake_groups = [
268 u for u in groups if u['value'].startswith('test_returns')]
269 assert len(fake_groups) == 1
270 assert fake_groups[0]['value'] == created_groups[-1].users_group_name
271 assert fake_groups[0]['value_display'].startswith(
272 'Group: test_returns')
273
274 def test_returns_limited_list(self, backend, user_util):
275 created_groups = []
276 for i in range(3):
277 created_groups.append(
278 user_util.create_user_group(users_group_active=True))
279 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
280 with self._patch_user_group_list():
281 groups = RepoModel().get_user_groups('test_returns')
282
283 fake_groups = [
284 u for u in groups if u['value'].startswith('test_returns')]
285 assert len(fake_groups) == 3
286
287 def test_returns_active_user_groups(self, backend, user_util):
288 for i in range(4):
289 is_active = i % 2 == 0
290 user_util.create_user_group(users_group_active=is_active)
291 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
292 with self._patch_user_group_list():
293 groups = RepoModel().get_user_groups()
294 expected = ('id', 'icon_link', 'value_display', 'value', 'value_type')
295 for group in groups:
296 assert group['value_type'] is 'user_group'
297 for key in expected:
298 assert key in group
299
300 fake_groups = [
301 u for u in groups if u['value'].startswith('test_returns')]
302 assert len(fake_groups) == 2
303 for user in fake_groups:
304 assert user['value_display'].startswith('Group: test_returns')
305
306 def _patch_user_group_list(self):
307 def side_effect(group_list, perm_set):
308 return group_list
309 return mock.patch(
310 'rhodecode.model.repo.UserGroupList', side_effect=side_effect)
@@ -1,161 +1,219 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 mock
22 22 import pytest
23 23
24 24 from rhodecode.model.db import User
25 25 from rhodecode.tests import TEST_USER_REGULAR_LOGIN
26 26 from rhodecode.tests.fixture import Fixture
27 27 from rhodecode.model.user_group import UserGroupModel
28 28 from rhodecode.model.meta import Session
29 29
30 30
31 31 fixture = Fixture()
32 32
33 33
34 34 def teardown_module(self):
35 35 _delete_all_user_groups()
36 36
37 37
38 class TestGetUserGroups(object):
39 def test_returns_filtered_list(self, backend, user_util):
40 created_groups = []
41 for i in range(4):
42 created_groups.append(
43 user_util.create_user_group(users_group_active=True))
44
45 group_filter = created_groups[-1].users_group_name[-2:]
46 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
47 with self._patch_user_group_list():
48 groups = UserGroupModel().get_user_groups(group_filter)
49
50 fake_groups = [
51 u for u in groups if u['value'].startswith('test_returns')]
52 assert len(fake_groups) == 1
53 assert fake_groups[0]['value'] == created_groups[-1].users_group_name
54 assert fake_groups[0]['value_display'].startswith(
55 'Group: test_returns')
56
57 def test_returns_limited_list(self, backend, user_util):
58 created_groups = []
59 for i in range(3):
60 created_groups.append(
61 user_util.create_user_group(users_group_active=True))
62 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
63 with self._patch_user_group_list():
64 groups = UserGroupModel().get_user_groups('test_returns')
65
66 fake_groups = [
67 u for u in groups if u['value'].startswith('test_returns')]
68 assert len(fake_groups) == 3
69
70 def test_returns_active_user_groups(self, backend, user_util):
71 for i in range(4):
72 is_active = i % 2 == 0
73 user_util.create_user_group(users_group_active=is_active)
74 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
75 with self._patch_user_group_list():
76 groups = UserGroupModel().get_user_groups()
77 expected = ('id', 'icon_link', 'value_display', 'value', 'value_type')
78 for group in groups:
79 assert group['value_type'] is 'user_group'
80 for key in expected:
81 assert key in group
82
83 fake_groups = [
84 u for u in groups if u['value'].startswith('test_returns')]
85 assert len(fake_groups) == 2
86 for user in fake_groups:
87 assert user['value_display'].startswith('Group: test_returns')
88
89 def _patch_user_group_list(self):
90 def side_effect(group_list, perm_set):
91 return group_list
92 return mock.patch(
93 'rhodecode.model.user_group.UserGroupList', side_effect=side_effect)
94
95
38 96 @pytest.mark.parametrize(
39 97 "pre_existing, regular_should_be, external_should_be, groups, "
40 98 "expected", [
41 99 ([], [], [], [], []),
42 100 # no changes of regular
43 101 ([], ['regular'], [], [], ['regular']),
44 102 # not added to regular group
45 103 (['some_other'], [], [], ['some_other'], []),
46 104 (
47 105 [], ['regular'], ['container'], ['container'],
48 106 ['regular', 'container']
49 107 ),
50 108 (
51 109 [], ['regular'], [], ['container', 'container2'],
52 110 ['regular', 'container', 'container2']
53 111 ),
54 112 # remove not used
55 113 ([], ['regular'], ['other'], [], ['regular']),
56 114 (
57 115 ['some_other'], ['regular'], ['other', 'container'],
58 116 ['container', 'container2'],
59 117 ['regular', 'container', 'container2']
60 118 ),
61 119 ])
62 120 def test_enforce_groups(pre_existing, regular_should_be,
63 121 external_should_be, groups, expected, backend_hg):
64 122 # TODO: anderson: adding backend_hg fixture so it sets up the database
65 123 # for when running this file alone
66 124 _delete_all_user_groups()
67 125
68 126 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
69 127 for gr in pre_existing:
70 128 gr = fixture.create_user_group(gr)
71 129 Session().commit()
72 130
73 131 # make sure use is just in those groups
74 132 for gr in regular_should_be:
75 133 gr = fixture.create_user_group(gr)
76 134 Session().commit()
77 135 UserGroupModel().add_user_to_group(gr, user)
78 136 Session().commit()
79 137
80 138 # now special external groups created by auth plugins
81 139 for gr in external_should_be:
82 140 gr = fixture.create_user_group(
83 141 gr, user_group_data={'extern_type': 'container'})
84 142 Session().commit()
85 143 UserGroupModel().add_user_to_group(gr, user)
86 144 Session().commit()
87 145
88 146 UserGroupModel().enforce_groups(user, groups, 'container')
89 147 Session().commit()
90 148
91 149 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
92 150 in_groups = user.group_member
93 151
94 152 expected.sort()
95 153 assert (
96 154 expected == sorted(x.users_group.users_group_name for x in in_groups))
97 155
98 156
99 157 def _delete_all_user_groups():
100 158 for gr in UserGroupModel.get_all():
101 159 fixture.destroy_user_group(gr)
102 160 Session().commit()
103 161
104 162
105 163 def test_add_and_remove_user_from_group(user_regular, user_util):
106 164 user_group = user_util.create_user_group()
107 165 assert user_group.members == []
108 166 UserGroupModel().add_user_to_group(user_group, user_regular)
109 167 Session().commit()
110 168 assert user_group.members[0].user == user_regular
111 169 UserGroupModel().remove_user_from_group(user_group, user_regular)
112 170 Session().commit()
113 171 assert user_group.members == []
114 172
115 173
116 174 @pytest.mark.parametrize('data, expected', [
117 175 ([], []),
118 176 ([{"member_user_id": 1, "type": "new"}], [1]),
119 177 ([{"member_user_id": 1, "type": "new"},
120 178 {"member_user_id": 1, "type": "existing"}], [1]),
121 179 ([{"member_user_id": 1, "type": "new"},
122 180 {"member_user_id": 2, "type": "new"},
123 181 {"member_user_id": 3, "type": "remove"}], [1, 2])
124 182 ])
125 183 def test_clean_members_data(data, expected):
126 184 cleaned = UserGroupModel()._clean_members_data(data)
127 185 assert cleaned == expected
128 186
129 187
130 188 def _create_test_members():
131 189 members = []
132 190 for member_number in range(3):
133 191 member = mock.Mock()
134 192 member.user_id = member_number + 1
135 193 member.user.user_id = member_number + 1
136 194 members.append(member)
137 195 return members
138 196
139 197
140 198 def test_get_added_and_removed_users():
141 199 members = _create_test_members()
142 200 mock_user_group = mock.Mock()
143 201 mock_user_group.members = [members[0], members[1]]
144 202 new_users_list = [members[1].user.user_id, members[2].user.user_id]
145 203 model = UserGroupModel()
146 204
147 205 added, removed = model._get_added_and_removed_user_ids(
148 206 mock_user_group, new_users_list)
149 207
150 208 assert added == [members[2].user.user_id]
151 209 assert removed == [members[0].user.user_id]
152 210
153 211
154 212 def test_set_users_as_members_and_find_user_in_group(
155 213 user_util, user_regular, user_admin):
156 214 user_group = user_util.create_user_group()
157 215 assert len(user_group.members) == 0
158 216 user_list = [user_regular.user_id, user_admin.user_id]
159 217 UserGroupModel()._set_users_as_members(user_group, user_list)
160 218 assert len(user_group.members) == 2
161 219 assert UserGroupModel()._find_user_in_group(user_regular, user_group)
General Comments 0
You need to be logged in to leave comments. Login now