##// END OF EJS Templates
admin-users: add view for user groups managment...
Bartłomiej Wołyńczyk -
r1556:9ac012a6 default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

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