##// END OF EJS Templates
tests: added tests for permission update views to catch obvious form errors.
marcink -
r2827:e2835069 default
parent child Browse files
Show More
@@ -0,0 +1,77 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22
23 from rhodecode.tests.utils import permission_update_data_generator
24
25
26 def route_path(name, params=None, **kwargs):
27 import urllib
28
29 base_url = {
30 'edit_repo_perms': '/{repo_name}/settings/permissions'
31 # update is the same url
32 }[name].format(**kwargs)
33
34 if params:
35 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
36 return base_url
37
38
39 @pytest.mark.usefixtures("app")
40 class TestRepoPermissionsView(object):
41
42 def test_edit_perms_view(self, user_util, autologin_user):
43 repo = user_util.create_repo()
44 self.app.get(
45 route_path('edit_repo_perms',
46 repo_name=repo.repo_name), status=200)
47
48 def test_update_permissions(self, csrf_token, user_util):
49 repo = user_util.create_repo()
50 repo_name = repo.repo_name
51 user = user_util.create_user()
52 user_id = user.user_id
53 username = user.username
54
55 # grant new
56 form_data = permission_update_data_generator(
57 csrf_token,
58 default='repository.write',
59 grant=[(user_id, 'repository.write', username, 'user')])
60
61 response = self.app.post(
62 route_path('edit_repo_perms',
63 repo_name=repo_name), form_data).follow()
64
65 assert 'Repository permissions updated' in response
66
67 # revoke given
68 form_data = permission_update_data_generator(
69 csrf_token,
70 default='repository.read',
71 revoke=[(user_id, 'user')])
72
73 response = self.app.post(
74 route_path('edit_repo_perms',
75 repo_name=repo_name), form_data).follow()
76
77 assert 'Repository permissions updated' in response
@@ -0,0 +1,80 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22
23 from rhodecode.tests.utils import permission_update_data_generator
24
25
26 def route_path(name, params=None, **kwargs):
27 import urllib
28 from rhodecode.apps._base import ADMIN_PREFIX
29
30 base_url = {
31 'edit_user_group_perms':
32 ADMIN_PREFIX + '/user_groups/{user_group_id}/edit/permissions',
33 'edit_user_group_perms_update':
34 ADMIN_PREFIX + '/user_groups/{user_group_id}/edit/permissions/update',
35 }[name].format(**kwargs)
36
37 if params:
38 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
39 return base_url
40
41
42 @pytest.mark.usefixtures("app")
43 class TestUserGroupPermissionsView(object):
44
45 def test_edit_perms_view(self, user_util, autologin_user):
46 user_group = user_util.create_user_group()
47 self.app.get(
48 route_path('edit_user_group_perms',
49 user_group_id=user_group.users_group_id), status=200)
50
51 def test_update_permissions(self, csrf_token, user_util):
52 user_group = user_util.create_user_group()
53 user_group_id = user_group.users_group_id
54 user = user_util.create_user()
55 user_id = user.user_id
56 username = user.username
57
58 # grant new
59 form_data = permission_update_data_generator(
60 csrf_token,
61 default='usergroup.write',
62 grant=[(user_id, 'usergroup.write', username, 'user')])
63
64 response = self.app.post(
65 route_path('edit_user_group_perms_update',
66 user_group_id=user_group_id), form_data).follow()
67
68 assert 'User Group permissions updated' in response
69
70 # revoke given
71 form_data = permission_update_data_generator(
72 csrf_token,
73 default='usergroup.read',
74 revoke=[(user_id, 'user')])
75
76 response = self.app.post(
77 route_path('edit_user_group_perms_update',
78 user_group_id=user_group_id), form_data).follow()
79
80 assert 'User Group permissions updated' in response
@@ -1,49 +1,86 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.tests.utils import permission_update_data_generator
24
23
25
24 def route_path(name, params=None, **kwargs):
26 def route_path(name, params=None, **kwargs):
25 import urllib
27 import urllib
26
28
27 base_url = {
29 base_url = {
28 'edit_repo_group_perms':
30 'edit_repo_group_perms':
29 '/{repo_group_name:}/_settings/permissions',
31 '/{repo_group_name:}/_settings/permissions',
30 'edit_repo_group_perms_update':
32 'edit_repo_group_perms_update':
31 '/{repo_group_name}/_settings/permissions/update',
33 '/{repo_group_name}/_settings/permissions/update',
32 }[name].format(**kwargs)
34 }[name].format(**kwargs)
33
35
34 if params:
36 if params:
35 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
37 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
36 return base_url
38 return base_url
37
39
38
40
39 @pytest.mark.usefixtures("app")
41 @pytest.mark.usefixtures("app")
40 class TestRepoGroupsPermissionsView(object):
42 class TestRepoGroupPermissionsView(object):
41
43
42 def test_edit_repo_group_perms(self, user_util, autologin_user):
44 def test_edit_perms_view(self, user_util, autologin_user):
43 repo_group = user_util.create_repo_group()
45 repo_group = user_util.create_repo_group()
46
44 self.app.get(
47 self.app.get(
45 route_path('edit_repo_group_perms',
48 route_path('edit_repo_group_perms',
46 repo_group_name=repo_group.group_name), status=200)
49 repo_group_name=repo_group.group_name), status=200)
47
50
48 def test_update_permissions(self):
51 def test_update_permissions(self, csrf_token, user_util):
49 pass
52 repo_group = user_util.create_repo_group()
53 repo_group_name = repo_group.group_name
54 user = user_util.create_user()
55 user_id = user.user_id
56 username = user.username
57
58 # grant new
59 form_data = permission_update_data_generator(
60 csrf_token,
61 default='group.write',
62 grant=[(user_id, 'group.write', username, 'user')])
63
64 # recursive flag required for repo groups
65 form_data.extend([('recursive', u'none')])
66
67 response = self.app.post(
68 route_path('edit_repo_group_perms_update',
69 repo_group_name=repo_group_name), form_data).follow()
70
71 assert 'Repository Group permissions updated' in response
72
73 # revoke given
74 form_data = permission_update_data_generator(
75 csrf_token,
76 default='group.read',
77 revoke=[(user_id, 'user')])
78
79 # recursive flag required for repo groups
80 form_data.extend([('recursive', u'none')])
81
82 response = self.app.post(
83 route_path('edit_repo_group_perms_update',
84 repo_group_name=repo_group_name), form_data).follow()
85
86 assert 'Repository Group permissions updated' in response
@@ -1,1041 +1,1050 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import re
22 import re
23 import shutil
23 import shutil
24 import time
24 import time
25 import logging
25 import logging
26 import traceback
26 import traceback
27 import datetime
27 import datetime
28
28
29 from pyramid.threadlocal import get_current_request
29 from pyramid.threadlocal import get_current_request
30 from zope.cachedescriptors.property import Lazy as LazyProperty
30 from zope.cachedescriptors.property import Lazy as LazyProperty
31
31
32 from rhodecode import events
32 from rhodecode import events
33 from rhodecode.lib.auth import HasUserGroupPermissionAny
33 from rhodecode.lib.auth import HasUserGroupPermissionAny
34 from rhodecode.lib.caching_query import FromCache
34 from rhodecode.lib.caching_query import FromCache
35 from rhodecode.lib.exceptions import AttachedForksError
35 from rhodecode.lib.exceptions import AttachedForksError
36 from rhodecode.lib.hooks_base import log_delete_repository
36 from rhodecode.lib.hooks_base import log_delete_repository
37 from rhodecode.lib.user_log_filter import user_log_filter
37 from rhodecode.lib.user_log_filter import user_log_filter
38 from rhodecode.lib.utils import make_db_config
38 from rhodecode.lib.utils import make_db_config
39 from rhodecode.lib.utils2 import (
39 from rhodecode.lib.utils2 import (
40 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
40 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
41 get_current_rhodecode_user, safe_int, datetime_to_time,
41 get_current_rhodecode_user, safe_int, datetime_to_time,
42 action_logger_generic)
42 action_logger_generic)
43 from rhodecode.lib.vcs.backends import get_backend
43 from rhodecode.lib.vcs.backends import get_backend
44 from rhodecode.model import BaseModel
44 from rhodecode.model import BaseModel
45 from rhodecode.model.db import (
45 from rhodecode.model.db import (
46 _hash_key, joinedload, or_, Repository, UserRepoToPerm, UserGroupRepoToPerm,
46 _hash_key, joinedload, or_, Repository, UserRepoToPerm, UserGroupRepoToPerm,
47 UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission,
47 UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission,
48 Statistics, UserGroup, RepoGroup, RepositoryField, UserLog)
48 Statistics, UserGroup, RepoGroup, RepositoryField, UserLog)
49
49
50 from rhodecode.model.settings import VcsSettingsModel
50 from rhodecode.model.settings import VcsSettingsModel
51
51
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 class RepoModel(BaseModel):
56 class RepoModel(BaseModel):
57
57
58 cls = Repository
58 cls = Repository
59
59
60 def _get_user_group(self, users_group):
60 def _get_user_group(self, users_group):
61 return self._get_instance(UserGroup, users_group,
61 return self._get_instance(UserGroup, users_group,
62 callback=UserGroup.get_by_group_name)
62 callback=UserGroup.get_by_group_name)
63
63
64 def _get_repo_group(self, repo_group):
64 def _get_repo_group(self, repo_group):
65 return self._get_instance(RepoGroup, repo_group,
65 return self._get_instance(RepoGroup, repo_group,
66 callback=RepoGroup.get_by_group_name)
66 callback=RepoGroup.get_by_group_name)
67
67
68 def _create_default_perms(self, repository, private):
68 def _create_default_perms(self, repository, private):
69 # create default permission
69 # create default permission
70 default = 'repository.read'
70 default = 'repository.read'
71 def_user = User.get_default_user()
71 def_user = User.get_default_user()
72 for p in def_user.user_perms:
72 for p in def_user.user_perms:
73 if p.permission.permission_name.startswith('repository.'):
73 if p.permission.permission_name.startswith('repository.'):
74 default = p.permission.permission_name
74 default = p.permission.permission_name
75 break
75 break
76
76
77 default_perm = 'repository.none' if private else default
77 default_perm = 'repository.none' if private else default
78
78
79 repo_to_perm = UserRepoToPerm()
79 repo_to_perm = UserRepoToPerm()
80 repo_to_perm.permission = Permission.get_by_key(default_perm)
80 repo_to_perm.permission = Permission.get_by_key(default_perm)
81
81
82 repo_to_perm.repository = repository
82 repo_to_perm.repository = repository
83 repo_to_perm.user_id = def_user.user_id
83 repo_to_perm.user_id = def_user.user_id
84
84
85 return repo_to_perm
85 return repo_to_perm
86
86
87 @LazyProperty
87 @LazyProperty
88 def repos_path(self):
88 def repos_path(self):
89 """
89 """
90 Gets the repositories root path from database
90 Gets the repositories root path from database
91 """
91 """
92 settings_model = VcsSettingsModel(sa=self.sa)
92 settings_model = VcsSettingsModel(sa=self.sa)
93 return settings_model.get_repos_location()
93 return settings_model.get_repos_location()
94
94
95 def get(self, repo_id, cache=False):
95 def get(self, repo_id, cache=False):
96 repo = self.sa.query(Repository) \
96 repo = self.sa.query(Repository) \
97 .filter(Repository.repo_id == repo_id)
97 .filter(Repository.repo_id == repo_id)
98
98
99 if cache:
99 if cache:
100 repo = repo.options(
100 repo = repo.options(
101 FromCache("sql_cache_short", "get_repo_%s" % repo_id))
101 FromCache("sql_cache_short", "get_repo_%s" % repo_id))
102 return repo.scalar()
102 return repo.scalar()
103
103
104 def get_repo(self, repository):
104 def get_repo(self, repository):
105 return self._get_repo(repository)
105 return self._get_repo(repository)
106
106
107 def get_by_repo_name(self, repo_name, cache=False):
107 def get_by_repo_name(self, repo_name, cache=False):
108 repo = self.sa.query(Repository) \
108 repo = self.sa.query(Repository) \
109 .filter(Repository.repo_name == repo_name)
109 .filter(Repository.repo_name == repo_name)
110
110
111 if cache:
111 if cache:
112 name_key = _hash_key(repo_name)
112 name_key = _hash_key(repo_name)
113 repo = repo.options(
113 repo = repo.options(
114 FromCache("sql_cache_short", "get_repo_%s" % name_key))
114 FromCache("sql_cache_short", "get_repo_%s" % name_key))
115 return repo.scalar()
115 return repo.scalar()
116
116
117 def _extract_id_from_repo_name(self, repo_name):
117 def _extract_id_from_repo_name(self, repo_name):
118 if repo_name.startswith('/'):
118 if repo_name.startswith('/'):
119 repo_name = repo_name.lstrip('/')
119 repo_name = repo_name.lstrip('/')
120 by_id_match = re.match(r'^_(\d{1,})', repo_name)
120 by_id_match = re.match(r'^_(\d{1,})', repo_name)
121 if by_id_match:
121 if by_id_match:
122 return by_id_match.groups()[0]
122 return by_id_match.groups()[0]
123
123
124 def get_repo_by_id(self, repo_name):
124 def get_repo_by_id(self, repo_name):
125 """
125 """
126 Extracts repo_name by id from special urls.
126 Extracts repo_name by id from special urls.
127 Example url is _11/repo_name
127 Example url is _11/repo_name
128
128
129 :param repo_name:
129 :param repo_name:
130 :return: repo object if matched else None
130 :return: repo object if matched else None
131 """
131 """
132
132
133 try:
133 try:
134 _repo_id = self._extract_id_from_repo_name(repo_name)
134 _repo_id = self._extract_id_from_repo_name(repo_name)
135 if _repo_id:
135 if _repo_id:
136 return self.get(_repo_id)
136 return self.get(_repo_id)
137 except Exception:
137 except Exception:
138 log.exception('Failed to extract repo_name from URL')
138 log.exception('Failed to extract repo_name from URL')
139
139
140 return None
140 return None
141
141
142 def get_repos_for_root(self, root, traverse=False):
142 def get_repos_for_root(self, root, traverse=False):
143 if traverse:
143 if traverse:
144 like_expression = u'{}%'.format(safe_unicode(root))
144 like_expression = u'{}%'.format(safe_unicode(root))
145 repos = Repository.query().filter(
145 repos = Repository.query().filter(
146 Repository.repo_name.like(like_expression)).all()
146 Repository.repo_name.like(like_expression)).all()
147 else:
147 else:
148 if root and not isinstance(root, RepoGroup):
148 if root and not isinstance(root, RepoGroup):
149 raise ValueError(
149 raise ValueError(
150 'Root must be an instance '
150 'Root must be an instance '
151 'of RepoGroup, got:{} instead'.format(type(root)))
151 'of RepoGroup, got:{} instead'.format(type(root)))
152 repos = Repository.query().filter(Repository.group == root).all()
152 repos = Repository.query().filter(Repository.group == root).all()
153 return repos
153 return repos
154
154
155 def get_url(self, repo, request=None, permalink=False):
155 def get_url(self, repo, request=None, permalink=False):
156 if not request:
156 if not request:
157 request = get_current_request()
157 request = get_current_request()
158
158
159 if not request:
159 if not request:
160 return
160 return
161
161
162 if permalink:
162 if permalink:
163 return request.route_url(
163 return request.route_url(
164 'repo_summary', repo_name='_{}'.format(safe_str(repo.repo_id)))
164 'repo_summary', repo_name='_{}'.format(safe_str(repo.repo_id)))
165 else:
165 else:
166 return request.route_url(
166 return request.route_url(
167 'repo_summary', repo_name=safe_str(repo.repo_name))
167 'repo_summary', repo_name=safe_str(repo.repo_name))
168
168
169 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
169 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
170 if not request:
170 if not request:
171 request = get_current_request()
171 request = get_current_request()
172
172
173 if not request:
173 if not request:
174 return
174 return
175
175
176 if permalink:
176 if permalink:
177 return request.route_url(
177 return request.route_url(
178 'repo_commit', repo_name=safe_str(repo.repo_id),
178 'repo_commit', repo_name=safe_str(repo.repo_id),
179 commit_id=commit_id)
179 commit_id=commit_id)
180
180
181 else:
181 else:
182 return request.route_url(
182 return request.route_url(
183 'repo_commit', repo_name=safe_str(repo.repo_name),
183 'repo_commit', repo_name=safe_str(repo.repo_name),
184 commit_id=commit_id)
184 commit_id=commit_id)
185
185
186 def get_repo_log(self, repo, filter_term):
186 def get_repo_log(self, repo, filter_term):
187 repo_log = UserLog.query()\
187 repo_log = UserLog.query()\
188 .filter(or_(UserLog.repository_id == repo.repo_id,
188 .filter(or_(UserLog.repository_id == repo.repo_id,
189 UserLog.repository_name == repo.repo_name))\
189 UserLog.repository_name == repo.repo_name))\
190 .options(joinedload(UserLog.user))\
190 .options(joinedload(UserLog.user))\
191 .options(joinedload(UserLog.repository))\
191 .options(joinedload(UserLog.repository))\
192 .order_by(UserLog.action_date.desc())
192 .order_by(UserLog.action_date.desc())
193
193
194 repo_log = user_log_filter(repo_log, filter_term)
194 repo_log = user_log_filter(repo_log, filter_term)
195 return repo_log
195 return repo_log
196
196
197 @classmethod
197 @classmethod
198 def update_repoinfo(cls, repositories=None):
198 def update_repoinfo(cls, repositories=None):
199 if not repositories:
199 if not repositories:
200 repositories = Repository.getAll()
200 repositories = Repository.getAll()
201 for repo in repositories:
201 for repo in repositories:
202 repo.update_commit_cache()
202 repo.update_commit_cache()
203
203
204 def get_repos_as_dict(self, repo_list=None, admin=False,
204 def get_repos_as_dict(self, repo_list=None, admin=False,
205 super_user_actions=False):
205 super_user_actions=False):
206 _render = get_current_request().get_partial_renderer(
206 _render = get_current_request().get_partial_renderer(
207 'rhodecode:templates/data_table/_dt_elements.mako')
207 'rhodecode:templates/data_table/_dt_elements.mako')
208 c = _render.get_call_context()
208 c = _render.get_call_context()
209
209
210 def quick_menu(repo_name):
210 def quick_menu(repo_name):
211 return _render('quick_menu', repo_name)
211 return _render('quick_menu', repo_name)
212
212
213 def repo_lnk(name, rtype, rstate, private, fork_of):
213 def repo_lnk(name, rtype, rstate, private, fork_of):
214 return _render('repo_name', name, rtype, rstate, private, fork_of,
214 return _render('repo_name', name, rtype, rstate, private, fork_of,
215 short_name=not admin, admin=False)
215 short_name=not admin, admin=False)
216
216
217 def last_change(last_change):
217 def last_change(last_change):
218 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
218 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
219 last_change = last_change + datetime.timedelta(seconds=
219 last_change = last_change + datetime.timedelta(seconds=
220 (datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
220 (datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
221 return _render("last_change", last_change)
221 return _render("last_change", last_change)
222
222
223 def rss_lnk(repo_name):
223 def rss_lnk(repo_name):
224 return _render("rss", repo_name)
224 return _render("rss", repo_name)
225
225
226 def atom_lnk(repo_name):
226 def atom_lnk(repo_name):
227 return _render("atom", repo_name)
227 return _render("atom", repo_name)
228
228
229 def last_rev(repo_name, cs_cache):
229 def last_rev(repo_name, cs_cache):
230 return _render('revision', repo_name, cs_cache.get('revision'),
230 return _render('revision', repo_name, cs_cache.get('revision'),
231 cs_cache.get('raw_id'), cs_cache.get('author'),
231 cs_cache.get('raw_id'), cs_cache.get('author'),
232 cs_cache.get('message'), cs_cache.get('date'))
232 cs_cache.get('message'), cs_cache.get('date'))
233
233
234 def desc(desc):
234 def desc(desc):
235 return _render('repo_desc', desc, c.visual.stylify_metatags)
235 return _render('repo_desc', desc, c.visual.stylify_metatags)
236
236
237 def state(repo_state):
237 def state(repo_state):
238 return _render("repo_state", repo_state)
238 return _render("repo_state", repo_state)
239
239
240 def repo_actions(repo_name):
240 def repo_actions(repo_name):
241 return _render('repo_actions', repo_name, super_user_actions)
241 return _render('repo_actions', repo_name, super_user_actions)
242
242
243 def user_profile(username):
243 def user_profile(username):
244 return _render('user_profile', username)
244 return _render('user_profile', username)
245
245
246 repos_data = []
246 repos_data = []
247 for repo in repo_list:
247 for repo in repo_list:
248 cs_cache = repo.changeset_cache
248 cs_cache = repo.changeset_cache
249 row = {
249 row = {
250 "menu": quick_menu(repo.repo_name),
250 "menu": quick_menu(repo.repo_name),
251
251
252 "name": repo_lnk(repo.repo_name, repo.repo_type,
252 "name": repo_lnk(repo.repo_name, repo.repo_type,
253 repo.repo_state, repo.private, repo.fork),
253 repo.repo_state, repo.private, repo.fork),
254 "name_raw": repo.repo_name.lower(),
254 "name_raw": repo.repo_name.lower(),
255
255
256 "last_change": last_change(repo.last_db_change),
256 "last_change": last_change(repo.last_db_change),
257 "last_change_raw": datetime_to_time(repo.last_db_change),
257 "last_change_raw": datetime_to_time(repo.last_db_change),
258
258
259 "last_changeset": last_rev(repo.repo_name, cs_cache),
259 "last_changeset": last_rev(repo.repo_name, cs_cache),
260 "last_changeset_raw": cs_cache.get('revision'),
260 "last_changeset_raw": cs_cache.get('revision'),
261
261
262 "desc": desc(repo.description_safe),
262 "desc": desc(repo.description_safe),
263 "owner": user_profile(repo.user.username),
263 "owner": user_profile(repo.user.username),
264
264
265 "state": state(repo.repo_state),
265 "state": state(repo.repo_state),
266 "rss": rss_lnk(repo.repo_name),
266 "rss": rss_lnk(repo.repo_name),
267
267
268 "atom": atom_lnk(repo.repo_name),
268 "atom": atom_lnk(repo.repo_name),
269 }
269 }
270 if admin:
270 if admin:
271 row.update({
271 row.update({
272 "action": repo_actions(repo.repo_name),
272 "action": repo_actions(repo.repo_name),
273 })
273 })
274 repos_data.append(row)
274 repos_data.append(row)
275
275
276 return repos_data
276 return repos_data
277
277
278 def _get_defaults(self, repo_name):
278 def _get_defaults(self, repo_name):
279 """
279 """
280 Gets information about repository, and returns a dict for
280 Gets information about repository, and returns a dict for
281 usage in forms
281 usage in forms
282
282
283 :param repo_name:
283 :param repo_name:
284 """
284 """
285
285
286 repo_info = Repository.get_by_repo_name(repo_name)
286 repo_info = Repository.get_by_repo_name(repo_name)
287
287
288 if repo_info is None:
288 if repo_info is None:
289 return None
289 return None
290
290
291 defaults = repo_info.get_dict()
291 defaults = repo_info.get_dict()
292 defaults['repo_name'] = repo_info.just_name
292 defaults['repo_name'] = repo_info.just_name
293
293
294 groups = repo_info.groups_with_parents
294 groups = repo_info.groups_with_parents
295 parent_group = groups[-1] if groups else None
295 parent_group = groups[-1] if groups else None
296
296
297 # we use -1 as this is how in HTML, we mark an empty group
297 # we use -1 as this is how in HTML, we mark an empty group
298 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
298 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
299
299
300 keys_to_process = (
300 keys_to_process = (
301 {'k': 'repo_type', 'strip': False},
301 {'k': 'repo_type', 'strip': False},
302 {'k': 'repo_enable_downloads', 'strip': True},
302 {'k': 'repo_enable_downloads', 'strip': True},
303 {'k': 'repo_description', 'strip': True},
303 {'k': 'repo_description', 'strip': True},
304 {'k': 'repo_enable_locking', 'strip': True},
304 {'k': 'repo_enable_locking', 'strip': True},
305 {'k': 'repo_landing_rev', 'strip': True},
305 {'k': 'repo_landing_rev', 'strip': True},
306 {'k': 'clone_uri', 'strip': False},
306 {'k': 'clone_uri', 'strip': False},
307 {'k': 'push_uri', 'strip': False},
307 {'k': 'push_uri', 'strip': False},
308 {'k': 'repo_private', 'strip': True},
308 {'k': 'repo_private', 'strip': True},
309 {'k': 'repo_enable_statistics', 'strip': True}
309 {'k': 'repo_enable_statistics', 'strip': True}
310 )
310 )
311
311
312 for item in keys_to_process:
312 for item in keys_to_process:
313 attr = item['k']
313 attr = item['k']
314 if item['strip']:
314 if item['strip']:
315 attr = remove_prefix(item['k'], 'repo_')
315 attr = remove_prefix(item['k'], 'repo_')
316
316
317 val = defaults[attr]
317 val = defaults[attr]
318 if item['k'] == 'repo_landing_rev':
318 if item['k'] == 'repo_landing_rev':
319 val = ':'.join(defaults[attr])
319 val = ':'.join(defaults[attr])
320 defaults[item['k']] = val
320 defaults[item['k']] = val
321 if item['k'] == 'clone_uri':
321 if item['k'] == 'clone_uri':
322 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
322 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
323 if item['k'] == 'push_uri':
323 if item['k'] == 'push_uri':
324 defaults['push_uri_hidden'] = repo_info.push_uri_hidden
324 defaults['push_uri_hidden'] = repo_info.push_uri_hidden
325
325
326 # fill owner
326 # fill owner
327 if repo_info.user:
327 if repo_info.user:
328 defaults.update({'user': repo_info.user.username})
328 defaults.update({'user': repo_info.user.username})
329 else:
329 else:
330 replacement_user = User.get_first_super_admin().username
330 replacement_user = User.get_first_super_admin().username
331 defaults.update({'user': replacement_user})
331 defaults.update({'user': replacement_user})
332
332
333 return defaults
333 return defaults
334
334
335 def update(self, repo, **kwargs):
335 def update(self, repo, **kwargs):
336 try:
336 try:
337 cur_repo = self._get_repo(repo)
337 cur_repo = self._get_repo(repo)
338 source_repo_name = cur_repo.repo_name
338 source_repo_name = cur_repo.repo_name
339 if 'user' in kwargs:
339 if 'user' in kwargs:
340 cur_repo.user = User.get_by_username(kwargs['user'])
340 cur_repo.user = User.get_by_username(kwargs['user'])
341
341
342 if 'repo_group' in kwargs:
342 if 'repo_group' in kwargs:
343 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
343 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
344 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
344 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
345
345
346 update_keys = [
346 update_keys = [
347 (1, 'repo_description'),
347 (1, 'repo_description'),
348 (1, 'repo_landing_rev'),
348 (1, 'repo_landing_rev'),
349 (1, 'repo_private'),
349 (1, 'repo_private'),
350 (1, 'repo_enable_downloads'),
350 (1, 'repo_enable_downloads'),
351 (1, 'repo_enable_locking'),
351 (1, 'repo_enable_locking'),
352 (1, 'repo_enable_statistics'),
352 (1, 'repo_enable_statistics'),
353 (0, 'clone_uri'),
353 (0, 'clone_uri'),
354 (0, 'push_uri'),
354 (0, 'push_uri'),
355 (0, 'fork_id')
355 (0, 'fork_id')
356 ]
356 ]
357 for strip, k in update_keys:
357 for strip, k in update_keys:
358 if k in kwargs:
358 if k in kwargs:
359 val = kwargs[k]
359 val = kwargs[k]
360 if strip:
360 if strip:
361 k = remove_prefix(k, 'repo_')
361 k = remove_prefix(k, 'repo_')
362
362
363 setattr(cur_repo, k, val)
363 setattr(cur_repo, k, val)
364
364
365 new_name = cur_repo.get_new_name(kwargs['repo_name'])
365 new_name = cur_repo.get_new_name(kwargs['repo_name'])
366 cur_repo.repo_name = new_name
366 cur_repo.repo_name = new_name
367
367
368 # if private flag is set, reset default permission to NONE
368 # if private flag is set, reset default permission to NONE
369 if kwargs.get('repo_private'):
369 if kwargs.get('repo_private'):
370 EMPTY_PERM = 'repository.none'
370 EMPTY_PERM = 'repository.none'
371 RepoModel().grant_user_permission(
371 RepoModel().grant_user_permission(
372 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
372 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
373 )
373 )
374
374
375 # handle extra fields
375 # handle extra fields
376 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
376 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
377 kwargs):
377 kwargs):
378 k = RepositoryField.un_prefix_key(field)
378 k = RepositoryField.un_prefix_key(field)
379 ex_field = RepositoryField.get_by_key_name(
379 ex_field = RepositoryField.get_by_key_name(
380 key=k, repo=cur_repo)
380 key=k, repo=cur_repo)
381 if ex_field:
381 if ex_field:
382 ex_field.field_value = kwargs[field]
382 ex_field.field_value = kwargs[field]
383 self.sa.add(ex_field)
383 self.sa.add(ex_field)
384 cur_repo.updated_on = datetime.datetime.now()
384 cur_repo.updated_on = datetime.datetime.now()
385 self.sa.add(cur_repo)
385 self.sa.add(cur_repo)
386
386
387 if source_repo_name != new_name:
387 if source_repo_name != new_name:
388 # rename repository
388 # rename repository
389 self._rename_filesystem_repo(
389 self._rename_filesystem_repo(
390 old=source_repo_name, new=new_name)
390 old=source_repo_name, new=new_name)
391
391
392 return cur_repo
392 return cur_repo
393 except Exception:
393 except Exception:
394 log.error(traceback.format_exc())
394 log.error(traceback.format_exc())
395 raise
395 raise
396
396
397 def _create_repo(self, repo_name, repo_type, description, owner,
397 def _create_repo(self, repo_name, repo_type, description, owner,
398 private=False, clone_uri=None, repo_group=None,
398 private=False, clone_uri=None, repo_group=None,
399 landing_rev='rev:tip', fork_of=None,
399 landing_rev='rev:tip', fork_of=None,
400 copy_fork_permissions=False, enable_statistics=False,
400 copy_fork_permissions=False, enable_statistics=False,
401 enable_locking=False, enable_downloads=False,
401 enable_locking=False, enable_downloads=False,
402 copy_group_permissions=False,
402 copy_group_permissions=False,
403 state=Repository.STATE_PENDING):
403 state=Repository.STATE_PENDING):
404 """
404 """
405 Create repository inside database with PENDING state, this should be
405 Create repository inside database with PENDING state, this should be
406 only executed by create() repo. With exception of importing existing
406 only executed by create() repo. With exception of importing existing
407 repos
407 repos
408 """
408 """
409 from rhodecode.model.scm import ScmModel
409 from rhodecode.model.scm import ScmModel
410
410
411 owner = self._get_user(owner)
411 owner = self._get_user(owner)
412 fork_of = self._get_repo(fork_of)
412 fork_of = self._get_repo(fork_of)
413 repo_group = self._get_repo_group(safe_int(repo_group))
413 repo_group = self._get_repo_group(safe_int(repo_group))
414
414
415 try:
415 try:
416 repo_name = safe_unicode(repo_name)
416 repo_name = safe_unicode(repo_name)
417 description = safe_unicode(description)
417 description = safe_unicode(description)
418 # repo name is just a name of repository
418 # repo name is just a name of repository
419 # while repo_name_full is a full qualified name that is combined
419 # while repo_name_full is a full qualified name that is combined
420 # with name and path of group
420 # with name and path of group
421 repo_name_full = repo_name
421 repo_name_full = repo_name
422 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
422 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
423
423
424 new_repo = Repository()
424 new_repo = Repository()
425 new_repo.repo_state = state
425 new_repo.repo_state = state
426 new_repo.enable_statistics = False
426 new_repo.enable_statistics = False
427 new_repo.repo_name = repo_name_full
427 new_repo.repo_name = repo_name_full
428 new_repo.repo_type = repo_type
428 new_repo.repo_type = repo_type
429 new_repo.user = owner
429 new_repo.user = owner
430 new_repo.group = repo_group
430 new_repo.group = repo_group
431 new_repo.description = description or repo_name
431 new_repo.description = description or repo_name
432 new_repo.private = private
432 new_repo.private = private
433 new_repo.clone_uri = clone_uri
433 new_repo.clone_uri = clone_uri
434 new_repo.landing_rev = landing_rev
434 new_repo.landing_rev = landing_rev
435
435
436 new_repo.enable_statistics = enable_statistics
436 new_repo.enable_statistics = enable_statistics
437 new_repo.enable_locking = enable_locking
437 new_repo.enable_locking = enable_locking
438 new_repo.enable_downloads = enable_downloads
438 new_repo.enable_downloads = enable_downloads
439
439
440 if repo_group:
440 if repo_group:
441 new_repo.enable_locking = repo_group.enable_locking
441 new_repo.enable_locking = repo_group.enable_locking
442
442
443 if fork_of:
443 if fork_of:
444 parent_repo = fork_of
444 parent_repo = fork_of
445 new_repo.fork = parent_repo
445 new_repo.fork = parent_repo
446
446
447 events.trigger(events.RepoPreCreateEvent(new_repo))
447 events.trigger(events.RepoPreCreateEvent(new_repo))
448
448
449 self.sa.add(new_repo)
449 self.sa.add(new_repo)
450
450
451 EMPTY_PERM = 'repository.none'
451 EMPTY_PERM = 'repository.none'
452 if fork_of and copy_fork_permissions:
452 if fork_of and copy_fork_permissions:
453 repo = fork_of
453 repo = fork_of
454 user_perms = UserRepoToPerm.query() \
454 user_perms = UserRepoToPerm.query() \
455 .filter(UserRepoToPerm.repository == repo).all()
455 .filter(UserRepoToPerm.repository == repo).all()
456 group_perms = UserGroupRepoToPerm.query() \
456 group_perms = UserGroupRepoToPerm.query() \
457 .filter(UserGroupRepoToPerm.repository == repo).all()
457 .filter(UserGroupRepoToPerm.repository == repo).all()
458
458
459 for perm in user_perms:
459 for perm in user_perms:
460 UserRepoToPerm.create(
460 UserRepoToPerm.create(
461 perm.user, new_repo, perm.permission)
461 perm.user, new_repo, perm.permission)
462
462
463 for perm in group_perms:
463 for perm in group_perms:
464 UserGroupRepoToPerm.create(
464 UserGroupRepoToPerm.create(
465 perm.users_group, new_repo, perm.permission)
465 perm.users_group, new_repo, perm.permission)
466 # in case we copy permissions and also set this repo to private
466 # in case we copy permissions and also set this repo to private
467 # override the default user permission to make it a private
467 # override the default user permission to make it a private
468 # repo
468 # repo
469 if private:
469 if private:
470 RepoModel(self.sa).grant_user_permission(
470 RepoModel(self.sa).grant_user_permission(
471 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
471 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
472
472
473 elif repo_group and copy_group_permissions:
473 elif repo_group and copy_group_permissions:
474 user_perms = UserRepoGroupToPerm.query() \
474 user_perms = UserRepoGroupToPerm.query() \
475 .filter(UserRepoGroupToPerm.group == repo_group).all()
475 .filter(UserRepoGroupToPerm.group == repo_group).all()
476
476
477 group_perms = UserGroupRepoGroupToPerm.query() \
477 group_perms = UserGroupRepoGroupToPerm.query() \
478 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
478 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
479
479
480 for perm in user_perms:
480 for perm in user_perms:
481 perm_name = perm.permission.permission_name.replace(
481 perm_name = perm.permission.permission_name.replace(
482 'group.', 'repository.')
482 'group.', 'repository.')
483 perm_obj = Permission.get_by_key(perm_name)
483 perm_obj = Permission.get_by_key(perm_name)
484 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
484 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
485
485
486 for perm in group_perms:
486 for perm in group_perms:
487 perm_name = perm.permission.permission_name.replace(
487 perm_name = perm.permission.permission_name.replace(
488 'group.', 'repository.')
488 'group.', 'repository.')
489 perm_obj = Permission.get_by_key(perm_name)
489 perm_obj = Permission.get_by_key(perm_name)
490 UserGroupRepoToPerm.create(
490 UserGroupRepoToPerm.create(
491 perm.users_group, new_repo, perm_obj)
491 perm.users_group, new_repo, perm_obj)
492
492
493 if private:
493 if private:
494 RepoModel(self.sa).grant_user_permission(
494 RepoModel(self.sa).grant_user_permission(
495 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
495 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
496
496
497 else:
497 else:
498 perm_obj = self._create_default_perms(new_repo, private)
498 perm_obj = self._create_default_perms(new_repo, private)
499 self.sa.add(perm_obj)
499 self.sa.add(perm_obj)
500
500
501 # now automatically start following this repository as owner
501 # now automatically start following this repository as owner
502 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
502 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
503 owner.user_id)
503 owner.user_id)
504
504
505 # we need to flush here, in order to check if database won't
505 # we need to flush here, in order to check if database won't
506 # throw any exceptions, create filesystem dirs at the very end
506 # throw any exceptions, create filesystem dirs at the very end
507 self.sa.flush()
507 self.sa.flush()
508 events.trigger(events.RepoCreateEvent(new_repo))
508 events.trigger(events.RepoCreateEvent(new_repo))
509 return new_repo
509 return new_repo
510
510
511 except Exception:
511 except Exception:
512 log.error(traceback.format_exc())
512 log.error(traceback.format_exc())
513 raise
513 raise
514
514
515 def create(self, form_data, cur_user):
515 def create(self, form_data, cur_user):
516 """
516 """
517 Create repository using celery tasks
517 Create repository using celery tasks
518
518
519 :param form_data:
519 :param form_data:
520 :param cur_user:
520 :param cur_user:
521 """
521 """
522 from rhodecode.lib.celerylib import tasks, run_task
522 from rhodecode.lib.celerylib import tasks, run_task
523 return run_task(tasks.create_repo, form_data, cur_user)
523 return run_task(tasks.create_repo, form_data, cur_user)
524
524
525 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
525 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
526 perm_deletions=None, check_perms=True,
526 perm_deletions=None, check_perms=True,
527 cur_user=None):
527 cur_user=None):
528 if not perm_additions:
528 if not perm_additions:
529 perm_additions = []
529 perm_additions = []
530 if not perm_updates:
530 if not perm_updates:
531 perm_updates = []
531 perm_updates = []
532 if not perm_deletions:
532 if not perm_deletions:
533 perm_deletions = []
533 perm_deletions = []
534
534
535 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
535 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
536
536
537 changes = {
537 changes = {
538 'added': [],
538 'added': [],
539 'updated': [],
539 'updated': [],
540 'deleted': []
540 'deleted': []
541 }
541 }
542 # update permissions
542 # update permissions
543 for member_id, perm, member_type in perm_updates:
543 for member_id, perm, member_type in perm_updates:
544 member_id = int(member_id)
544 member_id = int(member_id)
545 if member_type == 'user':
545 if member_type == 'user':
546 member_name = User.get(member_id).username
546 member_name = User.get(member_id).username
547 # this updates also current one if found
547 # this updates also current one if found
548 self.grant_user_permission(
548 self.grant_user_permission(
549 repo=repo, user=member_id, perm=perm)
549 repo=repo, user=member_id, perm=perm)
550 else: # set for user group
550 elif member_type == 'user_group':
551 # check if we have permissions to alter this usergroup
551 # check if we have permissions to alter this usergroup
552 member_name = UserGroup.get(member_id).users_group_name
552 member_name = UserGroup.get(member_id).users_group_name
553 if not check_perms or HasUserGroupPermissionAny(
553 if not check_perms or HasUserGroupPermissionAny(
554 *req_perms)(member_name, user=cur_user):
554 *req_perms)(member_name, user=cur_user):
555 self.grant_user_group_permission(
555 self.grant_user_group_permission(
556 repo=repo, group_name=member_id, perm=perm)
556 repo=repo, group_name=member_id, perm=perm)
557
557 else:
558 raise ValueError("member_type must be 'user' or 'user_group' "
559 "got {} instead".format(member_type))
558 changes['updated'].append({'type': member_type, 'id': member_id,
560 changes['updated'].append({'type': member_type, 'id': member_id,
559 'name': member_name, 'new_perm': perm})
561 'name': member_name, 'new_perm': perm})
560
562
561 # set new permissions
563 # set new permissions
562 for member_id, perm, member_type in perm_additions:
564 for member_id, perm, member_type in perm_additions:
563 member_id = int(member_id)
565 member_id = int(member_id)
564 if member_type == 'user':
566 if member_type == 'user':
565 member_name = User.get(member_id).username
567 member_name = User.get(member_id).username
566 self.grant_user_permission(
568 self.grant_user_permission(
567 repo=repo, user=member_id, perm=perm)
569 repo=repo, user=member_id, perm=perm)
568 else: # set for user group
570 elif member_type == 'user_group':
569 # check if we have permissions to alter this usergroup
571 # check if we have permissions to alter this usergroup
570 member_name = UserGroup.get(member_id).users_group_name
572 member_name = UserGroup.get(member_id).users_group_name
571 if not check_perms or HasUserGroupPermissionAny(
573 if not check_perms or HasUserGroupPermissionAny(
572 *req_perms)(member_name, user=cur_user):
574 *req_perms)(member_name, user=cur_user):
573 self.grant_user_group_permission(
575 self.grant_user_group_permission(
574 repo=repo, group_name=member_id, perm=perm)
576 repo=repo, group_name=member_id, perm=perm)
577 else:
578 raise ValueError("member_type must be 'user' or 'user_group' "
579 "got {} instead".format(member_type))
580
575 changes['added'].append({'type': member_type, 'id': member_id,
581 changes['added'].append({'type': member_type, 'id': member_id,
576 'name': member_name, 'new_perm': perm})
582 'name': member_name, 'new_perm': perm})
577 # delete permissions
583 # delete permissions
578 for member_id, perm, member_type in perm_deletions:
584 for member_id, perm, member_type in perm_deletions:
579 member_id = int(member_id)
585 member_id = int(member_id)
580 if member_type == 'user':
586 if member_type == 'user':
581 member_name = User.get(member_id).username
587 member_name = User.get(member_id).username
582 self.revoke_user_permission(repo=repo, user=member_id)
588 self.revoke_user_permission(repo=repo, user=member_id)
583 else: # set for user group
589 elif member_type == 'user_group':
584 # check if we have permissions to alter this usergroup
590 # check if we have permissions to alter this usergroup
585 member_name = UserGroup.get(member_id).users_group_name
591 member_name = UserGroup.get(member_id).users_group_name
586 if not check_perms or HasUserGroupPermissionAny(
592 if not check_perms or HasUserGroupPermissionAny(
587 *req_perms)(member_name, user=cur_user):
593 *req_perms)(member_name, user=cur_user):
588 self.revoke_user_group_permission(
594 self.revoke_user_group_permission(
589 repo=repo, group_name=member_id)
595 repo=repo, group_name=member_id)
596 else:
597 raise ValueError("member_type must be 'user' or 'user_group' "
598 "got {} instead".format(member_type))
590
599
591 changes['deleted'].append({'type': member_type, 'id': member_id,
600 changes['deleted'].append({'type': member_type, 'id': member_id,
592 'name': member_name, 'new_perm': perm})
601 'name': member_name, 'new_perm': perm})
593 return changes
602 return changes
594
603
595 def create_fork(self, form_data, cur_user):
604 def create_fork(self, form_data, cur_user):
596 """
605 """
597 Simple wrapper into executing celery task for fork creation
606 Simple wrapper into executing celery task for fork creation
598
607
599 :param form_data:
608 :param form_data:
600 :param cur_user:
609 :param cur_user:
601 """
610 """
602 from rhodecode.lib.celerylib import tasks, run_task
611 from rhodecode.lib.celerylib import tasks, run_task
603 return run_task(tasks.create_repo_fork, form_data, cur_user)
612 return run_task(tasks.create_repo_fork, form_data, cur_user)
604
613
605 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
614 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
606 """
615 """
607 Delete given repository, forks parameter defines what do do with
616 Delete given repository, forks parameter defines what do do with
608 attached forks. Throws AttachedForksError if deleted repo has attached
617 attached forks. Throws AttachedForksError if deleted repo has attached
609 forks
618 forks
610
619
611 :param repo:
620 :param repo:
612 :param forks: str 'delete' or 'detach'
621 :param forks: str 'delete' or 'detach'
613 :param fs_remove: remove(archive) repo from filesystem
622 :param fs_remove: remove(archive) repo from filesystem
614 """
623 """
615 if not cur_user:
624 if not cur_user:
616 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
625 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
617 repo = self._get_repo(repo)
626 repo = self._get_repo(repo)
618 if repo:
627 if repo:
619 if forks == 'detach':
628 if forks == 'detach':
620 for r in repo.forks:
629 for r in repo.forks:
621 r.fork = None
630 r.fork = None
622 self.sa.add(r)
631 self.sa.add(r)
623 elif forks == 'delete':
632 elif forks == 'delete':
624 for r in repo.forks:
633 for r in repo.forks:
625 self.delete(r, forks='delete')
634 self.delete(r, forks='delete')
626 elif [f for f in repo.forks]:
635 elif [f for f in repo.forks]:
627 raise AttachedForksError()
636 raise AttachedForksError()
628
637
629 old_repo_dict = repo.get_dict()
638 old_repo_dict = repo.get_dict()
630 events.trigger(events.RepoPreDeleteEvent(repo))
639 events.trigger(events.RepoPreDeleteEvent(repo))
631 try:
640 try:
632 self.sa.delete(repo)
641 self.sa.delete(repo)
633 if fs_remove:
642 if fs_remove:
634 self._delete_filesystem_repo(repo)
643 self._delete_filesystem_repo(repo)
635 else:
644 else:
636 log.debug('skipping removal from filesystem')
645 log.debug('skipping removal from filesystem')
637 old_repo_dict.update({
646 old_repo_dict.update({
638 'deleted_by': cur_user,
647 'deleted_by': cur_user,
639 'deleted_on': time.time(),
648 'deleted_on': time.time(),
640 })
649 })
641 log_delete_repository(**old_repo_dict)
650 log_delete_repository(**old_repo_dict)
642 events.trigger(events.RepoDeleteEvent(repo))
651 events.trigger(events.RepoDeleteEvent(repo))
643 except Exception:
652 except Exception:
644 log.error(traceback.format_exc())
653 log.error(traceback.format_exc())
645 raise
654 raise
646
655
647 def grant_user_permission(self, repo, user, perm):
656 def grant_user_permission(self, repo, user, perm):
648 """
657 """
649 Grant permission for user on given repository, or update existing one
658 Grant permission for user on given repository, or update existing one
650 if found
659 if found
651
660
652 :param repo: Instance of Repository, repository_id, or repository name
661 :param repo: Instance of Repository, repository_id, or repository name
653 :param user: Instance of User, user_id or username
662 :param user: Instance of User, user_id or username
654 :param perm: Instance of Permission, or permission_name
663 :param perm: Instance of Permission, or permission_name
655 """
664 """
656 user = self._get_user(user)
665 user = self._get_user(user)
657 repo = self._get_repo(repo)
666 repo = self._get_repo(repo)
658 permission = self._get_perm(perm)
667 permission = self._get_perm(perm)
659
668
660 # check if we have that permission already
669 # check if we have that permission already
661 obj = self.sa.query(UserRepoToPerm) \
670 obj = self.sa.query(UserRepoToPerm) \
662 .filter(UserRepoToPerm.user == user) \
671 .filter(UserRepoToPerm.user == user) \
663 .filter(UserRepoToPerm.repository == repo) \
672 .filter(UserRepoToPerm.repository == repo) \
664 .scalar()
673 .scalar()
665 if obj is None:
674 if obj is None:
666 # create new !
675 # create new !
667 obj = UserRepoToPerm()
676 obj = UserRepoToPerm()
668 obj.repository = repo
677 obj.repository = repo
669 obj.user = user
678 obj.user = user
670 obj.permission = permission
679 obj.permission = permission
671 self.sa.add(obj)
680 self.sa.add(obj)
672 log.debug('Granted perm %s to %s on %s', perm, user, repo)
681 log.debug('Granted perm %s to %s on %s', perm, user, repo)
673 action_logger_generic(
682 action_logger_generic(
674 'granted permission: {} to user: {} on repo: {}'.format(
683 'granted permission: {} to user: {} on repo: {}'.format(
675 perm, user, repo), namespace='security.repo')
684 perm, user, repo), namespace='security.repo')
676 return obj
685 return obj
677
686
678 def revoke_user_permission(self, repo, user):
687 def revoke_user_permission(self, repo, user):
679 """
688 """
680 Revoke permission for user on given repository
689 Revoke permission for user on given repository
681
690
682 :param repo: Instance of Repository, repository_id, or repository name
691 :param repo: Instance of Repository, repository_id, or repository name
683 :param user: Instance of User, user_id or username
692 :param user: Instance of User, user_id or username
684 """
693 """
685
694
686 user = self._get_user(user)
695 user = self._get_user(user)
687 repo = self._get_repo(repo)
696 repo = self._get_repo(repo)
688
697
689 obj = self.sa.query(UserRepoToPerm) \
698 obj = self.sa.query(UserRepoToPerm) \
690 .filter(UserRepoToPerm.repository == repo) \
699 .filter(UserRepoToPerm.repository == repo) \
691 .filter(UserRepoToPerm.user == user) \
700 .filter(UserRepoToPerm.user == user) \
692 .scalar()
701 .scalar()
693 if obj:
702 if obj:
694 self.sa.delete(obj)
703 self.sa.delete(obj)
695 log.debug('Revoked perm on %s on %s', repo, user)
704 log.debug('Revoked perm on %s on %s', repo, user)
696 action_logger_generic(
705 action_logger_generic(
697 'revoked permission from user: {} on repo: {}'.format(
706 'revoked permission from user: {} on repo: {}'.format(
698 user, repo), namespace='security.repo')
707 user, repo), namespace='security.repo')
699
708
700 def grant_user_group_permission(self, repo, group_name, perm):
709 def grant_user_group_permission(self, repo, group_name, perm):
701 """
710 """
702 Grant permission for user group on given repository, or update
711 Grant permission for user group on given repository, or update
703 existing one if found
712 existing one if found
704
713
705 :param repo: Instance of Repository, repository_id, or repository name
714 :param repo: Instance of Repository, repository_id, or repository name
706 :param group_name: Instance of UserGroup, users_group_id,
715 :param group_name: Instance of UserGroup, users_group_id,
707 or user group name
716 or user group name
708 :param perm: Instance of Permission, or permission_name
717 :param perm: Instance of Permission, or permission_name
709 """
718 """
710 repo = self._get_repo(repo)
719 repo = self._get_repo(repo)
711 group_name = self._get_user_group(group_name)
720 group_name = self._get_user_group(group_name)
712 permission = self._get_perm(perm)
721 permission = self._get_perm(perm)
713
722
714 # check if we have that permission already
723 # check if we have that permission already
715 obj = self.sa.query(UserGroupRepoToPerm) \
724 obj = self.sa.query(UserGroupRepoToPerm) \
716 .filter(UserGroupRepoToPerm.users_group == group_name) \
725 .filter(UserGroupRepoToPerm.users_group == group_name) \
717 .filter(UserGroupRepoToPerm.repository == repo) \
726 .filter(UserGroupRepoToPerm.repository == repo) \
718 .scalar()
727 .scalar()
719
728
720 if obj is None:
729 if obj is None:
721 # create new
730 # create new
722 obj = UserGroupRepoToPerm()
731 obj = UserGroupRepoToPerm()
723
732
724 obj.repository = repo
733 obj.repository = repo
725 obj.users_group = group_name
734 obj.users_group = group_name
726 obj.permission = permission
735 obj.permission = permission
727 self.sa.add(obj)
736 self.sa.add(obj)
728 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
737 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
729 action_logger_generic(
738 action_logger_generic(
730 'granted permission: {} to usergroup: {} on repo: {}'.format(
739 'granted permission: {} to usergroup: {} on repo: {}'.format(
731 perm, group_name, repo), namespace='security.repo')
740 perm, group_name, repo), namespace='security.repo')
732
741
733 return obj
742 return obj
734
743
735 def revoke_user_group_permission(self, repo, group_name):
744 def revoke_user_group_permission(self, repo, group_name):
736 """
745 """
737 Revoke permission for user group on given repository
746 Revoke permission for user group on given repository
738
747
739 :param repo: Instance of Repository, repository_id, or repository name
748 :param repo: Instance of Repository, repository_id, or repository name
740 :param group_name: Instance of UserGroup, users_group_id,
749 :param group_name: Instance of UserGroup, users_group_id,
741 or user group name
750 or user group name
742 """
751 """
743 repo = self._get_repo(repo)
752 repo = self._get_repo(repo)
744 group_name = self._get_user_group(group_name)
753 group_name = self._get_user_group(group_name)
745
754
746 obj = self.sa.query(UserGroupRepoToPerm) \
755 obj = self.sa.query(UserGroupRepoToPerm) \
747 .filter(UserGroupRepoToPerm.repository == repo) \
756 .filter(UserGroupRepoToPerm.repository == repo) \
748 .filter(UserGroupRepoToPerm.users_group == group_name) \
757 .filter(UserGroupRepoToPerm.users_group == group_name) \
749 .scalar()
758 .scalar()
750 if obj:
759 if obj:
751 self.sa.delete(obj)
760 self.sa.delete(obj)
752 log.debug('Revoked perm to %s on %s', repo, group_name)
761 log.debug('Revoked perm to %s on %s', repo, group_name)
753 action_logger_generic(
762 action_logger_generic(
754 'revoked permission from usergroup: {} on repo: {}'.format(
763 'revoked permission from usergroup: {} on repo: {}'.format(
755 group_name, repo), namespace='security.repo')
764 group_name, repo), namespace='security.repo')
756
765
757 def delete_stats(self, repo_name):
766 def delete_stats(self, repo_name):
758 """
767 """
759 removes stats for given repo
768 removes stats for given repo
760
769
761 :param repo_name:
770 :param repo_name:
762 """
771 """
763 repo = self._get_repo(repo_name)
772 repo = self._get_repo(repo_name)
764 try:
773 try:
765 obj = self.sa.query(Statistics) \
774 obj = self.sa.query(Statistics) \
766 .filter(Statistics.repository == repo).scalar()
775 .filter(Statistics.repository == repo).scalar()
767 if obj:
776 if obj:
768 self.sa.delete(obj)
777 self.sa.delete(obj)
769 except Exception:
778 except Exception:
770 log.error(traceback.format_exc())
779 log.error(traceback.format_exc())
771 raise
780 raise
772
781
773 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
782 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
774 field_type='str', field_desc=''):
783 field_type='str', field_desc=''):
775
784
776 repo = self._get_repo(repo_name)
785 repo = self._get_repo(repo_name)
777
786
778 new_field = RepositoryField()
787 new_field = RepositoryField()
779 new_field.repository = repo
788 new_field.repository = repo
780 new_field.field_key = field_key
789 new_field.field_key = field_key
781 new_field.field_type = field_type # python type
790 new_field.field_type = field_type # python type
782 new_field.field_value = field_value
791 new_field.field_value = field_value
783 new_field.field_desc = field_desc
792 new_field.field_desc = field_desc
784 new_field.field_label = field_label
793 new_field.field_label = field_label
785 self.sa.add(new_field)
794 self.sa.add(new_field)
786 return new_field
795 return new_field
787
796
788 def delete_repo_field(self, repo_name, field_key):
797 def delete_repo_field(self, repo_name, field_key):
789 repo = self._get_repo(repo_name)
798 repo = self._get_repo(repo_name)
790 field = RepositoryField.get_by_key_name(field_key, repo)
799 field = RepositoryField.get_by_key_name(field_key, repo)
791 if field:
800 if field:
792 self.sa.delete(field)
801 self.sa.delete(field)
793
802
794 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
803 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
795 clone_uri=None, repo_store_location=None,
804 clone_uri=None, repo_store_location=None,
796 use_global_config=False):
805 use_global_config=False):
797 """
806 """
798 makes repository on filesystem. It's group aware means it'll create
807 makes repository on filesystem. It's group aware means it'll create
799 a repository within a group, and alter the paths accordingly of
808 a repository within a group, and alter the paths accordingly of
800 group location
809 group location
801
810
802 :param repo_name:
811 :param repo_name:
803 :param alias:
812 :param alias:
804 :param parent:
813 :param parent:
805 :param clone_uri:
814 :param clone_uri:
806 :param repo_store_location:
815 :param repo_store_location:
807 """
816 """
808 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
817 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
809 from rhodecode.model.scm import ScmModel
818 from rhodecode.model.scm import ScmModel
810
819
811 if Repository.NAME_SEP in repo_name:
820 if Repository.NAME_SEP in repo_name:
812 raise ValueError(
821 raise ValueError(
813 'repo_name must not contain groups got `%s`' % repo_name)
822 'repo_name must not contain groups got `%s`' % repo_name)
814
823
815 if isinstance(repo_group, RepoGroup):
824 if isinstance(repo_group, RepoGroup):
816 new_parent_path = os.sep.join(repo_group.full_path_splitted)
825 new_parent_path = os.sep.join(repo_group.full_path_splitted)
817 else:
826 else:
818 new_parent_path = repo_group or ''
827 new_parent_path = repo_group or ''
819
828
820 if repo_store_location:
829 if repo_store_location:
821 _paths = [repo_store_location]
830 _paths = [repo_store_location]
822 else:
831 else:
823 _paths = [self.repos_path, new_parent_path, repo_name]
832 _paths = [self.repos_path, new_parent_path, repo_name]
824 # we need to make it str for mercurial
833 # we need to make it str for mercurial
825 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
834 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
826
835
827 # check if this path is not a repository
836 # check if this path is not a repository
828 if is_valid_repo(repo_path, self.repos_path):
837 if is_valid_repo(repo_path, self.repos_path):
829 raise Exception('This path %s is a valid repository' % repo_path)
838 raise Exception('This path %s is a valid repository' % repo_path)
830
839
831 # check if this path is a group
840 # check if this path is a group
832 if is_valid_repo_group(repo_path, self.repos_path):
841 if is_valid_repo_group(repo_path, self.repos_path):
833 raise Exception('This path %s is a valid group' % repo_path)
842 raise Exception('This path %s is a valid group' % repo_path)
834
843
835 log.info('creating repo %s in %s from url: `%s`',
844 log.info('creating repo %s in %s from url: `%s`',
836 repo_name, safe_unicode(repo_path),
845 repo_name, safe_unicode(repo_path),
837 obfuscate_url_pw(clone_uri))
846 obfuscate_url_pw(clone_uri))
838
847
839 backend = get_backend(repo_type)
848 backend = get_backend(repo_type)
840
849
841 config_repo = None if use_global_config else repo_name
850 config_repo = None if use_global_config else repo_name
842 if config_repo and new_parent_path:
851 if config_repo and new_parent_path:
843 config_repo = Repository.NAME_SEP.join(
852 config_repo = Repository.NAME_SEP.join(
844 (new_parent_path, config_repo))
853 (new_parent_path, config_repo))
845 config = make_db_config(clear_session=False, repo=config_repo)
854 config = make_db_config(clear_session=False, repo=config_repo)
846 config.set('extensions', 'largefiles', '')
855 config.set('extensions', 'largefiles', '')
847
856
848 # patch and reset hooks section of UI config to not run any
857 # patch and reset hooks section of UI config to not run any
849 # hooks on creating remote repo
858 # hooks on creating remote repo
850 config.clear_section('hooks')
859 config.clear_section('hooks')
851
860
852 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
861 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
853 if repo_type == 'git':
862 if repo_type == 'git':
854 repo = backend(
863 repo = backend(
855 repo_path, config=config, create=True, src_url=clone_uri,
864 repo_path, config=config, create=True, src_url=clone_uri,
856 bare=True)
865 bare=True)
857 else:
866 else:
858 repo = backend(
867 repo = backend(
859 repo_path, config=config, create=True, src_url=clone_uri)
868 repo_path, config=config, create=True, src_url=clone_uri)
860
869
861 repo.install_hooks()
870 repo.install_hooks()
862
871
863 log.debug('Created repo %s with %s backend',
872 log.debug('Created repo %s with %s backend',
864 safe_unicode(repo_name), safe_unicode(repo_type))
873 safe_unicode(repo_name), safe_unicode(repo_type))
865 return repo
874 return repo
866
875
867 def _rename_filesystem_repo(self, old, new):
876 def _rename_filesystem_repo(self, old, new):
868 """
877 """
869 renames repository on filesystem
878 renames repository on filesystem
870
879
871 :param old: old name
880 :param old: old name
872 :param new: new name
881 :param new: new name
873 """
882 """
874 log.info('renaming repo from %s to %s', old, new)
883 log.info('renaming repo from %s to %s', old, new)
875
884
876 old_path = os.path.join(self.repos_path, old)
885 old_path = os.path.join(self.repos_path, old)
877 new_path = os.path.join(self.repos_path, new)
886 new_path = os.path.join(self.repos_path, new)
878 if os.path.isdir(new_path):
887 if os.path.isdir(new_path):
879 raise Exception(
888 raise Exception(
880 'Was trying to rename to already existing dir %s' % new_path
889 'Was trying to rename to already existing dir %s' % new_path
881 )
890 )
882 shutil.move(old_path, new_path)
891 shutil.move(old_path, new_path)
883
892
884 def _delete_filesystem_repo(self, repo):
893 def _delete_filesystem_repo(self, repo):
885 """
894 """
886 removes repo from filesystem, the removal is acctually made by
895 removes repo from filesystem, the removal is acctually made by
887 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
896 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
888 repository is no longer valid for rhodecode, can be undeleted later on
897 repository is no longer valid for rhodecode, can be undeleted later on
889 by reverting the renames on this repository
898 by reverting the renames on this repository
890
899
891 :param repo: repo object
900 :param repo: repo object
892 """
901 """
893 rm_path = os.path.join(self.repos_path, repo.repo_name)
902 rm_path = os.path.join(self.repos_path, repo.repo_name)
894 repo_group = repo.group
903 repo_group = repo.group
895 log.info("Removing repository %s", rm_path)
904 log.info("Removing repository %s", rm_path)
896 # disable hg/git internal that it doesn't get detected as repo
905 # disable hg/git internal that it doesn't get detected as repo
897 alias = repo.repo_type
906 alias = repo.repo_type
898
907
899 config = make_db_config(clear_session=False)
908 config = make_db_config(clear_session=False)
900 config.set('extensions', 'largefiles', '')
909 config.set('extensions', 'largefiles', '')
901 bare = getattr(repo.scm_instance(config=config), 'bare', False)
910 bare = getattr(repo.scm_instance(config=config), 'bare', False)
902
911
903 # skip this for bare git repos
912 # skip this for bare git repos
904 if not bare:
913 if not bare:
905 # disable VCS repo
914 # disable VCS repo
906 vcs_path = os.path.join(rm_path, '.%s' % alias)
915 vcs_path = os.path.join(rm_path, '.%s' % alias)
907 if os.path.exists(vcs_path):
916 if os.path.exists(vcs_path):
908 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
917 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
909
918
910 _now = datetime.datetime.now()
919 _now = datetime.datetime.now()
911 _ms = str(_now.microsecond).rjust(6, '0')
920 _ms = str(_now.microsecond).rjust(6, '0')
912 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
921 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
913 repo.just_name)
922 repo.just_name)
914 if repo_group:
923 if repo_group:
915 # if repository is in group, prefix the removal path with the group
924 # if repository is in group, prefix the removal path with the group
916 args = repo_group.full_path_splitted + [_d]
925 args = repo_group.full_path_splitted + [_d]
917 _d = os.path.join(*args)
926 _d = os.path.join(*args)
918
927
919 if os.path.isdir(rm_path):
928 if os.path.isdir(rm_path):
920 shutil.move(rm_path, os.path.join(self.repos_path, _d))
929 shutil.move(rm_path, os.path.join(self.repos_path, _d))
921
930
922 # finally cleanup diff-cache if it exists
931 # finally cleanup diff-cache if it exists
923 cached_diffs_dir = repo.cached_diffs_dir
932 cached_diffs_dir = repo.cached_diffs_dir
924 if os.path.isdir(cached_diffs_dir):
933 if os.path.isdir(cached_diffs_dir):
925 shutil.rmtree(cached_diffs_dir)
934 shutil.rmtree(cached_diffs_dir)
926
935
927
936
928 class ReadmeFinder:
937 class ReadmeFinder:
929 """
938 """
930 Utility which knows how to find a readme for a specific commit.
939 Utility which knows how to find a readme for a specific commit.
931
940
932 The main idea is that this is a configurable algorithm. When creating an
941 The main idea is that this is a configurable algorithm. When creating an
933 instance you can define parameters, currently only the `default_renderer`.
942 instance you can define parameters, currently only the `default_renderer`.
934 Based on this configuration the method :meth:`search` behaves slightly
943 Based on this configuration the method :meth:`search` behaves slightly
935 different.
944 different.
936 """
945 """
937
946
938 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
947 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
939 path_re = re.compile(r'^docs?', re.IGNORECASE)
948 path_re = re.compile(r'^docs?', re.IGNORECASE)
940
949
941 default_priorities = {
950 default_priorities = {
942 None: 0,
951 None: 0,
943 '.text': 2,
952 '.text': 2,
944 '.txt': 3,
953 '.txt': 3,
945 '.rst': 1,
954 '.rst': 1,
946 '.rest': 2,
955 '.rest': 2,
947 '.md': 1,
956 '.md': 1,
948 '.mkdn': 2,
957 '.mkdn': 2,
949 '.mdown': 3,
958 '.mdown': 3,
950 '.markdown': 4,
959 '.markdown': 4,
951 }
960 }
952
961
953 path_priority = {
962 path_priority = {
954 'doc': 0,
963 'doc': 0,
955 'docs': 1,
964 'docs': 1,
956 }
965 }
957
966
958 FALLBACK_PRIORITY = 99
967 FALLBACK_PRIORITY = 99
959
968
960 RENDERER_TO_EXTENSION = {
969 RENDERER_TO_EXTENSION = {
961 'rst': ['.rst', '.rest'],
970 'rst': ['.rst', '.rest'],
962 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
971 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
963 }
972 }
964
973
965 def __init__(self, default_renderer=None):
974 def __init__(self, default_renderer=None):
966 self._default_renderer = default_renderer
975 self._default_renderer = default_renderer
967 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
976 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
968 default_renderer, [])
977 default_renderer, [])
969
978
970 def search(self, commit, path='/'):
979 def search(self, commit, path='/'):
971 """
980 """
972 Find a readme in the given `commit`.
981 Find a readme in the given `commit`.
973 """
982 """
974 nodes = commit.get_nodes(path)
983 nodes = commit.get_nodes(path)
975 matches = self._match_readmes(nodes)
984 matches = self._match_readmes(nodes)
976 matches = self._sort_according_to_priority(matches)
985 matches = self._sort_according_to_priority(matches)
977 if matches:
986 if matches:
978 return matches[0].node
987 return matches[0].node
979
988
980 paths = self._match_paths(nodes)
989 paths = self._match_paths(nodes)
981 paths = self._sort_paths_according_to_priority(paths)
990 paths = self._sort_paths_according_to_priority(paths)
982 for path in paths:
991 for path in paths:
983 match = self.search(commit, path=path)
992 match = self.search(commit, path=path)
984 if match:
993 if match:
985 return match
994 return match
986
995
987 return None
996 return None
988
997
989 def _match_readmes(self, nodes):
998 def _match_readmes(self, nodes):
990 for node in nodes:
999 for node in nodes:
991 if not node.is_file():
1000 if not node.is_file():
992 continue
1001 continue
993 path = node.path.rsplit('/', 1)[-1]
1002 path = node.path.rsplit('/', 1)[-1]
994 match = self.readme_re.match(path)
1003 match = self.readme_re.match(path)
995 if match:
1004 if match:
996 extension = match.group(1)
1005 extension = match.group(1)
997 yield ReadmeMatch(node, match, self._priority(extension))
1006 yield ReadmeMatch(node, match, self._priority(extension))
998
1007
999 def _match_paths(self, nodes):
1008 def _match_paths(self, nodes):
1000 for node in nodes:
1009 for node in nodes:
1001 if not node.is_dir():
1010 if not node.is_dir():
1002 continue
1011 continue
1003 match = self.path_re.match(node.path)
1012 match = self.path_re.match(node.path)
1004 if match:
1013 if match:
1005 yield node.path
1014 yield node.path
1006
1015
1007 def _priority(self, extension):
1016 def _priority(self, extension):
1008 renderer_priority = (
1017 renderer_priority = (
1009 0 if extension in self._renderer_extensions else 1)
1018 0 if extension in self._renderer_extensions else 1)
1010 extension_priority = self.default_priorities.get(
1019 extension_priority = self.default_priorities.get(
1011 extension, self.FALLBACK_PRIORITY)
1020 extension, self.FALLBACK_PRIORITY)
1012 return (renderer_priority, extension_priority)
1021 return (renderer_priority, extension_priority)
1013
1022
1014 def _sort_according_to_priority(self, matches):
1023 def _sort_according_to_priority(self, matches):
1015
1024
1016 def priority_and_path(match):
1025 def priority_and_path(match):
1017 return (match.priority, match.path)
1026 return (match.priority, match.path)
1018
1027
1019 return sorted(matches, key=priority_and_path)
1028 return sorted(matches, key=priority_and_path)
1020
1029
1021 def _sort_paths_according_to_priority(self, paths):
1030 def _sort_paths_according_to_priority(self, paths):
1022
1031
1023 def priority_and_path(path):
1032 def priority_and_path(path):
1024 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1033 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1025
1034
1026 return sorted(paths, key=priority_and_path)
1035 return sorted(paths, key=priority_and_path)
1027
1036
1028
1037
1029 class ReadmeMatch:
1038 class ReadmeMatch:
1030
1039
1031 def __init__(self, node, match, priority):
1040 def __init__(self, node, match, priority):
1032 self.node = node
1041 self.node = node
1033 self._match = match
1042 self._match = match
1034 self.priority = priority
1043 self.priority = priority
1035
1044
1036 @property
1045 @property
1037 def path(self):
1046 def path(self):
1038 return self.node.path
1047 return self.node.path
1039
1048
1040 def __repr__(self):
1049 def __repr__(self):
1041 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
1050 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
@@ -1,764 +1,773 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2018 RhodeCode GmbH
3 # Copyright (C) 2011-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 repo group model for RhodeCode
23 repo group model for RhodeCode
24 """
24 """
25
25
26 import os
26 import os
27 import datetime
27 import datetime
28 import itertools
28 import itertools
29 import logging
29 import logging
30 import shutil
30 import shutil
31 import traceback
31 import traceback
32 import string
32 import string
33
33
34 from zope.cachedescriptors.property import Lazy as LazyProperty
34 from zope.cachedescriptors.property import Lazy as LazyProperty
35
35
36 from rhodecode import events
36 from rhodecode import events
37 from rhodecode.model import BaseModel
37 from rhodecode.model import BaseModel
38 from rhodecode.model.db import (_hash_key,
38 from rhodecode.model.db import (_hash_key,
39 RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
39 RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
40 UserGroup, Repository)
40 UserGroup, Repository)
41 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
41 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
42 from rhodecode.lib.caching_query import FromCache
42 from rhodecode.lib.caching_query import FromCache
43 from rhodecode.lib.utils2 import action_logger_generic, datetime_to_time
43 from rhodecode.lib.utils2 import action_logger_generic, datetime_to_time
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 class RepoGroupModel(BaseModel):
48 class RepoGroupModel(BaseModel):
49
49
50 cls = RepoGroup
50 cls = RepoGroup
51 PERSONAL_GROUP_DESC = 'personal repo group of user `%(username)s`'
51 PERSONAL_GROUP_DESC = 'personal repo group of user `%(username)s`'
52 PERSONAL_GROUP_PATTERN = '${username}' # default
52 PERSONAL_GROUP_PATTERN = '${username}' # default
53
53
54 def _get_user_group(self, users_group):
54 def _get_user_group(self, users_group):
55 return self._get_instance(UserGroup, users_group,
55 return self._get_instance(UserGroup, users_group,
56 callback=UserGroup.get_by_group_name)
56 callback=UserGroup.get_by_group_name)
57
57
58 def _get_repo_group(self, repo_group):
58 def _get_repo_group(self, repo_group):
59 return self._get_instance(RepoGroup, repo_group,
59 return self._get_instance(RepoGroup, repo_group,
60 callback=RepoGroup.get_by_group_name)
60 callback=RepoGroup.get_by_group_name)
61
61
62 @LazyProperty
62 @LazyProperty
63 def repos_path(self):
63 def repos_path(self):
64 """
64 """
65 Gets the repositories root path from database
65 Gets the repositories root path from database
66 """
66 """
67
67
68 settings_model = VcsSettingsModel(sa=self.sa)
68 settings_model = VcsSettingsModel(sa=self.sa)
69 return settings_model.get_repos_location()
69 return settings_model.get_repos_location()
70
70
71 def get_by_group_name(self, repo_group_name, cache=None):
71 def get_by_group_name(self, repo_group_name, cache=None):
72 repo = self.sa.query(RepoGroup) \
72 repo = self.sa.query(RepoGroup) \
73 .filter(RepoGroup.group_name == repo_group_name)
73 .filter(RepoGroup.group_name == repo_group_name)
74
74
75 if cache:
75 if cache:
76 name_key = _hash_key(repo_group_name)
76 name_key = _hash_key(repo_group_name)
77 repo = repo.options(
77 repo = repo.options(
78 FromCache("sql_cache_short", "get_repo_group_%s" % name_key))
78 FromCache("sql_cache_short", "get_repo_group_%s" % name_key))
79 return repo.scalar()
79 return repo.scalar()
80
80
81 def get_default_create_personal_repo_group(self):
81 def get_default_create_personal_repo_group(self):
82 value = SettingsModel().get_setting_by_name(
82 value = SettingsModel().get_setting_by_name(
83 'create_personal_repo_group')
83 'create_personal_repo_group')
84 return value.app_settings_value if value else None or False
84 return value.app_settings_value if value else None or False
85
85
86 def get_personal_group_name_pattern(self):
86 def get_personal_group_name_pattern(self):
87 value = SettingsModel().get_setting_by_name(
87 value = SettingsModel().get_setting_by_name(
88 'personal_repo_group_pattern')
88 'personal_repo_group_pattern')
89 val = value.app_settings_value if value else None
89 val = value.app_settings_value if value else None
90 group_template = val or self.PERSONAL_GROUP_PATTERN
90 group_template = val or self.PERSONAL_GROUP_PATTERN
91
91
92 group_template = group_template.lstrip('/')
92 group_template = group_template.lstrip('/')
93 return group_template
93 return group_template
94
94
95 def get_personal_group_name(self, user):
95 def get_personal_group_name(self, user):
96 template = self.get_personal_group_name_pattern()
96 template = self.get_personal_group_name_pattern()
97 return string.Template(template).safe_substitute(
97 return string.Template(template).safe_substitute(
98 username=user.username,
98 username=user.username,
99 user_id=user.user_id,
99 user_id=user.user_id,
100 )
100 )
101
101
102 def create_personal_repo_group(self, user, commit_early=True):
102 def create_personal_repo_group(self, user, commit_early=True):
103 desc = self.PERSONAL_GROUP_DESC % {'username': user.username}
103 desc = self.PERSONAL_GROUP_DESC % {'username': user.username}
104 personal_repo_group_name = self.get_personal_group_name(user)
104 personal_repo_group_name = self.get_personal_group_name(user)
105
105
106 # create a new one
106 # create a new one
107 RepoGroupModel().create(
107 RepoGroupModel().create(
108 group_name=personal_repo_group_name,
108 group_name=personal_repo_group_name,
109 group_description=desc,
109 group_description=desc,
110 owner=user.username,
110 owner=user.username,
111 personal=True,
111 personal=True,
112 commit_early=commit_early)
112 commit_early=commit_early)
113
113
114 def _create_default_perms(self, new_group):
114 def _create_default_perms(self, new_group):
115 # create default permission
115 # create default permission
116 default_perm = 'group.read'
116 default_perm = 'group.read'
117 def_user = User.get_default_user()
117 def_user = User.get_default_user()
118 for p in def_user.user_perms:
118 for p in def_user.user_perms:
119 if p.permission.permission_name.startswith('group.'):
119 if p.permission.permission_name.startswith('group.'):
120 default_perm = p.permission.permission_name
120 default_perm = p.permission.permission_name
121 break
121 break
122
122
123 repo_group_to_perm = UserRepoGroupToPerm()
123 repo_group_to_perm = UserRepoGroupToPerm()
124 repo_group_to_perm.permission = Permission.get_by_key(default_perm)
124 repo_group_to_perm.permission = Permission.get_by_key(default_perm)
125
125
126 repo_group_to_perm.group = new_group
126 repo_group_to_perm.group = new_group
127 repo_group_to_perm.user_id = def_user.user_id
127 repo_group_to_perm.user_id = def_user.user_id
128 return repo_group_to_perm
128 return repo_group_to_perm
129
129
130 def _get_group_name_and_parent(self, group_name_full, repo_in_path=False,
130 def _get_group_name_and_parent(self, group_name_full, repo_in_path=False,
131 get_object=False):
131 get_object=False):
132 """
132 """
133 Get's the group name and a parent group name from given group name.
133 Get's the group name and a parent group name from given group name.
134 If repo_in_path is set to truth, we asume the full path also includes
134 If repo_in_path is set to truth, we asume the full path also includes
135 repo name, in such case we clean the last element.
135 repo name, in such case we clean the last element.
136
136
137 :param group_name_full:
137 :param group_name_full:
138 """
138 """
139 split_paths = 1
139 split_paths = 1
140 if repo_in_path:
140 if repo_in_path:
141 split_paths = 2
141 split_paths = 2
142 _parts = group_name_full.rsplit(RepoGroup.url_sep(), split_paths)
142 _parts = group_name_full.rsplit(RepoGroup.url_sep(), split_paths)
143
143
144 if repo_in_path and len(_parts) > 1:
144 if repo_in_path and len(_parts) > 1:
145 # such case last element is the repo_name
145 # such case last element is the repo_name
146 _parts.pop(-1)
146 _parts.pop(-1)
147 group_name_cleaned = _parts[-1] # just the group name
147 group_name_cleaned = _parts[-1] # just the group name
148 parent_repo_group_name = None
148 parent_repo_group_name = None
149
149
150 if len(_parts) > 1:
150 if len(_parts) > 1:
151 parent_repo_group_name = _parts[0]
151 parent_repo_group_name = _parts[0]
152
152
153 parent_group = None
153 parent_group = None
154 if parent_repo_group_name:
154 if parent_repo_group_name:
155 parent_group = RepoGroup.get_by_group_name(parent_repo_group_name)
155 parent_group = RepoGroup.get_by_group_name(parent_repo_group_name)
156
156
157 if get_object:
157 if get_object:
158 return group_name_cleaned, parent_repo_group_name, parent_group
158 return group_name_cleaned, parent_repo_group_name, parent_group
159
159
160 return group_name_cleaned, parent_repo_group_name
160 return group_name_cleaned, parent_repo_group_name
161
161
162 def check_exist_filesystem(self, group_name, exc_on_failure=True):
162 def check_exist_filesystem(self, group_name, exc_on_failure=True):
163 create_path = os.path.join(self.repos_path, group_name)
163 create_path = os.path.join(self.repos_path, group_name)
164 log.debug('creating new group in %s', create_path)
164 log.debug('creating new group in %s', create_path)
165
165
166 if os.path.isdir(create_path):
166 if os.path.isdir(create_path):
167 if exc_on_failure:
167 if exc_on_failure:
168 abs_create_path = os.path.abspath(create_path)
168 abs_create_path = os.path.abspath(create_path)
169 raise Exception('Directory `{}` already exists !'.format(abs_create_path))
169 raise Exception('Directory `{}` already exists !'.format(abs_create_path))
170 return False
170 return False
171 return True
171 return True
172
172
173 def _create_group(self, group_name):
173 def _create_group(self, group_name):
174 """
174 """
175 makes repository group on filesystem
175 makes repository group on filesystem
176
176
177 :param repo_name:
177 :param repo_name:
178 :param parent_id:
178 :param parent_id:
179 """
179 """
180
180
181 self.check_exist_filesystem(group_name)
181 self.check_exist_filesystem(group_name)
182 create_path = os.path.join(self.repos_path, group_name)
182 create_path = os.path.join(self.repos_path, group_name)
183 log.debug('creating new group in %s', create_path)
183 log.debug('creating new group in %s', create_path)
184 os.makedirs(create_path, mode=0755)
184 os.makedirs(create_path, mode=0755)
185 log.debug('created group in %s', create_path)
185 log.debug('created group in %s', create_path)
186
186
187 def _rename_group(self, old, new):
187 def _rename_group(self, old, new):
188 """
188 """
189 Renames a group on filesystem
189 Renames a group on filesystem
190
190
191 :param group_name:
191 :param group_name:
192 """
192 """
193
193
194 if old == new:
194 if old == new:
195 log.debug('skipping group rename')
195 log.debug('skipping group rename')
196 return
196 return
197
197
198 log.debug('renaming repository group from %s to %s', old, new)
198 log.debug('renaming repository group from %s to %s', old, new)
199
199
200 old_path = os.path.join(self.repos_path, old)
200 old_path = os.path.join(self.repos_path, old)
201 new_path = os.path.join(self.repos_path, new)
201 new_path = os.path.join(self.repos_path, new)
202
202
203 log.debug('renaming repos paths from %s to %s', old_path, new_path)
203 log.debug('renaming repos paths from %s to %s', old_path, new_path)
204
204
205 if os.path.isdir(new_path):
205 if os.path.isdir(new_path):
206 raise Exception('Was trying to rename to already '
206 raise Exception('Was trying to rename to already '
207 'existing dir %s' % new_path)
207 'existing dir %s' % new_path)
208 shutil.move(old_path, new_path)
208 shutil.move(old_path, new_path)
209
209
210 def _delete_filesystem_group(self, group, force_delete=False):
210 def _delete_filesystem_group(self, group, force_delete=False):
211 """
211 """
212 Deletes a group from a filesystem
212 Deletes a group from a filesystem
213
213
214 :param group: instance of group from database
214 :param group: instance of group from database
215 :param force_delete: use shutil rmtree to remove all objects
215 :param force_delete: use shutil rmtree to remove all objects
216 """
216 """
217 paths = group.full_path.split(RepoGroup.url_sep())
217 paths = group.full_path.split(RepoGroup.url_sep())
218 paths = os.sep.join(paths)
218 paths = os.sep.join(paths)
219
219
220 rm_path = os.path.join(self.repos_path, paths)
220 rm_path = os.path.join(self.repos_path, paths)
221 log.info("Removing group %s", rm_path)
221 log.info("Removing group %s", rm_path)
222 # delete only if that path really exists
222 # delete only if that path really exists
223 if os.path.isdir(rm_path):
223 if os.path.isdir(rm_path):
224 if force_delete:
224 if force_delete:
225 shutil.rmtree(rm_path)
225 shutil.rmtree(rm_path)
226 else:
226 else:
227 # archive that group`
227 # archive that group`
228 _now = datetime.datetime.now()
228 _now = datetime.datetime.now()
229 _ms = str(_now.microsecond).rjust(6, '0')
229 _ms = str(_now.microsecond).rjust(6, '0')
230 _d = 'rm__%s_GROUP_%s' % (
230 _d = 'rm__%s_GROUP_%s' % (
231 _now.strftime('%Y%m%d_%H%M%S_' + _ms), group.name)
231 _now.strftime('%Y%m%d_%H%M%S_' + _ms), group.name)
232 shutil.move(rm_path, os.path.join(self.repos_path, _d))
232 shutil.move(rm_path, os.path.join(self.repos_path, _d))
233
233
234 def create(self, group_name, group_description, owner, just_db=False,
234 def create(self, group_name, group_description, owner, just_db=False,
235 copy_permissions=False, personal=None, commit_early=True):
235 copy_permissions=False, personal=None, commit_early=True):
236
236
237 (group_name_cleaned,
237 (group_name_cleaned,
238 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(group_name)
238 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(group_name)
239
239
240 parent_group = None
240 parent_group = None
241 if parent_group_name:
241 if parent_group_name:
242 parent_group = self._get_repo_group(parent_group_name)
242 parent_group = self._get_repo_group(parent_group_name)
243 if not parent_group:
243 if not parent_group:
244 # we tried to create a nested group, but the parent is not
244 # we tried to create a nested group, but the parent is not
245 # existing
245 # existing
246 raise ValueError(
246 raise ValueError(
247 'Parent group `%s` given in `%s` group name '
247 'Parent group `%s` given in `%s` group name '
248 'is not yet existing.' % (parent_group_name, group_name))
248 'is not yet existing.' % (parent_group_name, group_name))
249
249
250 # because we are doing a cleanup, we need to check if such directory
250 # because we are doing a cleanup, we need to check if such directory
251 # already exists. If we don't do that we can accidentally delete
251 # already exists. If we don't do that we can accidentally delete
252 # existing directory via cleanup that can cause data issues, since
252 # existing directory via cleanup that can cause data issues, since
253 # delete does a folder rename to special syntax later cleanup
253 # delete does a folder rename to special syntax later cleanup
254 # functions can delete this
254 # functions can delete this
255 cleanup_group = self.check_exist_filesystem(group_name,
255 cleanup_group = self.check_exist_filesystem(group_name,
256 exc_on_failure=False)
256 exc_on_failure=False)
257 user = self._get_user(owner)
257 user = self._get_user(owner)
258 if not user:
258 if not user:
259 raise ValueError('Owner %s not found as rhodecode user', owner)
259 raise ValueError('Owner %s not found as rhodecode user', owner)
260
260
261 try:
261 try:
262 new_repo_group = RepoGroup()
262 new_repo_group = RepoGroup()
263 new_repo_group.user = user
263 new_repo_group.user = user
264 new_repo_group.group_description = group_description or group_name
264 new_repo_group.group_description = group_description or group_name
265 new_repo_group.parent_group = parent_group
265 new_repo_group.parent_group = parent_group
266 new_repo_group.group_name = group_name
266 new_repo_group.group_name = group_name
267 new_repo_group.personal = personal
267 new_repo_group.personal = personal
268
268
269 self.sa.add(new_repo_group)
269 self.sa.add(new_repo_group)
270
270
271 # create an ADMIN permission for owner except if we're super admin,
271 # create an ADMIN permission for owner except if we're super admin,
272 # later owner should go into the owner field of groups
272 # later owner should go into the owner field of groups
273 if not user.is_admin:
273 if not user.is_admin:
274 self.grant_user_permission(repo_group=new_repo_group,
274 self.grant_user_permission(repo_group=new_repo_group,
275 user=owner, perm='group.admin')
275 user=owner, perm='group.admin')
276
276
277 if parent_group and copy_permissions:
277 if parent_group and copy_permissions:
278 # copy permissions from parent
278 # copy permissions from parent
279 user_perms = UserRepoGroupToPerm.query() \
279 user_perms = UserRepoGroupToPerm.query() \
280 .filter(UserRepoGroupToPerm.group == parent_group).all()
280 .filter(UserRepoGroupToPerm.group == parent_group).all()
281
281
282 group_perms = UserGroupRepoGroupToPerm.query() \
282 group_perms = UserGroupRepoGroupToPerm.query() \
283 .filter(UserGroupRepoGroupToPerm.group == parent_group).all()
283 .filter(UserGroupRepoGroupToPerm.group == parent_group).all()
284
284
285 for perm in user_perms:
285 for perm in user_perms:
286 # don't copy over the permission for user who is creating
286 # don't copy over the permission for user who is creating
287 # this group, if he is not super admin he get's admin
287 # this group, if he is not super admin he get's admin
288 # permission set above
288 # permission set above
289 if perm.user != user or user.is_admin:
289 if perm.user != user or user.is_admin:
290 UserRepoGroupToPerm.create(
290 UserRepoGroupToPerm.create(
291 perm.user, new_repo_group, perm.permission)
291 perm.user, new_repo_group, perm.permission)
292
292
293 for perm in group_perms:
293 for perm in group_perms:
294 UserGroupRepoGroupToPerm.create(
294 UserGroupRepoGroupToPerm.create(
295 perm.users_group, new_repo_group, perm.permission)
295 perm.users_group, new_repo_group, perm.permission)
296 else:
296 else:
297 perm_obj = self._create_default_perms(new_repo_group)
297 perm_obj = self._create_default_perms(new_repo_group)
298 self.sa.add(perm_obj)
298 self.sa.add(perm_obj)
299
299
300 # now commit the changes, earlier so we are sure everything is in
300 # now commit the changes, earlier so we are sure everything is in
301 # the database.
301 # the database.
302 if commit_early:
302 if commit_early:
303 self.sa.commit()
303 self.sa.commit()
304 if not just_db:
304 if not just_db:
305 self._create_group(new_repo_group.group_name)
305 self._create_group(new_repo_group.group_name)
306
306
307 # trigger the post hook
307 # trigger the post hook
308 from rhodecode.lib.hooks_base import log_create_repository_group
308 from rhodecode.lib.hooks_base import log_create_repository_group
309 repo_group = RepoGroup.get_by_group_name(group_name)
309 repo_group = RepoGroup.get_by_group_name(group_name)
310 log_create_repository_group(
310 log_create_repository_group(
311 created_by=user.username, **repo_group.get_dict())
311 created_by=user.username, **repo_group.get_dict())
312
312
313 # Trigger create event.
313 # Trigger create event.
314 events.trigger(events.RepoGroupCreateEvent(repo_group))
314 events.trigger(events.RepoGroupCreateEvent(repo_group))
315
315
316 return new_repo_group
316 return new_repo_group
317 except Exception:
317 except Exception:
318 self.sa.rollback()
318 self.sa.rollback()
319 log.exception('Exception occurred when creating repository group, '
319 log.exception('Exception occurred when creating repository group, '
320 'doing cleanup...')
320 'doing cleanup...')
321 # rollback things manually !
321 # rollback things manually !
322 repo_group = RepoGroup.get_by_group_name(group_name)
322 repo_group = RepoGroup.get_by_group_name(group_name)
323 if repo_group:
323 if repo_group:
324 RepoGroup.delete(repo_group.group_id)
324 RepoGroup.delete(repo_group.group_id)
325 self.sa.commit()
325 self.sa.commit()
326 if cleanup_group:
326 if cleanup_group:
327 RepoGroupModel()._delete_filesystem_group(repo_group)
327 RepoGroupModel()._delete_filesystem_group(repo_group)
328 raise
328 raise
329
329
330 def update_permissions(
330 def update_permissions(
331 self, repo_group, perm_additions=None, perm_updates=None,
331 self, repo_group, perm_additions=None, perm_updates=None,
332 perm_deletions=None, recursive=None, check_perms=True,
332 perm_deletions=None, recursive=None, check_perms=True,
333 cur_user=None):
333 cur_user=None):
334 from rhodecode.model.repo import RepoModel
334 from rhodecode.model.repo import RepoModel
335 from rhodecode.lib.auth import HasUserGroupPermissionAny
335 from rhodecode.lib.auth import HasUserGroupPermissionAny
336
336
337 if not perm_additions:
337 if not perm_additions:
338 perm_additions = []
338 perm_additions = []
339 if not perm_updates:
339 if not perm_updates:
340 perm_updates = []
340 perm_updates = []
341 if not perm_deletions:
341 if not perm_deletions:
342 perm_deletions = []
342 perm_deletions = []
343
343
344 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
344 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
345
345
346 changes = {
346 changes = {
347 'added': [],
347 'added': [],
348 'updated': [],
348 'updated': [],
349 'deleted': []
349 'deleted': []
350 }
350 }
351
351
352 def _set_perm_user(obj, user, perm):
352 def _set_perm_user(obj, user, perm):
353 if isinstance(obj, RepoGroup):
353 if isinstance(obj, RepoGroup):
354 self.grant_user_permission(
354 self.grant_user_permission(
355 repo_group=obj, user=user, perm=perm)
355 repo_group=obj, user=user, perm=perm)
356 elif isinstance(obj, Repository):
356 elif isinstance(obj, Repository):
357 # private repos will not allow to change the default
357 # private repos will not allow to change the default
358 # permissions using recursive mode
358 # permissions using recursive mode
359 if obj.private and user == User.DEFAULT_USER:
359 if obj.private and user == User.DEFAULT_USER:
360 return
360 return
361
361
362 # we set group permission but we have to switch to repo
362 # we set group permission but we have to switch to repo
363 # permission
363 # permission
364 perm = perm.replace('group.', 'repository.')
364 perm = perm.replace('group.', 'repository.')
365 RepoModel().grant_user_permission(
365 RepoModel().grant_user_permission(
366 repo=obj, user=user, perm=perm)
366 repo=obj, user=user, perm=perm)
367
367
368 def _set_perm_group(obj, users_group, perm):
368 def _set_perm_group(obj, users_group, perm):
369 if isinstance(obj, RepoGroup):
369 if isinstance(obj, RepoGroup):
370 self.grant_user_group_permission(
370 self.grant_user_group_permission(
371 repo_group=obj, group_name=users_group, perm=perm)
371 repo_group=obj, group_name=users_group, perm=perm)
372 elif isinstance(obj, Repository):
372 elif isinstance(obj, Repository):
373 # we set group permission but we have to switch to repo
373 # we set group permission but we have to switch to repo
374 # permission
374 # permission
375 perm = perm.replace('group.', 'repository.')
375 perm = perm.replace('group.', 'repository.')
376 RepoModel().grant_user_group_permission(
376 RepoModel().grant_user_group_permission(
377 repo=obj, group_name=users_group, perm=perm)
377 repo=obj, group_name=users_group, perm=perm)
378
378
379 def _revoke_perm_user(obj, user):
379 def _revoke_perm_user(obj, user):
380 if isinstance(obj, RepoGroup):
380 if isinstance(obj, RepoGroup):
381 self.revoke_user_permission(repo_group=obj, user=user)
381 self.revoke_user_permission(repo_group=obj, user=user)
382 elif isinstance(obj, Repository):
382 elif isinstance(obj, Repository):
383 RepoModel().revoke_user_permission(repo=obj, user=user)
383 RepoModel().revoke_user_permission(repo=obj, user=user)
384
384
385 def _revoke_perm_group(obj, user_group):
385 def _revoke_perm_group(obj, user_group):
386 if isinstance(obj, RepoGroup):
386 if isinstance(obj, RepoGroup):
387 self.revoke_user_group_permission(
387 self.revoke_user_group_permission(
388 repo_group=obj, group_name=user_group)
388 repo_group=obj, group_name=user_group)
389 elif isinstance(obj, Repository):
389 elif isinstance(obj, Repository):
390 RepoModel().revoke_user_group_permission(
390 RepoModel().revoke_user_group_permission(
391 repo=obj, group_name=user_group)
391 repo=obj, group_name=user_group)
392
392
393 # start updates
393 # start updates
394 log.debug('Now updating permissions for %s in recursive mode:%s',
394 log.debug('Now updating permissions for %s in recursive mode:%s',
395 repo_group, recursive)
395 repo_group, recursive)
396
396
397 # initialize check function, we'll call that multiple times
397 # initialize check function, we'll call that multiple times
398 has_group_perm = HasUserGroupPermissionAny(*req_perms)
398 has_group_perm = HasUserGroupPermissionAny(*req_perms)
399
399
400 for obj in repo_group.recursive_groups_and_repos():
400 for obj in repo_group.recursive_groups_and_repos():
401 # iterated obj is an instance of a repos group or repository in
401 # iterated obj is an instance of a repos group or repository in
402 # that group, recursive option can be: none, repos, groups, all
402 # that group, recursive option can be: none, repos, groups, all
403 if recursive == 'all':
403 if recursive == 'all':
404 obj = obj
404 obj = obj
405 elif recursive == 'repos':
405 elif recursive == 'repos':
406 # skip groups, other than this one
406 # skip groups, other than this one
407 if isinstance(obj, RepoGroup) and not obj == repo_group:
407 if isinstance(obj, RepoGroup) and not obj == repo_group:
408 continue
408 continue
409 elif recursive == 'groups':
409 elif recursive == 'groups':
410 # skip repos
410 # skip repos
411 if isinstance(obj, Repository):
411 if isinstance(obj, Repository):
412 continue
412 continue
413 else: # recursive == 'none':
413 else: # recursive == 'none':
414 # DEFAULT option - don't apply to iterated objects
414 # DEFAULT option - don't apply to iterated objects
415 # also we do a break at the end of this loop. if we are not
415 # also we do a break at the end of this loop. if we are not
416 # in recursive mode
416 # in recursive mode
417 obj = repo_group
417 obj = repo_group
418
418
419 change_obj = obj.get_api_data()
419 change_obj = obj.get_api_data()
420
420
421 # update permissions
421 # update permissions
422 for member_id, perm, member_type in perm_updates:
422 for member_id, perm, member_type in perm_updates:
423 member_id = int(member_id)
423 member_id = int(member_id)
424 if member_type == 'user':
424 if member_type == 'user':
425 member_name = User.get(member_id).username
425 member_name = User.get(member_id).username
426 # this updates also current one if found
426 # this updates also current one if found
427 _set_perm_user(obj, user=member_id, perm=perm)
427 _set_perm_user(obj, user=member_id, perm=perm)
428 else: # set for user group
428 elif member_type == 'user_group':
429 member_name = UserGroup.get(member_id).users_group_name
429 member_name = UserGroup.get(member_id).users_group_name
430 if not check_perms or has_group_perm(member_name,
430 if not check_perms or has_group_perm(member_name,
431 user=cur_user):
431 user=cur_user):
432 _set_perm_group(obj, users_group=member_id, perm=perm)
432 _set_perm_group(obj, users_group=member_id, perm=perm)
433 else:
434 raise ValueError("member_type must be 'user' or 'user_group' "
435 "got {} instead".format(member_type))
433
436
434 changes['updated'].append(
437 changes['updated'].append(
435 {'change_obj': change_obj, 'type': member_type,
438 {'change_obj': change_obj, 'type': member_type,
436 'id': member_id, 'name': member_name, 'new_perm': perm})
439 'id': member_id, 'name': member_name, 'new_perm': perm})
437
440
438 # set new permissions
441 # set new permissions
439 for member_id, perm, member_type in perm_additions:
442 for member_id, perm, member_type in perm_additions:
440 member_id = int(member_id)
443 member_id = int(member_id)
441 if member_type == 'user':
444 if member_type == 'user':
442 member_name = User.get(member_id).username
445 member_name = User.get(member_id).username
443 _set_perm_user(obj, user=member_id, perm=perm)
446 _set_perm_user(obj, user=member_id, perm=perm)
444 else: # set for user group
447 elif member_type == 'user_group':
445 # check if we have permissions to alter this usergroup
448 # check if we have permissions to alter this usergroup
446 member_name = UserGroup.get(member_id).users_group_name
449 member_name = UserGroup.get(member_id).users_group_name
447 if not check_perms or has_group_perm(member_name,
450 if not check_perms or has_group_perm(member_name,
448 user=cur_user):
451 user=cur_user):
449 _set_perm_group(obj, users_group=member_id, perm=perm)
452 _set_perm_group(obj, users_group=member_id, perm=perm)
453 else:
454 raise ValueError("member_type must be 'user' or 'user_group' "
455 "got {} instead".format(member_type))
450
456
451 changes['added'].append(
457 changes['added'].append(
452 {'change_obj': change_obj, 'type': member_type,
458 {'change_obj': change_obj, 'type': member_type,
453 'id': member_id, 'name': member_name, 'new_perm': perm})
459 'id': member_id, 'name': member_name, 'new_perm': perm})
454
460
455 # delete permissions
461 # delete permissions
456 for member_id, perm, member_type in perm_deletions:
462 for member_id, perm, member_type in perm_deletions:
457 member_id = int(member_id)
463 member_id = int(member_id)
458 if member_type == 'user':
464 if member_type == 'user':
459 member_name = User.get(member_id).username
465 member_name = User.get(member_id).username
460 _revoke_perm_user(obj, user=member_id)
466 _revoke_perm_user(obj, user=member_id)
461 else: # set for user group
467 elif member_type == 'user_group':
462 # check if we have permissions to alter this usergroup
468 # check if we have permissions to alter this usergroup
463 member_name = UserGroup.get(member_id).users_group_name
469 member_name = UserGroup.get(member_id).users_group_name
464 if not check_perms or has_group_perm(member_name,
470 if not check_perms or has_group_perm(member_name,
465 user=cur_user):
471 user=cur_user):
466 _revoke_perm_group(obj, user_group=member_id)
472 _revoke_perm_group(obj, user_group=member_id)
473 else:
474 raise ValueError("member_type must be 'user' or 'user_group' "
475 "got {} instead".format(member_type))
467
476
468 changes['deleted'].append(
477 changes['deleted'].append(
469 {'change_obj': change_obj, 'type': member_type,
478 {'change_obj': change_obj, 'type': member_type,
470 'id': member_id, 'name': member_name, 'new_perm': perm})
479 'id': member_id, 'name': member_name, 'new_perm': perm})
471
480
472 # if it's not recursive call for all,repos,groups
481 # if it's not recursive call for all,repos,groups
473 # break the loop and don't proceed with other changes
482 # break the loop and don't proceed with other changes
474 if recursive not in ['all', 'repos', 'groups']:
483 if recursive not in ['all', 'repos', 'groups']:
475 break
484 break
476
485
477 return changes
486 return changes
478
487
479 def update(self, repo_group, form_data):
488 def update(self, repo_group, form_data):
480 try:
489 try:
481 repo_group = self._get_repo_group(repo_group)
490 repo_group = self._get_repo_group(repo_group)
482 old_path = repo_group.full_path
491 old_path = repo_group.full_path
483
492
484 # change properties
493 # change properties
485 if 'group_description' in form_data:
494 if 'group_description' in form_data:
486 repo_group.group_description = form_data['group_description']
495 repo_group.group_description = form_data['group_description']
487
496
488 if 'enable_locking' in form_data:
497 if 'enable_locking' in form_data:
489 repo_group.enable_locking = form_data['enable_locking']
498 repo_group.enable_locking = form_data['enable_locking']
490
499
491 if 'group_parent_id' in form_data:
500 if 'group_parent_id' in form_data:
492 parent_group = (
501 parent_group = (
493 self._get_repo_group(form_data['group_parent_id']))
502 self._get_repo_group(form_data['group_parent_id']))
494 repo_group.group_parent_id = (
503 repo_group.group_parent_id = (
495 parent_group.group_id if parent_group else None)
504 parent_group.group_id if parent_group else None)
496 repo_group.parent_group = parent_group
505 repo_group.parent_group = parent_group
497
506
498 # mikhail: to update the full_path, we have to explicitly
507 # mikhail: to update the full_path, we have to explicitly
499 # update group_name
508 # update group_name
500 group_name = form_data.get('group_name', repo_group.name)
509 group_name = form_data.get('group_name', repo_group.name)
501 repo_group.group_name = repo_group.get_new_name(group_name)
510 repo_group.group_name = repo_group.get_new_name(group_name)
502
511
503 new_path = repo_group.full_path
512 new_path = repo_group.full_path
504
513
505 if 'user' in form_data:
514 if 'user' in form_data:
506 repo_group.user = User.get_by_username(form_data['user'])
515 repo_group.user = User.get_by_username(form_data['user'])
507 repo_group.updated_on = datetime.datetime.now()
516 repo_group.updated_on = datetime.datetime.now()
508 self.sa.add(repo_group)
517 self.sa.add(repo_group)
509
518
510 # iterate over all members of this groups and do fixes
519 # iterate over all members of this groups and do fixes
511 # set locking if given
520 # set locking if given
512 # if obj is a repoGroup also fix the name of the group according
521 # if obj is a repoGroup also fix the name of the group according
513 # to the parent
522 # to the parent
514 # if obj is a Repo fix it's name
523 # if obj is a Repo fix it's name
515 # this can be potentially heavy operation
524 # this can be potentially heavy operation
516 for obj in repo_group.recursive_groups_and_repos():
525 for obj in repo_group.recursive_groups_and_repos():
517 # set the value from it's parent
526 # set the value from it's parent
518 obj.enable_locking = repo_group.enable_locking
527 obj.enable_locking = repo_group.enable_locking
519 if isinstance(obj, RepoGroup):
528 if isinstance(obj, RepoGroup):
520 new_name = obj.get_new_name(obj.name)
529 new_name = obj.get_new_name(obj.name)
521 log.debug('Fixing group %s to new name %s',
530 log.debug('Fixing group %s to new name %s',
522 obj.group_name, new_name)
531 obj.group_name, new_name)
523 obj.group_name = new_name
532 obj.group_name = new_name
524 obj.updated_on = datetime.datetime.now()
533 obj.updated_on = datetime.datetime.now()
525 elif isinstance(obj, Repository):
534 elif isinstance(obj, Repository):
526 # we need to get all repositories from this new group and
535 # we need to get all repositories from this new group and
527 # rename them accordingly to new group path
536 # rename them accordingly to new group path
528 new_name = obj.get_new_name(obj.just_name)
537 new_name = obj.get_new_name(obj.just_name)
529 log.debug('Fixing repo %s to new name %s',
538 log.debug('Fixing repo %s to new name %s',
530 obj.repo_name, new_name)
539 obj.repo_name, new_name)
531 obj.repo_name = new_name
540 obj.repo_name = new_name
532 obj.updated_on = datetime.datetime.now()
541 obj.updated_on = datetime.datetime.now()
533 self.sa.add(obj)
542 self.sa.add(obj)
534
543
535 self._rename_group(old_path, new_path)
544 self._rename_group(old_path, new_path)
536
545
537 # Trigger update event.
546 # Trigger update event.
538 events.trigger(events.RepoGroupUpdateEvent(repo_group))
547 events.trigger(events.RepoGroupUpdateEvent(repo_group))
539
548
540 return repo_group
549 return repo_group
541 except Exception:
550 except Exception:
542 log.error(traceback.format_exc())
551 log.error(traceback.format_exc())
543 raise
552 raise
544
553
545 def delete(self, repo_group, force_delete=False, fs_remove=True):
554 def delete(self, repo_group, force_delete=False, fs_remove=True):
546 repo_group = self._get_repo_group(repo_group)
555 repo_group = self._get_repo_group(repo_group)
547 if not repo_group:
556 if not repo_group:
548 return False
557 return False
549 try:
558 try:
550 self.sa.delete(repo_group)
559 self.sa.delete(repo_group)
551 if fs_remove:
560 if fs_remove:
552 self._delete_filesystem_group(repo_group, force_delete)
561 self._delete_filesystem_group(repo_group, force_delete)
553 else:
562 else:
554 log.debug('skipping removal from filesystem')
563 log.debug('skipping removal from filesystem')
555
564
556 # Trigger delete event.
565 # Trigger delete event.
557 events.trigger(events.RepoGroupDeleteEvent(repo_group))
566 events.trigger(events.RepoGroupDeleteEvent(repo_group))
558 return True
567 return True
559
568
560 except Exception:
569 except Exception:
561 log.error('Error removing repo_group %s', repo_group)
570 log.error('Error removing repo_group %s', repo_group)
562 raise
571 raise
563
572
564 def grant_user_permission(self, repo_group, user, perm):
573 def grant_user_permission(self, repo_group, user, perm):
565 """
574 """
566 Grant permission for user on given repository group, or update
575 Grant permission for user on given repository group, or update
567 existing one if found
576 existing one if found
568
577
569 :param repo_group: Instance of RepoGroup, repositories_group_id,
578 :param repo_group: Instance of RepoGroup, repositories_group_id,
570 or repositories_group name
579 or repositories_group name
571 :param user: Instance of User, user_id or username
580 :param user: Instance of User, user_id or username
572 :param perm: Instance of Permission, or permission_name
581 :param perm: Instance of Permission, or permission_name
573 """
582 """
574
583
575 repo_group = self._get_repo_group(repo_group)
584 repo_group = self._get_repo_group(repo_group)
576 user = self._get_user(user)
585 user = self._get_user(user)
577 permission = self._get_perm(perm)
586 permission = self._get_perm(perm)
578
587
579 # check if we have that permission already
588 # check if we have that permission already
580 obj = self.sa.query(UserRepoGroupToPerm)\
589 obj = self.sa.query(UserRepoGroupToPerm)\
581 .filter(UserRepoGroupToPerm.user == user)\
590 .filter(UserRepoGroupToPerm.user == user)\
582 .filter(UserRepoGroupToPerm.group == repo_group)\
591 .filter(UserRepoGroupToPerm.group == repo_group)\
583 .scalar()
592 .scalar()
584 if obj is None:
593 if obj is None:
585 # create new !
594 # create new !
586 obj = UserRepoGroupToPerm()
595 obj = UserRepoGroupToPerm()
587 obj.group = repo_group
596 obj.group = repo_group
588 obj.user = user
597 obj.user = user
589 obj.permission = permission
598 obj.permission = permission
590 self.sa.add(obj)
599 self.sa.add(obj)
591 log.debug('Granted perm %s to %s on %s', perm, user, repo_group)
600 log.debug('Granted perm %s to %s on %s', perm, user, repo_group)
592 action_logger_generic(
601 action_logger_generic(
593 'granted permission: {} to user: {} on repogroup: {}'.format(
602 'granted permission: {} to user: {} on repogroup: {}'.format(
594 perm, user, repo_group), namespace='security.repogroup')
603 perm, user, repo_group), namespace='security.repogroup')
595 return obj
604 return obj
596
605
597 def revoke_user_permission(self, repo_group, user):
606 def revoke_user_permission(self, repo_group, user):
598 """
607 """
599 Revoke permission for user on given repository group
608 Revoke permission for user on given repository group
600
609
601 :param repo_group: Instance of RepoGroup, repositories_group_id,
610 :param repo_group: Instance of RepoGroup, repositories_group_id,
602 or repositories_group name
611 or repositories_group name
603 :param user: Instance of User, user_id or username
612 :param user: Instance of User, user_id or username
604 """
613 """
605
614
606 repo_group = self._get_repo_group(repo_group)
615 repo_group = self._get_repo_group(repo_group)
607 user = self._get_user(user)
616 user = self._get_user(user)
608
617
609 obj = self.sa.query(UserRepoGroupToPerm)\
618 obj = self.sa.query(UserRepoGroupToPerm)\
610 .filter(UserRepoGroupToPerm.user == user)\
619 .filter(UserRepoGroupToPerm.user == user)\
611 .filter(UserRepoGroupToPerm.group == repo_group)\
620 .filter(UserRepoGroupToPerm.group == repo_group)\
612 .scalar()
621 .scalar()
613 if obj:
622 if obj:
614 self.sa.delete(obj)
623 self.sa.delete(obj)
615 log.debug('Revoked perm on %s on %s', repo_group, user)
624 log.debug('Revoked perm on %s on %s', repo_group, user)
616 action_logger_generic(
625 action_logger_generic(
617 'revoked permission from user: {} on repogroup: {}'.format(
626 'revoked permission from user: {} on repogroup: {}'.format(
618 user, repo_group), namespace='security.repogroup')
627 user, repo_group), namespace='security.repogroup')
619
628
620 def grant_user_group_permission(self, repo_group, group_name, perm):
629 def grant_user_group_permission(self, repo_group, group_name, perm):
621 """
630 """
622 Grant permission for user group on given repository group, or update
631 Grant permission for user group on given repository group, or update
623 existing one if found
632 existing one if found
624
633
625 :param repo_group: Instance of RepoGroup, repositories_group_id,
634 :param repo_group: Instance of RepoGroup, repositories_group_id,
626 or repositories_group name
635 or repositories_group name
627 :param group_name: Instance of UserGroup, users_group_id,
636 :param group_name: Instance of UserGroup, users_group_id,
628 or user group name
637 or user group name
629 :param perm: Instance of Permission, or permission_name
638 :param perm: Instance of Permission, or permission_name
630 """
639 """
631 repo_group = self._get_repo_group(repo_group)
640 repo_group = self._get_repo_group(repo_group)
632 group_name = self._get_user_group(group_name)
641 group_name = self._get_user_group(group_name)
633 permission = self._get_perm(perm)
642 permission = self._get_perm(perm)
634
643
635 # check if we have that permission already
644 # check if we have that permission already
636 obj = self.sa.query(UserGroupRepoGroupToPerm)\
645 obj = self.sa.query(UserGroupRepoGroupToPerm)\
637 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
646 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
638 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
647 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
639 .scalar()
648 .scalar()
640
649
641 if obj is None:
650 if obj is None:
642 # create new
651 # create new
643 obj = UserGroupRepoGroupToPerm()
652 obj = UserGroupRepoGroupToPerm()
644
653
645 obj.group = repo_group
654 obj.group = repo_group
646 obj.users_group = group_name
655 obj.users_group = group_name
647 obj.permission = permission
656 obj.permission = permission
648 self.sa.add(obj)
657 self.sa.add(obj)
649 log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group)
658 log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group)
650 action_logger_generic(
659 action_logger_generic(
651 'granted permission: {} to usergroup: {} on repogroup: {}'.format(
660 'granted permission: {} to usergroup: {} on repogroup: {}'.format(
652 perm, group_name, repo_group), namespace='security.repogroup')
661 perm, group_name, repo_group), namespace='security.repogroup')
653 return obj
662 return obj
654
663
655 def revoke_user_group_permission(self, repo_group, group_name):
664 def revoke_user_group_permission(self, repo_group, group_name):
656 """
665 """
657 Revoke permission for user group on given repository group
666 Revoke permission for user group on given repository group
658
667
659 :param repo_group: Instance of RepoGroup, repositories_group_id,
668 :param repo_group: Instance of RepoGroup, repositories_group_id,
660 or repositories_group name
669 or repositories_group name
661 :param group_name: Instance of UserGroup, users_group_id,
670 :param group_name: Instance of UserGroup, users_group_id,
662 or user group name
671 or user group name
663 """
672 """
664 repo_group = self._get_repo_group(repo_group)
673 repo_group = self._get_repo_group(repo_group)
665 group_name = self._get_user_group(group_name)
674 group_name = self._get_user_group(group_name)
666
675
667 obj = self.sa.query(UserGroupRepoGroupToPerm)\
676 obj = self.sa.query(UserGroupRepoGroupToPerm)\
668 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
677 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
669 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
678 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
670 .scalar()
679 .scalar()
671 if obj:
680 if obj:
672 self.sa.delete(obj)
681 self.sa.delete(obj)
673 log.debug('Revoked perm to %s on %s', repo_group, group_name)
682 log.debug('Revoked perm to %s on %s', repo_group, group_name)
674 action_logger_generic(
683 action_logger_generic(
675 'revoked permission from usergroup: {} on repogroup: {}'.format(
684 'revoked permission from usergroup: {} on repogroup: {}'.format(
676 group_name, repo_group), namespace='security.repogroup')
685 group_name, repo_group), namespace='security.repogroup')
677
686
678 def get_repo_groups_as_dict(self, repo_group_list=None, admin=False,
687 def get_repo_groups_as_dict(self, repo_group_list=None, admin=False,
679 super_user_actions=False):
688 super_user_actions=False):
680
689
681 from pyramid.threadlocal import get_current_request
690 from pyramid.threadlocal import get_current_request
682 _render = get_current_request().get_partial_renderer(
691 _render = get_current_request().get_partial_renderer(
683 'rhodecode:templates/data_table/_dt_elements.mako')
692 'rhodecode:templates/data_table/_dt_elements.mako')
684 c = _render.get_call_context()
693 c = _render.get_call_context()
685 h = _render.get_helpers()
694 h = _render.get_helpers()
686
695
687 def quick_menu(repo_group_name):
696 def quick_menu(repo_group_name):
688 return _render('quick_repo_group_menu', repo_group_name)
697 return _render('quick_repo_group_menu', repo_group_name)
689
698
690 def repo_group_lnk(repo_group_name):
699 def repo_group_lnk(repo_group_name):
691 return _render('repo_group_name', repo_group_name)
700 return _render('repo_group_name', repo_group_name)
692
701
693 def last_change(last_change):
702 def last_change(last_change):
694 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
703 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
695 last_change = last_change + datetime.timedelta(seconds=
704 last_change = last_change + datetime.timedelta(seconds=
696 (datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
705 (datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
697 return _render("last_change", last_change)
706 return _render("last_change", last_change)
698
707
699 def desc(desc, personal):
708 def desc(desc, personal):
700 return _render(
709 return _render(
701 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
710 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
702
711
703 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
712 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
704 return _render(
713 return _render(
705 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
714 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
706
715
707 def repo_group_name(repo_group_name, children_groups):
716 def repo_group_name(repo_group_name, children_groups):
708 return _render("repo_group_name", repo_group_name, children_groups)
717 return _render("repo_group_name", repo_group_name, children_groups)
709
718
710 def user_profile(username):
719 def user_profile(username):
711 return _render('user_profile', username)
720 return _render('user_profile', username)
712
721
713 repo_group_data = []
722 repo_group_data = []
714 for group in repo_group_list:
723 for group in repo_group_list:
715
724
716 row = {
725 row = {
717 "menu": quick_menu(group.group_name),
726 "menu": quick_menu(group.group_name),
718 "name": repo_group_lnk(group.group_name),
727 "name": repo_group_lnk(group.group_name),
719 "name_raw": group.group_name,
728 "name_raw": group.group_name,
720 "last_change": last_change(group.last_db_change),
729 "last_change": last_change(group.last_db_change),
721 "last_change_raw": datetime_to_time(group.last_db_change),
730 "last_change_raw": datetime_to_time(group.last_db_change),
722 "desc": desc(group.description_safe, group.personal),
731 "desc": desc(group.description_safe, group.personal),
723 "top_level_repos": 0,
732 "top_level_repos": 0,
724 "owner": user_profile(group.user.username)
733 "owner": user_profile(group.user.username)
725 }
734 }
726 if admin:
735 if admin:
727 repo_count = group.repositories.count()
736 repo_count = group.repositories.count()
728 children_groups = map(
737 children_groups = map(
729 h.safe_unicode,
738 h.safe_unicode,
730 itertools.chain((g.name for g in group.parents),
739 itertools.chain((g.name for g in group.parents),
731 (x.name for x in [group])))
740 (x.name for x in [group])))
732 row.update({
741 row.update({
733 "action": repo_group_actions(
742 "action": repo_group_actions(
734 group.group_id, group.group_name, repo_count),
743 group.group_id, group.group_name, repo_count),
735 "top_level_repos": repo_count,
744 "top_level_repos": repo_count,
736 "name": repo_group_name(group.group_name, children_groups),
745 "name": repo_group_name(group.group_name, children_groups),
737
746
738 })
747 })
739 repo_group_data.append(row)
748 repo_group_data.append(row)
740
749
741 return repo_group_data
750 return repo_group_data
742
751
743 def _get_defaults(self, repo_group_name):
752 def _get_defaults(self, repo_group_name):
744 repo_group = RepoGroup.get_by_group_name(repo_group_name)
753 repo_group = RepoGroup.get_by_group_name(repo_group_name)
745
754
746 if repo_group is None:
755 if repo_group is None:
747 return None
756 return None
748
757
749 defaults = repo_group.get_dict()
758 defaults = repo_group.get_dict()
750 defaults['repo_group_name'] = repo_group.name
759 defaults['repo_group_name'] = repo_group.name
751 defaults['repo_group_description'] = repo_group.group_description
760 defaults['repo_group_description'] = repo_group.group_description
752 defaults['repo_group_enable_locking'] = repo_group.enable_locking
761 defaults['repo_group_enable_locking'] = repo_group.enable_locking
753
762
754 # we use -1 as this is how in HTML, we mark an empty group
763 # we use -1 as this is how in HTML, we mark an empty group
755 defaults['repo_group'] = defaults['group_parent_id'] or -1
764 defaults['repo_group'] = defaults['group_parent_id'] or -1
756
765
757 # fill owner
766 # fill owner
758 if repo_group.user:
767 if repo_group.user:
759 defaults.update({'user': repo_group.user.username})
768 defaults.update({'user': repo_group.user.username})
760 else:
769 else:
761 replacement_user = User.get_first_super_admin().username
770 replacement_user = User.get_first_super_admin().username
762 defaults.update({'user': replacement_user})
771 defaults.update({'user': replacement_user})
763
772
764 return defaults
773 return defaults
@@ -1,745 +1,754 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2018 RhodeCode GmbH
3 # Copyright (C) 2011-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import traceback
22 import traceback
23
23
24 from rhodecode.lib.utils2 import safe_str, safe_unicode
24 from rhodecode.lib.utils2 import safe_str, safe_unicode
25 from rhodecode.lib.exceptions import (
25 from rhodecode.lib.exceptions import (
26 UserGroupAssignedException, RepoGroupAssignmentError)
26 UserGroupAssignedException, RepoGroupAssignmentError)
27 from rhodecode.lib.utils2 import (
27 from rhodecode.lib.utils2 import (
28 get_current_rhodecode_user, action_logger_generic)
28 get_current_rhodecode_user, action_logger_generic)
29 from rhodecode.model import BaseModel
29 from rhodecode.model import BaseModel
30 from rhodecode.model.scm import UserGroupList
30 from rhodecode.model.scm import UserGroupList
31 from rhodecode.model.db import (
31 from rhodecode.model.db import (
32 joinedload, true, func, User, UserGroupMember, UserGroup,
32 joinedload, true, func, User, UserGroupMember, UserGroup,
33 UserGroupRepoToPerm, Permission, UserGroupToPerm, UserUserGroupToPerm,
33 UserGroupRepoToPerm, Permission, UserGroupToPerm, UserUserGroupToPerm,
34 UserGroupUserGroupToPerm, UserGroupRepoGroupToPerm)
34 UserGroupUserGroupToPerm, UserGroupRepoGroupToPerm)
35
35
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39
39
40 class UserGroupModel(BaseModel):
40 class UserGroupModel(BaseModel):
41
41
42 cls = UserGroup
42 cls = UserGroup
43
43
44 def _get_user_group(self, user_group):
44 def _get_user_group(self, user_group):
45 return self._get_instance(UserGroup, user_group,
45 return self._get_instance(UserGroup, user_group,
46 callback=UserGroup.get_by_group_name)
46 callback=UserGroup.get_by_group_name)
47
47
48 def _create_default_perms(self, user_group):
48 def _create_default_perms(self, user_group):
49 # create default permission
49 # create default permission
50 default_perm = 'usergroup.read'
50 default_perm = 'usergroup.read'
51 def_user = User.get_default_user()
51 def_user = User.get_default_user()
52 for p in def_user.user_perms:
52 for p in def_user.user_perms:
53 if p.permission.permission_name.startswith('usergroup.'):
53 if p.permission.permission_name.startswith('usergroup.'):
54 default_perm = p.permission.permission_name
54 default_perm = p.permission.permission_name
55 break
55 break
56
56
57 user_group_to_perm = UserUserGroupToPerm()
57 user_group_to_perm = UserUserGroupToPerm()
58 user_group_to_perm.permission = Permission.get_by_key(default_perm)
58 user_group_to_perm.permission = Permission.get_by_key(default_perm)
59
59
60 user_group_to_perm.user_group = user_group
60 user_group_to_perm.user_group = user_group
61 user_group_to_perm.user_id = def_user.user_id
61 user_group_to_perm.user_id = def_user.user_id
62 return user_group_to_perm
62 return user_group_to_perm
63
63
64 def update_permissions(
64 def update_permissions(
65 self, user_group, perm_additions=None, perm_updates=None,
65 self, user_group, perm_additions=None, perm_updates=None,
66 perm_deletions=None, check_perms=True, cur_user=None):
66 perm_deletions=None, check_perms=True, cur_user=None):
67
67
68 from rhodecode.lib.auth import HasUserGroupPermissionAny
68 from rhodecode.lib.auth import HasUserGroupPermissionAny
69 if not perm_additions:
69 if not perm_additions:
70 perm_additions = []
70 perm_additions = []
71 if not perm_updates:
71 if not perm_updates:
72 perm_updates = []
72 perm_updates = []
73 if not perm_deletions:
73 if not perm_deletions:
74 perm_deletions = []
74 perm_deletions = []
75
75
76 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
76 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
77
77
78 changes = {
78 changes = {
79 'added': [],
79 'added': [],
80 'updated': [],
80 'updated': [],
81 'deleted': []
81 'deleted': []
82 }
82 }
83 change_obj = user_group.get_api_data()
83 change_obj = user_group.get_api_data()
84 # update permissions
84 # update permissions
85 for member_id, perm, member_type in perm_updates:
85 for member_id, perm, member_type in perm_updates:
86 member_id = int(member_id)
86 member_id = int(member_id)
87 if member_type == 'user':
87 if member_type == 'user':
88 member_name = User.get(member_id).username
88 member_name = User.get(member_id).username
89 # this updates existing one
89 # this updates existing one
90 self.grant_user_permission(
90 self.grant_user_permission(
91 user_group=user_group, user=member_id, perm=perm
91 user_group=user_group, user=member_id, perm=perm
92 )
92 )
93 else:
93 elif member_type == 'user_group':
94 # check if we have permissions to alter this usergroup
94 # check if we have permissions to alter this usergroup
95 member_name = UserGroup.get(member_id).users_group_name
95 member_name = UserGroup.get(member_id).users_group_name
96 if not check_perms or HasUserGroupPermissionAny(
96 if not check_perms or HasUserGroupPermissionAny(
97 *req_perms)(member_name, user=cur_user):
97 *req_perms)(member_name, user=cur_user):
98 self.grant_user_group_permission(
98 self.grant_user_group_permission(
99 target_user_group=user_group, user_group=member_id, perm=perm)
99 target_user_group=user_group, user_group=member_id, perm=perm)
100 else:
101 raise ValueError("member_type must be 'user' or 'user_group' "
102 "got {} instead".format(member_type))
100
103
101 changes['updated'].append({
104 changes['updated'].append({
102 'change_obj': change_obj,
105 'change_obj': change_obj,
103 'type': member_type, 'id': member_id,
106 'type': member_type, 'id': member_id,
104 'name': member_name, 'new_perm': perm})
107 'name': member_name, 'new_perm': perm})
105
108
106 # set new permissions
109 # set new permissions
107 for member_id, perm, member_type in perm_additions:
110 for member_id, perm, member_type in perm_additions:
108 member_id = int(member_id)
111 member_id = int(member_id)
109 if member_type == 'user':
112 if member_type == 'user':
110 member_name = User.get(member_id).username
113 member_name = User.get(member_id).username
111 self.grant_user_permission(
114 self.grant_user_permission(
112 user_group=user_group, user=member_id, perm=perm)
115 user_group=user_group, user=member_id, perm=perm)
113 else:
116 elif member_type == 'user_group':
114 # check if we have permissions to alter this usergroup
117 # check if we have permissions to alter this usergroup
115 member_name = UserGroup.get(member_id).users_group_name
118 member_name = UserGroup.get(member_id).users_group_name
116 if not check_perms or HasUserGroupPermissionAny(
119 if not check_perms or HasUserGroupPermissionAny(
117 *req_perms)(member_name, user=cur_user):
120 *req_perms)(member_name, user=cur_user):
118 self.grant_user_group_permission(
121 self.grant_user_group_permission(
119 target_user_group=user_group, user_group=member_id, perm=perm)
122 target_user_group=user_group, user_group=member_id, perm=perm)
123 else:
124 raise ValueError("member_type must be 'user' or 'user_group' "
125 "got {} instead".format(member_type))
120
126
121 changes['added'].append({
127 changes['added'].append({
122 'change_obj': change_obj,
128 'change_obj': change_obj,
123 'type': member_type, 'id': member_id,
129 'type': member_type, 'id': member_id,
124 'name': member_name, 'new_perm': perm})
130 'name': member_name, 'new_perm': perm})
125
131
126 # delete permissions
132 # delete permissions
127 for member_id, perm, member_type in perm_deletions:
133 for member_id, perm, member_type in perm_deletions:
128 member_id = int(member_id)
134 member_id = int(member_id)
129 if member_type == 'user':
135 if member_type == 'user':
130 member_name = User.get(member_id).username
136 member_name = User.get(member_id).username
131 self.revoke_user_permission(user_group=user_group, user=member_id)
137 self.revoke_user_permission(user_group=user_group, user=member_id)
132 else:
138 elif member_type == 'user_group':
133 # check if we have permissions to alter this usergroup
139 # check if we have permissions to alter this usergroup
134 member_name = UserGroup.get(member_id).users_group_name
140 member_name = UserGroup.get(member_id).users_group_name
135 if not check_perms or HasUserGroupPermissionAny(
141 if not check_perms or HasUserGroupPermissionAny(
136 *req_perms)(member_name, user=cur_user):
142 *req_perms)(member_name, user=cur_user):
137 self.revoke_user_group_permission(
143 self.revoke_user_group_permission(
138 target_user_group=user_group, user_group=member_id)
144 target_user_group=user_group, user_group=member_id)
145 else:
146 raise ValueError("member_type must be 'user' or 'user_group' "
147 "got {} instead".format(member_type))
139
148
140 changes['deleted'].append({
149 changes['deleted'].append({
141 'change_obj': change_obj,
150 'change_obj': change_obj,
142 'type': member_type, 'id': member_id,
151 'type': member_type, 'id': member_id,
143 'name': member_name, 'new_perm': perm})
152 'name': member_name, 'new_perm': perm})
144
153
145 return changes
154 return changes
146
155
147 def get(self, user_group_id, cache=False):
156 def get(self, user_group_id, cache=False):
148 return UserGroup.get(user_group_id)
157 return UserGroup.get(user_group_id)
149
158
150 def get_group(self, user_group):
159 def get_group(self, user_group):
151 return self._get_user_group(user_group)
160 return self._get_user_group(user_group)
152
161
153 def get_by_name(self, name, cache=False, case_insensitive=False):
162 def get_by_name(self, name, cache=False, case_insensitive=False):
154 return UserGroup.get_by_group_name(name, cache, case_insensitive)
163 return UserGroup.get_by_group_name(name, cache, case_insensitive)
155
164
156 def create(self, name, description, owner, active=True, group_data=None):
165 def create(self, name, description, owner, active=True, group_data=None):
157 try:
166 try:
158 new_user_group = UserGroup()
167 new_user_group = UserGroup()
159 new_user_group.user = self._get_user(owner)
168 new_user_group.user = self._get_user(owner)
160 new_user_group.users_group_name = name
169 new_user_group.users_group_name = name
161 new_user_group.user_group_description = description
170 new_user_group.user_group_description = description
162 new_user_group.users_group_active = active
171 new_user_group.users_group_active = active
163 if group_data:
172 if group_data:
164 new_user_group.group_data = group_data
173 new_user_group.group_data = group_data
165 self.sa.add(new_user_group)
174 self.sa.add(new_user_group)
166 perm_obj = self._create_default_perms(new_user_group)
175 perm_obj = self._create_default_perms(new_user_group)
167 self.sa.add(perm_obj)
176 self.sa.add(perm_obj)
168
177
169 self.grant_user_permission(user_group=new_user_group,
178 self.grant_user_permission(user_group=new_user_group,
170 user=owner, perm='usergroup.admin')
179 user=owner, perm='usergroup.admin')
171
180
172 return new_user_group
181 return new_user_group
173 except Exception:
182 except Exception:
174 log.error(traceback.format_exc())
183 log.error(traceback.format_exc())
175 raise
184 raise
176
185
177 def _get_memberships_for_user_ids(self, user_group, user_id_list):
186 def _get_memberships_for_user_ids(self, user_group, user_id_list):
178 members = []
187 members = []
179 for user_id in user_id_list:
188 for user_id in user_id_list:
180 member = self._get_membership(user_group.users_group_id, user_id)
189 member = self._get_membership(user_group.users_group_id, user_id)
181 members.append(member)
190 members.append(member)
182 return members
191 return members
183
192
184 def _get_added_and_removed_user_ids(self, user_group, user_id_list):
193 def _get_added_and_removed_user_ids(self, user_group, user_id_list):
185 current_members = user_group.members or []
194 current_members = user_group.members or []
186 current_members_ids = [m.user.user_id for m in current_members]
195 current_members_ids = [m.user.user_id for m in current_members]
187
196
188 added_members = [
197 added_members = [
189 user_id for user_id in user_id_list
198 user_id for user_id in user_id_list
190 if user_id not in current_members_ids]
199 if user_id not in current_members_ids]
191 if user_id_list == []:
200 if user_id_list == []:
192 # all members were deleted
201 # all members were deleted
193 deleted_members = current_members_ids
202 deleted_members = current_members_ids
194 else:
203 else:
195 deleted_members = [
204 deleted_members = [
196 user_id for user_id in current_members_ids
205 user_id for user_id in current_members_ids
197 if user_id not in user_id_list]
206 if user_id not in user_id_list]
198
207
199 return added_members, deleted_members
208 return added_members, deleted_members
200
209
201 def _set_users_as_members(self, user_group, user_ids):
210 def _set_users_as_members(self, user_group, user_ids):
202 user_group.members = []
211 user_group.members = []
203 self.sa.flush()
212 self.sa.flush()
204 members = self._get_memberships_for_user_ids(
213 members = self._get_memberships_for_user_ids(
205 user_group, user_ids)
214 user_group, user_ids)
206 user_group.members = members
215 user_group.members = members
207 self.sa.add(user_group)
216 self.sa.add(user_group)
208
217
209 def _update_members_from_user_ids(self, user_group, user_ids):
218 def _update_members_from_user_ids(self, user_group, user_ids):
210 added, removed = self._get_added_and_removed_user_ids(
219 added, removed = self._get_added_and_removed_user_ids(
211 user_group, user_ids)
220 user_group, user_ids)
212 self._set_users_as_members(user_group, user_ids)
221 self._set_users_as_members(user_group, user_ids)
213 self._log_user_changes('added to', user_group, added)
222 self._log_user_changes('added to', user_group, added)
214 self._log_user_changes('removed from', user_group, removed)
223 self._log_user_changes('removed from', user_group, removed)
215 return added, removed
224 return added, removed
216
225
217 def _clean_members_data(self, members_data):
226 def _clean_members_data(self, members_data):
218 if not members_data:
227 if not members_data:
219 members_data = []
228 members_data = []
220
229
221 members = []
230 members = []
222 for user in members_data:
231 for user in members_data:
223 uid = int(user['member_user_id'])
232 uid = int(user['member_user_id'])
224 if uid not in members and user['type'] in ['new', 'existing']:
233 if uid not in members and user['type'] in ['new', 'existing']:
225 members.append(uid)
234 members.append(uid)
226 return members
235 return members
227
236
228 def update(self, user_group, form_data, group_data=None):
237 def update(self, user_group, form_data, group_data=None):
229 user_group = self._get_user_group(user_group)
238 user_group = self._get_user_group(user_group)
230 if 'users_group_name' in form_data:
239 if 'users_group_name' in form_data:
231 user_group.users_group_name = form_data['users_group_name']
240 user_group.users_group_name = form_data['users_group_name']
232 if 'users_group_active' in form_data:
241 if 'users_group_active' in form_data:
233 user_group.users_group_active = form_data['users_group_active']
242 user_group.users_group_active = form_data['users_group_active']
234 if 'user_group_description' in form_data:
243 if 'user_group_description' in form_data:
235 user_group.user_group_description = form_data[
244 user_group.user_group_description = form_data[
236 'user_group_description']
245 'user_group_description']
237
246
238 # handle owner change
247 # handle owner change
239 if 'user' in form_data:
248 if 'user' in form_data:
240 owner = form_data['user']
249 owner = form_data['user']
241 if isinstance(owner, basestring):
250 if isinstance(owner, basestring):
242 owner = User.get_by_username(form_data['user'])
251 owner = User.get_by_username(form_data['user'])
243
252
244 if not isinstance(owner, User):
253 if not isinstance(owner, User):
245 raise ValueError(
254 raise ValueError(
246 'invalid owner for user group: %s' % form_data['user'])
255 'invalid owner for user group: %s' % form_data['user'])
247
256
248 user_group.user = owner
257 user_group.user = owner
249
258
250 added_user_ids = []
259 added_user_ids = []
251 removed_user_ids = []
260 removed_user_ids = []
252 if 'users_group_members' in form_data:
261 if 'users_group_members' in form_data:
253 members_id_list = self._clean_members_data(
262 members_id_list = self._clean_members_data(
254 form_data['users_group_members'])
263 form_data['users_group_members'])
255 added_user_ids, removed_user_ids = \
264 added_user_ids, removed_user_ids = \
256 self._update_members_from_user_ids(user_group, members_id_list)
265 self._update_members_from_user_ids(user_group, members_id_list)
257
266
258 if group_data:
267 if group_data:
259 new_group_data = {}
268 new_group_data = {}
260 new_group_data.update(group_data)
269 new_group_data.update(group_data)
261 user_group.group_data = new_group_data
270 user_group.group_data = new_group_data
262
271
263 self.sa.add(user_group)
272 self.sa.add(user_group)
264 return user_group, added_user_ids, removed_user_ids
273 return user_group, added_user_ids, removed_user_ids
265
274
266 def delete(self, user_group, force=False):
275 def delete(self, user_group, force=False):
267 """
276 """
268 Deletes repository group, unless force flag is used
277 Deletes repository group, unless force flag is used
269 raises exception if there are members in that group, else deletes
278 raises exception if there are members in that group, else deletes
270 group and users
279 group and users
271
280
272 :param user_group:
281 :param user_group:
273 :param force:
282 :param force:
274 """
283 """
275 user_group = self._get_user_group(user_group)
284 user_group = self._get_user_group(user_group)
276 if not user_group:
285 if not user_group:
277 return
286 return
278
287
279 try:
288 try:
280 # check if this group is not assigned to repo
289 # check if this group is not assigned to repo
281 assigned_to_repo = [x.repository for x in UserGroupRepoToPerm.query()\
290 assigned_to_repo = [x.repository for x in UserGroupRepoToPerm.query()\
282 .filter(UserGroupRepoToPerm.users_group == user_group).all()]
291 .filter(UserGroupRepoToPerm.users_group == user_group).all()]
283 # check if this group is not assigned to repo
292 # check if this group is not assigned to repo
284 assigned_to_repo_group = [x.group for x in UserGroupRepoGroupToPerm.query()\
293 assigned_to_repo_group = [x.group for x in UserGroupRepoGroupToPerm.query()\
285 .filter(UserGroupRepoGroupToPerm.users_group == user_group).all()]
294 .filter(UserGroupRepoGroupToPerm.users_group == user_group).all()]
286
295
287 if (assigned_to_repo or assigned_to_repo_group) and not force:
296 if (assigned_to_repo or assigned_to_repo_group) and not force:
288 assigned = ','.join(map(safe_str,
297 assigned = ','.join(map(safe_str,
289 assigned_to_repo+assigned_to_repo_group))
298 assigned_to_repo+assigned_to_repo_group))
290
299
291 raise UserGroupAssignedException(
300 raise UserGroupAssignedException(
292 'UserGroup assigned to %s' % (assigned,))
301 'UserGroup assigned to %s' % (assigned,))
293 self.sa.delete(user_group)
302 self.sa.delete(user_group)
294 except Exception:
303 except Exception:
295 log.error(traceback.format_exc())
304 log.error(traceback.format_exc())
296 raise
305 raise
297
306
298 def _log_user_changes(self, action, user_group, user_or_users):
307 def _log_user_changes(self, action, user_group, user_or_users):
299 users = user_or_users
308 users = user_or_users
300 if not isinstance(users, (list, tuple)):
309 if not isinstance(users, (list, tuple)):
301 users = [users]
310 users = [users]
302
311
303 group_name = user_group.users_group_name
312 group_name = user_group.users_group_name
304
313
305 for user_or_user_id in users:
314 for user_or_user_id in users:
306 user = self._get_user(user_or_user_id)
315 user = self._get_user(user_or_user_id)
307 log_text = 'User {user} {action} {group}'.format(
316 log_text = 'User {user} {action} {group}'.format(
308 action=action, user=user.username, group=group_name)
317 action=action, user=user.username, group=group_name)
309 action_logger_generic(log_text)
318 action_logger_generic(log_text)
310
319
311 def _find_user_in_group(self, user, user_group):
320 def _find_user_in_group(self, user, user_group):
312 user_group_member = None
321 user_group_member = None
313 for m in user_group.members:
322 for m in user_group.members:
314 if m.user_id == user.user_id:
323 if m.user_id == user.user_id:
315 # Found this user's membership row
324 # Found this user's membership row
316 user_group_member = m
325 user_group_member = m
317 break
326 break
318
327
319 return user_group_member
328 return user_group_member
320
329
321 def _get_membership(self, user_group_id, user_id):
330 def _get_membership(self, user_group_id, user_id):
322 user_group_member = UserGroupMember(user_group_id, user_id)
331 user_group_member = UserGroupMember(user_group_id, user_id)
323 return user_group_member
332 return user_group_member
324
333
325 def add_user_to_group(self, user_group, user):
334 def add_user_to_group(self, user_group, user):
326 user_group = self._get_user_group(user_group)
335 user_group = self._get_user_group(user_group)
327 user = self._get_user(user)
336 user = self._get_user(user)
328 user_member = self._find_user_in_group(user, user_group)
337 user_member = self._find_user_in_group(user, user_group)
329 if user_member:
338 if user_member:
330 # user already in the group, skip
339 # user already in the group, skip
331 return True
340 return True
332
341
333 member = self._get_membership(
342 member = self._get_membership(
334 user_group.users_group_id, user.user_id)
343 user_group.users_group_id, user.user_id)
335 user_group.members.append(member)
344 user_group.members.append(member)
336
345
337 try:
346 try:
338 self.sa.add(member)
347 self.sa.add(member)
339 except Exception:
348 except Exception:
340 # what could go wrong here?
349 # what could go wrong here?
341 log.error(traceback.format_exc())
350 log.error(traceback.format_exc())
342 raise
351 raise
343
352
344 self._log_user_changes('added to', user_group, user)
353 self._log_user_changes('added to', user_group, user)
345 return member
354 return member
346
355
347 def remove_user_from_group(self, user_group, user):
356 def remove_user_from_group(self, user_group, user):
348 user_group = self._get_user_group(user_group)
357 user_group = self._get_user_group(user_group)
349 user = self._get_user(user)
358 user = self._get_user(user)
350 user_group_member = self._find_user_in_group(user, user_group)
359 user_group_member = self._find_user_in_group(user, user_group)
351
360
352 if not user_group_member:
361 if not user_group_member:
353 # User isn't in that group
362 # User isn't in that group
354 return False
363 return False
355
364
356 try:
365 try:
357 self.sa.delete(user_group_member)
366 self.sa.delete(user_group_member)
358 except Exception:
367 except Exception:
359 log.error(traceback.format_exc())
368 log.error(traceback.format_exc())
360 raise
369 raise
361
370
362 self._log_user_changes('removed from', user_group, user)
371 self._log_user_changes('removed from', user_group, user)
363 return True
372 return True
364
373
365 def has_perm(self, user_group, perm):
374 def has_perm(self, user_group, perm):
366 user_group = self._get_user_group(user_group)
375 user_group = self._get_user_group(user_group)
367 perm = self._get_perm(perm)
376 perm = self._get_perm(perm)
368
377
369 return UserGroupToPerm.query()\
378 return UserGroupToPerm.query()\
370 .filter(UserGroupToPerm.users_group == user_group)\
379 .filter(UserGroupToPerm.users_group == user_group)\
371 .filter(UserGroupToPerm.permission == perm).scalar() is not None
380 .filter(UserGroupToPerm.permission == perm).scalar() is not None
372
381
373 def grant_perm(self, user_group, perm):
382 def grant_perm(self, user_group, perm):
374 user_group = self._get_user_group(user_group)
383 user_group = self._get_user_group(user_group)
375 perm = self._get_perm(perm)
384 perm = self._get_perm(perm)
376
385
377 # if this permission is already granted skip it
386 # if this permission is already granted skip it
378 _perm = UserGroupToPerm.query()\
387 _perm = UserGroupToPerm.query()\
379 .filter(UserGroupToPerm.users_group == user_group)\
388 .filter(UserGroupToPerm.users_group == user_group)\
380 .filter(UserGroupToPerm.permission == perm)\
389 .filter(UserGroupToPerm.permission == perm)\
381 .scalar()
390 .scalar()
382 if _perm:
391 if _perm:
383 return
392 return
384
393
385 new = UserGroupToPerm()
394 new = UserGroupToPerm()
386 new.users_group = user_group
395 new.users_group = user_group
387 new.permission = perm
396 new.permission = perm
388 self.sa.add(new)
397 self.sa.add(new)
389 return new
398 return new
390
399
391 def revoke_perm(self, user_group, perm):
400 def revoke_perm(self, user_group, perm):
392 user_group = self._get_user_group(user_group)
401 user_group = self._get_user_group(user_group)
393 perm = self._get_perm(perm)
402 perm = self._get_perm(perm)
394
403
395 obj = UserGroupToPerm.query()\
404 obj = UserGroupToPerm.query()\
396 .filter(UserGroupToPerm.users_group == user_group)\
405 .filter(UserGroupToPerm.users_group == user_group)\
397 .filter(UserGroupToPerm.permission == perm).scalar()
406 .filter(UserGroupToPerm.permission == perm).scalar()
398 if obj:
407 if obj:
399 self.sa.delete(obj)
408 self.sa.delete(obj)
400
409
401 def grant_user_permission(self, user_group, user, perm):
410 def grant_user_permission(self, user_group, user, perm):
402 """
411 """
403 Grant permission for user on given user group, or update
412 Grant permission for user on given user group, or update
404 existing one if found
413 existing one if found
405
414
406 :param user_group: Instance of UserGroup, users_group_id,
415 :param user_group: Instance of UserGroup, users_group_id,
407 or users_group_name
416 or users_group_name
408 :param user: Instance of User, user_id or username
417 :param user: Instance of User, user_id or username
409 :param perm: Instance of Permission, or permission_name
418 :param perm: Instance of Permission, or permission_name
410 """
419 """
411 changes = {
420 changes = {
412 'added': [],
421 'added': [],
413 'updated': [],
422 'updated': [],
414 'deleted': []
423 'deleted': []
415 }
424 }
416
425
417 user_group = self._get_user_group(user_group)
426 user_group = self._get_user_group(user_group)
418 user = self._get_user(user)
427 user = self._get_user(user)
419 permission = self._get_perm(perm)
428 permission = self._get_perm(perm)
420 perm_name = permission.permission_name
429 perm_name = permission.permission_name
421 member_id = user.user_id
430 member_id = user.user_id
422 member_name = user.username
431 member_name = user.username
423
432
424 # check if we have that permission already
433 # check if we have that permission already
425 obj = self.sa.query(UserUserGroupToPerm)\
434 obj = self.sa.query(UserUserGroupToPerm)\
426 .filter(UserUserGroupToPerm.user == user)\
435 .filter(UserUserGroupToPerm.user == user)\
427 .filter(UserUserGroupToPerm.user_group == user_group)\
436 .filter(UserUserGroupToPerm.user_group == user_group)\
428 .scalar()
437 .scalar()
429 if obj is None:
438 if obj is None:
430 # create new !
439 # create new !
431 obj = UserUserGroupToPerm()
440 obj = UserUserGroupToPerm()
432 obj.user_group = user_group
441 obj.user_group = user_group
433 obj.user = user
442 obj.user = user
434 obj.permission = permission
443 obj.permission = permission
435 self.sa.add(obj)
444 self.sa.add(obj)
436 log.debug('Granted perm %s to %s on %s', perm, user, user_group)
445 log.debug('Granted perm %s to %s on %s', perm, user, user_group)
437 action_logger_generic(
446 action_logger_generic(
438 'granted permission: {} to user: {} on usergroup: {}'.format(
447 'granted permission: {} to user: {} on usergroup: {}'.format(
439 perm, user, user_group), namespace='security.usergroup')
448 perm, user, user_group), namespace='security.usergroup')
440
449
441 changes['added'].append({
450 changes['added'].append({
442 'change_obj': user_group.get_api_data(),
451 'change_obj': user_group.get_api_data(),
443 'type': 'user', 'id': member_id,
452 'type': 'user', 'id': member_id,
444 'name': member_name, 'new_perm': perm_name})
453 'name': member_name, 'new_perm': perm_name})
445
454
446 return changes
455 return changes
447
456
448 def revoke_user_permission(self, user_group, user):
457 def revoke_user_permission(self, user_group, user):
449 """
458 """
450 Revoke permission for user on given user group
459 Revoke permission for user on given user group
451
460
452 :param user_group: Instance of UserGroup, users_group_id,
461 :param user_group: Instance of UserGroup, users_group_id,
453 or users_group name
462 or users_group name
454 :param user: Instance of User, user_id or username
463 :param user: Instance of User, user_id or username
455 """
464 """
456 changes = {
465 changes = {
457 'added': [],
466 'added': [],
458 'updated': [],
467 'updated': [],
459 'deleted': []
468 'deleted': []
460 }
469 }
461
470
462 user_group = self._get_user_group(user_group)
471 user_group = self._get_user_group(user_group)
463 user = self._get_user(user)
472 user = self._get_user(user)
464 perm_name = 'usergroup.none'
473 perm_name = 'usergroup.none'
465 member_id = user.user_id
474 member_id = user.user_id
466 member_name = user.username
475 member_name = user.username
467
476
468 obj = self.sa.query(UserUserGroupToPerm)\
477 obj = self.sa.query(UserUserGroupToPerm)\
469 .filter(UserUserGroupToPerm.user == user)\
478 .filter(UserUserGroupToPerm.user == user)\
470 .filter(UserUserGroupToPerm.user_group == user_group)\
479 .filter(UserUserGroupToPerm.user_group == user_group)\
471 .scalar()
480 .scalar()
472 if obj:
481 if obj:
473 self.sa.delete(obj)
482 self.sa.delete(obj)
474 log.debug('Revoked perm on %s on %s', user_group, user)
483 log.debug('Revoked perm on %s on %s', user_group, user)
475 action_logger_generic(
484 action_logger_generic(
476 'revoked permission from user: {} on usergroup: {}'.format(
485 'revoked permission from user: {} on usergroup: {}'.format(
477 user, user_group), namespace='security.usergroup')
486 user, user_group), namespace='security.usergroup')
478
487
479 changes['deleted'].append({
488 changes['deleted'].append({
480 'change_obj': user_group.get_api_data(),
489 'change_obj': user_group.get_api_data(),
481 'type': 'user', 'id': member_id,
490 'type': 'user', 'id': member_id,
482 'name': member_name, 'new_perm': perm_name})
491 'name': member_name, 'new_perm': perm_name})
483
492
484 return changes
493 return changes
485
494
486 def grant_user_group_permission(self, target_user_group, user_group, perm):
495 def grant_user_group_permission(self, target_user_group, user_group, perm):
487 """
496 """
488 Grant user group permission for given target_user_group
497 Grant user group permission for given target_user_group
489
498
490 :param target_user_group:
499 :param target_user_group:
491 :param user_group:
500 :param user_group:
492 :param perm:
501 :param perm:
493 """
502 """
494 changes = {
503 changes = {
495 'added': [],
504 'added': [],
496 'updated': [],
505 'updated': [],
497 'deleted': []
506 'deleted': []
498 }
507 }
499
508
500 target_user_group = self._get_user_group(target_user_group)
509 target_user_group = self._get_user_group(target_user_group)
501 user_group = self._get_user_group(user_group)
510 user_group = self._get_user_group(user_group)
502 permission = self._get_perm(perm)
511 permission = self._get_perm(perm)
503 perm_name = permission.permission_name
512 perm_name = permission.permission_name
504 member_id = user_group.users_group_id
513 member_id = user_group.users_group_id
505 member_name = user_group.users_group_name
514 member_name = user_group.users_group_name
506
515
507 # forbid assigning same user group to itself
516 # forbid assigning same user group to itself
508 if target_user_group == user_group:
517 if target_user_group == user_group:
509 raise RepoGroupAssignmentError('target repo:%s cannot be '
518 raise RepoGroupAssignmentError('target repo:%s cannot be '
510 'assigned to itself' % target_user_group)
519 'assigned to itself' % target_user_group)
511
520
512 # check if we have that permission already
521 # check if we have that permission already
513 obj = self.sa.query(UserGroupUserGroupToPerm)\
522 obj = self.sa.query(UserGroupUserGroupToPerm)\
514 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
523 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
515 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
524 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
516 .scalar()
525 .scalar()
517 if obj is None:
526 if obj is None:
518 # create new !
527 # create new !
519 obj = UserGroupUserGroupToPerm()
528 obj = UserGroupUserGroupToPerm()
520 obj.user_group = user_group
529 obj.user_group = user_group
521 obj.target_user_group = target_user_group
530 obj.target_user_group = target_user_group
522 obj.permission = permission
531 obj.permission = permission
523 self.sa.add(obj)
532 self.sa.add(obj)
524 log.debug(
533 log.debug(
525 'Granted perm %s to %s on %s', perm, target_user_group, user_group)
534 'Granted perm %s to %s on %s', perm, target_user_group, user_group)
526 action_logger_generic(
535 action_logger_generic(
527 'granted permission: {} to usergroup: {} on usergroup: {}'.format(
536 'granted permission: {} to usergroup: {} on usergroup: {}'.format(
528 perm, user_group, target_user_group),
537 perm, user_group, target_user_group),
529 namespace='security.usergroup')
538 namespace='security.usergroup')
530
539
531 changes['added'].append({
540 changes['added'].append({
532 'change_obj': target_user_group.get_api_data(),
541 'change_obj': target_user_group.get_api_data(),
533 'type': 'user_group', 'id': member_id,
542 'type': 'user_group', 'id': member_id,
534 'name': member_name, 'new_perm': perm_name})
543 'name': member_name, 'new_perm': perm_name})
535
544
536 return changes
545 return changes
537
546
538 def revoke_user_group_permission(self, target_user_group, user_group):
547 def revoke_user_group_permission(self, target_user_group, user_group):
539 """
548 """
540 Revoke user group permission for given target_user_group
549 Revoke user group permission for given target_user_group
541
550
542 :param target_user_group:
551 :param target_user_group:
543 :param user_group:
552 :param user_group:
544 """
553 """
545 changes = {
554 changes = {
546 'added': [],
555 'added': [],
547 'updated': [],
556 'updated': [],
548 'deleted': []
557 'deleted': []
549 }
558 }
550
559
551 target_user_group = self._get_user_group(target_user_group)
560 target_user_group = self._get_user_group(target_user_group)
552 user_group = self._get_user_group(user_group)
561 user_group = self._get_user_group(user_group)
553 perm_name = 'usergroup.none'
562 perm_name = 'usergroup.none'
554 member_id = user_group.users_group_id
563 member_id = user_group.users_group_id
555 member_name = user_group.users_group_name
564 member_name = user_group.users_group_name
556
565
557 obj = self.sa.query(UserGroupUserGroupToPerm)\
566 obj = self.sa.query(UserGroupUserGroupToPerm)\
558 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
567 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
559 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
568 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
560 .scalar()
569 .scalar()
561 if obj:
570 if obj:
562 self.sa.delete(obj)
571 self.sa.delete(obj)
563 log.debug(
572 log.debug(
564 'Revoked perm on %s on %s', target_user_group, user_group)
573 'Revoked perm on %s on %s', target_user_group, user_group)
565 action_logger_generic(
574 action_logger_generic(
566 'revoked permission from usergroup: {} on usergroup: {}'.format(
575 'revoked permission from usergroup: {} on usergroup: {}'.format(
567 user_group, target_user_group),
576 user_group, target_user_group),
568 namespace='security.repogroup')
577 namespace='security.repogroup')
569
578
570 changes['deleted'].append({
579 changes['deleted'].append({
571 'change_obj': target_user_group.get_api_data(),
580 'change_obj': target_user_group.get_api_data(),
572 'type': 'user_group', 'id': member_id,
581 'type': 'user_group', 'id': member_id,
573 'name': member_name, 'new_perm': perm_name})
582 'name': member_name, 'new_perm': perm_name})
574
583
575 return changes
584 return changes
576
585
577 def get_perms_summary(self, user_group_id):
586 def get_perms_summary(self, user_group_id):
578 permissions = {
587 permissions = {
579 'repositories': {},
588 'repositories': {},
580 'repositories_groups': {},
589 'repositories_groups': {},
581 }
590 }
582 ugroup_repo_perms = UserGroupRepoToPerm.query()\
591 ugroup_repo_perms = UserGroupRepoToPerm.query()\
583 .options(joinedload(UserGroupRepoToPerm.permission))\
592 .options(joinedload(UserGroupRepoToPerm.permission))\
584 .options(joinedload(UserGroupRepoToPerm.repository))\
593 .options(joinedload(UserGroupRepoToPerm.repository))\
585 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
594 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
586 .all()
595 .all()
587
596
588 for gr in ugroup_repo_perms:
597 for gr in ugroup_repo_perms:
589 permissions['repositories'][gr.repository.repo_name] \
598 permissions['repositories'][gr.repository.repo_name] \
590 = gr.permission.permission_name
599 = gr.permission.permission_name
591
600
592 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
601 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
593 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
602 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
594 .options(joinedload(UserGroupRepoGroupToPerm.group))\
603 .options(joinedload(UserGroupRepoGroupToPerm.group))\
595 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
604 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
596 .all()
605 .all()
597
606
598 for gr in ugroup_group_perms:
607 for gr in ugroup_group_perms:
599 permissions['repositories_groups'][gr.group.group_name] \
608 permissions['repositories_groups'][gr.group.group_name] \
600 = gr.permission.permission_name
609 = gr.permission.permission_name
601 return permissions
610 return permissions
602
611
603 def enforce_groups(self, user, groups, extern_type=None):
612 def enforce_groups(self, user, groups, extern_type=None):
604 user = self._get_user(user)
613 user = self._get_user(user)
605 current_groups = user.group_member
614 current_groups = user.group_member
606
615
607 # find the external created groups, i.e automatically created
616 # find the external created groups, i.e automatically created
608 log.debug('Enforcing user group set `%s` on user %s', groups, user)
617 log.debug('Enforcing user group set `%s` on user %s', groups, user)
609 # calculate from what groups user should be removed
618 # calculate from what groups user should be removed
610 # external_groups that are not in groups
619 # external_groups that are not in groups
611 for gr in [x.users_group for x in current_groups]:
620 for gr in [x.users_group for x in current_groups]:
612 managed = gr.group_data.get('extern_type')
621 managed = gr.group_data.get('extern_type')
613 if managed:
622 if managed:
614 if gr.users_group_name not in groups:
623 if gr.users_group_name not in groups:
615 log.debug('Removing user %s from user group %s. '
624 log.debug('Removing user %s from user group %s. '
616 'Group sync managed by: %s', user, gr, managed)
625 'Group sync managed by: %s', user, gr, managed)
617 self.remove_user_from_group(gr, user)
626 self.remove_user_from_group(gr, user)
618 else:
627 else:
619 log.debug('Skipping removal from group %s since it is '
628 log.debug('Skipping removal from group %s since it is '
620 'not set to be automatically synchronized' % gr)
629 'not set to be automatically synchronized' % gr)
621
630
622 # now we calculate in which groups user should be == groups params
631 # now we calculate in which groups user should be == groups params
623 owner = User.get_first_super_admin().username
632 owner = User.get_first_super_admin().username
624 for gr in set(groups):
633 for gr in set(groups):
625 existing_group = UserGroup.get_by_group_name(gr)
634 existing_group = UserGroup.get_by_group_name(gr)
626 if not existing_group:
635 if not existing_group:
627 desc = 'Automatically created from plugin:%s' % extern_type
636 desc = 'Automatically created from plugin:%s' % extern_type
628 # we use first admin account to set the owner of the group
637 # we use first admin account to set the owner of the group
629 existing_group = UserGroupModel().create(
638 existing_group = UserGroupModel().create(
630 gr, desc, owner, group_data={'extern_type': extern_type})
639 gr, desc, owner, group_data={'extern_type': extern_type})
631
640
632 # we can only add users to groups which have set sync flag via
641 # we can only add users to groups which have set sync flag via
633 # extern_type attribute.
642 # extern_type attribute.
634 # This is either set and created via plugins, or manually
643 # This is either set and created via plugins, or manually
635 managed = existing_group.group_data.get('extern_type')
644 managed = existing_group.group_data.get('extern_type')
636 if managed:
645 if managed:
637 log.debug('Adding user %s to user group %s', user, gr)
646 log.debug('Adding user %s to user group %s', user, gr)
638 UserGroupModel().add_user_to_group(existing_group, user)
647 UserGroupModel().add_user_to_group(existing_group, user)
639 else:
648 else:
640 log.debug('Skipping addition to group %s since it is '
649 log.debug('Skipping addition to group %s since it is '
641 'not set to be automatically synchronized' % gr)
650 'not set to be automatically synchronized' % gr)
642
651
643 def change_groups(self, user, groups):
652 def change_groups(self, user, groups):
644 """
653 """
645 This method changes user group assignment
654 This method changes user group assignment
646 :param user: User
655 :param user: User
647 :param groups: array of UserGroupModel
656 :param groups: array of UserGroupModel
648 """
657 """
649 user = self._get_user(user)
658 user = self._get_user(user)
650 log.debug('Changing user(%s) assignment to groups(%s)', user, groups)
659 log.debug('Changing user(%s) assignment to groups(%s)', user, groups)
651 current_groups = user.group_member
660 current_groups = user.group_member
652 current_groups = [x.users_group for x in current_groups]
661 current_groups = [x.users_group for x in current_groups]
653
662
654 # calculate from what groups user should be removed/add
663 # calculate from what groups user should be removed/add
655 groups = set(groups)
664 groups = set(groups)
656 current_groups = set(current_groups)
665 current_groups = set(current_groups)
657
666
658 groups_to_remove = current_groups - groups
667 groups_to_remove = current_groups - groups
659 groups_to_add = groups - current_groups
668 groups_to_add = groups - current_groups
660
669
661 removed_from_groups = []
670 removed_from_groups = []
662 added_to_groups = []
671 added_to_groups = []
663 for gr in groups_to_remove:
672 for gr in groups_to_remove:
664 log.debug('Removing user %s from user group %s',
673 log.debug('Removing user %s from user group %s',
665 user.username, gr.users_group_name)
674 user.username, gr.users_group_name)
666 removed_from_groups.append(gr.users_group_id)
675 removed_from_groups.append(gr.users_group_id)
667 self.remove_user_from_group(gr.users_group_name, user.username)
676 self.remove_user_from_group(gr.users_group_name, user.username)
668 for gr in groups_to_add:
677 for gr in groups_to_add:
669 log.debug('Adding user %s to user group %s',
678 log.debug('Adding user %s to user group %s',
670 user.username, gr.users_group_name)
679 user.username, gr.users_group_name)
671 added_to_groups.append(gr.users_group_id)
680 added_to_groups.append(gr.users_group_id)
672 UserGroupModel().add_user_to_group(
681 UserGroupModel().add_user_to_group(
673 gr.users_group_name, user.username)
682 gr.users_group_name, user.username)
674
683
675 return added_to_groups, removed_from_groups
684 return added_to_groups, removed_from_groups
676
685
677 def _serialize_user_group(self, user_group):
686 def _serialize_user_group(self, user_group):
678 import rhodecode.lib.helpers as h
687 import rhodecode.lib.helpers as h
679 return {
688 return {
680 'id': user_group.users_group_id,
689 'id': user_group.users_group_id,
681 # TODO: marcink figure out a way to generate the url for the
690 # TODO: marcink figure out a way to generate the url for the
682 # icon
691 # icon
683 'icon_link': '',
692 'icon_link': '',
684 'value_display': 'Group: %s (%d members)' % (
693 'value_display': 'Group: %s (%d members)' % (
685 user_group.users_group_name, len(user_group.members),),
694 user_group.users_group_name, len(user_group.members),),
686 'value': user_group.users_group_name,
695 'value': user_group.users_group_name,
687 'description': user_group.user_group_description,
696 'description': user_group.user_group_description,
688 'owner': user_group.user.username,
697 'owner': user_group.user.username,
689
698
690 'owner_icon': h.gravatar_url(user_group.user.email, 30),
699 'owner_icon': h.gravatar_url(user_group.user.email, 30),
691 'value_display_owner': h.person(user_group.user.email),
700 'value_display_owner': h.person(user_group.user.email),
692
701
693 'value_type': 'user_group',
702 'value_type': 'user_group',
694 'active': user_group.users_group_active,
703 'active': user_group.users_group_active,
695 }
704 }
696
705
697 def get_user_groups(self, name_contains=None, limit=20, only_active=True,
706 def get_user_groups(self, name_contains=None, limit=20, only_active=True,
698 expand_groups=False):
707 expand_groups=False):
699 query = self.sa.query(UserGroup)
708 query = self.sa.query(UserGroup)
700 if only_active:
709 if only_active:
701 query = query.filter(UserGroup.users_group_active == true())
710 query = query.filter(UserGroup.users_group_active == true())
702
711
703 if name_contains:
712 if name_contains:
704 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
713 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
705 query = query.filter(
714 query = query.filter(
706 UserGroup.users_group_name.ilike(ilike_expression))\
715 UserGroup.users_group_name.ilike(ilike_expression))\
707 .order_by(func.length(UserGroup.users_group_name))\
716 .order_by(func.length(UserGroup.users_group_name))\
708 .order_by(UserGroup.users_group_name)
717 .order_by(UserGroup.users_group_name)
709
718
710 query = query.limit(limit)
719 query = query.limit(limit)
711 user_groups = query.all()
720 user_groups = query.all()
712 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
721 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
713 user_groups = UserGroupList(user_groups, perm_set=perm_set)
722 user_groups = UserGroupList(user_groups, perm_set=perm_set)
714
723
715 # store same serialize method to extract data from User
724 # store same serialize method to extract data from User
716 from rhodecode.model.user import UserModel
725 from rhodecode.model.user import UserModel
717 serialize_user = UserModel()._serialize_user
726 serialize_user = UserModel()._serialize_user
718
727
719 _groups = []
728 _groups = []
720 for group in user_groups:
729 for group in user_groups:
721 entry = self._serialize_user_group(group)
730 entry = self._serialize_user_group(group)
722 if expand_groups:
731 if expand_groups:
723 expanded_members = []
732 expanded_members = []
724 for member in group.members:
733 for member in group.members:
725 expanded_members.append(serialize_user(member.user))
734 expanded_members.append(serialize_user(member.user))
726 entry['members'] = expanded_members
735 entry['members'] = expanded_members
727 _groups.append(entry)
736 _groups.append(entry)
728 return _groups
737 return _groups
729
738
730 @staticmethod
739 @staticmethod
731 def get_user_groups_as_dict(user_group):
740 def get_user_groups_as_dict(user_group):
732 import rhodecode.lib.helpers as h
741 import rhodecode.lib.helpers as h
733
742
734 data = {
743 data = {
735 'users_group_id': user_group.users_group_id,
744 'users_group_id': user_group.users_group_id,
736 'group_name': h.link_to_group(user_group.users_group_name),
745 'group_name': h.link_to_group(user_group.users_group_name),
737 'group_description': user_group.user_group_description,
746 'group_description': user_group.user_group_description,
738 'active': user_group.users_group_active,
747 'active': user_group.users_group_active,
739 "owner": user_group.user.username,
748 "owner": user_group.user.username,
740 'owner_icon': h.gravatar_url(user_group.user.email, 30),
749 'owner_icon': h.gravatar_url(user_group.user.email, 30),
741 "owner_data": {
750 "owner_data": {
742 'owner': user_group.user.username,
751 'owner': user_group.user.username,
743 'owner_icon': h.gravatar_url(user_group.user.email, 30)}
752 'owner_icon': h.gravatar_url(user_group.user.email, 30)}
744 }
753 }
745 return data
754 return data
@@ -1,1115 +1,1115 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Set of generic validators
22 Set of generic validators
23 """
23 """
24
24
25
25
26 import os
26 import os
27 import re
27 import re
28 import logging
28 import logging
29 import collections
29 import collections
30
30
31 import formencode
31 import formencode
32 import ipaddress
32 import ipaddress
33 from formencode.validators import (
33 from formencode.validators import (
34 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
34 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
35 NotEmpty, IPAddress, CIDR, String, FancyValidator
35 NotEmpty, IPAddress, CIDR, String, FancyValidator
36 )
36 )
37
37
38 from sqlalchemy.sql.expression import true
38 from sqlalchemy.sql.expression import true
39 from sqlalchemy.util import OrderedSet
39 from sqlalchemy.util import OrderedSet
40
40
41 from rhodecode.authentication import (
41 from rhodecode.authentication import (
42 legacy_plugin_prefix, _import_legacy_plugin)
42 legacy_plugin_prefix, _import_legacy_plugin)
43 from rhodecode.authentication.base import loadplugin
43 from rhodecode.authentication.base import loadplugin
44 from rhodecode.apps._base import ADMIN_PREFIX
44 from rhodecode.apps._base import ADMIN_PREFIX
45 from rhodecode.lib.auth import HasRepoGroupPermissionAny, HasPermissionAny
45 from rhodecode.lib.auth import HasRepoGroupPermissionAny, HasPermissionAny
46 from rhodecode.lib.utils import repo_name_slug, make_db_config
46 from rhodecode.lib.utils import repo_name_slug, make_db_config
47 from rhodecode.lib.utils2 import safe_int, str2bool, aslist, md5, safe_unicode
47 from rhodecode.lib.utils2 import safe_int, str2bool, aslist, md5, safe_unicode
48 from rhodecode.lib.vcs.backends.git.repository import GitRepository
48 from rhodecode.lib.vcs.backends.git.repository import GitRepository
49 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
49 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
50 from rhodecode.lib.vcs.backends.svn.repository import SubversionRepository
50 from rhodecode.lib.vcs.backends.svn.repository import SubversionRepository
51 from rhodecode.model.db import (
51 from rhodecode.model.db import (
52 RepoGroup, Repository, UserGroup, User, ChangesetStatus, Gist)
52 RepoGroup, Repository, UserGroup, User, ChangesetStatus, Gist)
53 from rhodecode.model.settings import VcsSettingsModel
53 from rhodecode.model.settings import VcsSettingsModel
54
54
55 # silence warnings and pylint
55 # silence warnings and pylint
56 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
56 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
57 NotEmpty, IPAddress, CIDR, String, FancyValidator
57 NotEmpty, IPAddress, CIDR, String, FancyValidator
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61
61
62 class _Missing(object):
62 class _Missing(object):
63 pass
63 pass
64
64
65
65
66 Missing = _Missing()
66 Missing = _Missing()
67
67
68
68
69 def M(self, key, state, **kwargs):
69 def M(self, key, state, **kwargs):
70 """
70 """
71 returns string from self.message based on given key,
71 returns string from self.message based on given key,
72 passed kw params are used to substitute %(named)s params inside
72 passed kw params are used to substitute %(named)s params inside
73 translated strings
73 translated strings
74
74
75 :param msg:
75 :param msg:
76 :param state:
76 :param state:
77 """
77 """
78
78
79 #state._ = staticmethod(_)
79 #state._ = staticmethod(_)
80 # inject validator into state object
80 # inject validator into state object
81 return self.message(key, state, **kwargs)
81 return self.message(key, state, **kwargs)
82
82
83
83
84 def UniqueList(localizer, convert=None):
84 def UniqueList(localizer, convert=None):
85 _ = localizer
85 _ = localizer
86
86
87 class _validator(formencode.FancyValidator):
87 class _validator(formencode.FancyValidator):
88 """
88 """
89 Unique List !
89 Unique List !
90 """
90 """
91 messages = {
91 messages = {
92 'empty': _(u'Value cannot be an empty list'),
92 'empty': _(u'Value cannot be an empty list'),
93 'missing_value': _(u'Value cannot be an empty list'),
93 'missing_value': _(u'Value cannot be an empty list'),
94 }
94 }
95
95
96 def _to_python(self, value, state):
96 def _to_python(self, value, state):
97 ret_val = []
97 ret_val = []
98
98
99 def make_unique(value):
99 def make_unique(value):
100 seen = []
100 seen = []
101 return [c for c in value if not (c in seen or seen.append(c))]
101 return [c for c in value if not (c in seen or seen.append(c))]
102
102
103 if isinstance(value, list):
103 if isinstance(value, list):
104 ret_val = make_unique(value)
104 ret_val = make_unique(value)
105 elif isinstance(value, set):
105 elif isinstance(value, set):
106 ret_val = make_unique(list(value))
106 ret_val = make_unique(list(value))
107 elif isinstance(value, tuple):
107 elif isinstance(value, tuple):
108 ret_val = make_unique(list(value))
108 ret_val = make_unique(list(value))
109 elif value is None:
109 elif value is None:
110 ret_val = []
110 ret_val = []
111 else:
111 else:
112 ret_val = [value]
112 ret_val = [value]
113
113
114 if convert:
114 if convert:
115 ret_val = map(convert, ret_val)
115 ret_val = map(convert, ret_val)
116 return ret_val
116 return ret_val
117
117
118 def empty_value(self, value):
118 def empty_value(self, value):
119 return []
119 return []
120 return _validator
120 return _validator
121
121
122
122
123 def UniqueListFromString(localizer):
123 def UniqueListFromString(localizer):
124 _ = localizer
124 _ = localizer
125
125
126 class _validator(UniqueList(localizer)):
126 class _validator(UniqueList(localizer)):
127 def _to_python(self, value, state):
127 def _to_python(self, value, state):
128 if isinstance(value, basestring):
128 if isinstance(value, basestring):
129 value = aslist(value, ',')
129 value = aslist(value, ',')
130 return super(_validator, self)._to_python(value, state)
130 return super(_validator, self)._to_python(value, state)
131 return _validator
131 return _validator
132
132
133
133
134 def ValidSvnPattern(localizer, section, repo_name=None):
134 def ValidSvnPattern(localizer, section, repo_name=None):
135 _ = localizer
135 _ = localizer
136
136
137 class _validator(formencode.validators.FancyValidator):
137 class _validator(formencode.validators.FancyValidator):
138 messages = {
138 messages = {
139 'pattern_exists': _(u'Pattern already exists'),
139 'pattern_exists': _(u'Pattern already exists'),
140 }
140 }
141
141
142 def validate_python(self, value, state):
142 def validate_python(self, value, state):
143 if not value:
143 if not value:
144 return
144 return
145 model = VcsSettingsModel(repo=repo_name)
145 model = VcsSettingsModel(repo=repo_name)
146 ui_settings = model.get_svn_patterns(section=section)
146 ui_settings = model.get_svn_patterns(section=section)
147 for entry in ui_settings:
147 for entry in ui_settings:
148 if value == entry.value:
148 if value == entry.value:
149 msg = M(self, 'pattern_exists', state)
149 msg = M(self, 'pattern_exists', state)
150 raise formencode.Invalid(msg, value, state)
150 raise formencode.Invalid(msg, value, state)
151 return _validator
151 return _validator
152
152
153
153
154 def ValidUsername(localizer, edit=False, old_data=None):
154 def ValidUsername(localizer, edit=False, old_data=None):
155 _ = localizer
155 _ = localizer
156 old_data = old_data or {}
156 old_data = old_data or {}
157
157
158 class _validator(formencode.validators.FancyValidator):
158 class _validator(formencode.validators.FancyValidator):
159 messages = {
159 messages = {
160 'username_exists': _(u'Username "%(username)s" already exists'),
160 'username_exists': _(u'Username "%(username)s" already exists'),
161 'system_invalid_username':
161 'system_invalid_username':
162 _(u'Username "%(username)s" is forbidden'),
162 _(u'Username "%(username)s" is forbidden'),
163 'invalid_username':
163 'invalid_username':
164 _(u'Username may only contain alphanumeric characters '
164 _(u'Username may only contain alphanumeric characters '
165 u'underscores, periods or dashes and must begin with '
165 u'underscores, periods or dashes and must begin with '
166 u'alphanumeric character or underscore')
166 u'alphanumeric character or underscore')
167 }
167 }
168
168
169 def validate_python(self, value, state):
169 def validate_python(self, value, state):
170 if value in ['default', 'new_user']:
170 if value in ['default', 'new_user']:
171 msg = M(self, 'system_invalid_username', state, username=value)
171 msg = M(self, 'system_invalid_username', state, username=value)
172 raise formencode.Invalid(msg, value, state)
172 raise formencode.Invalid(msg, value, state)
173 # check if user is unique
173 # check if user is unique
174 old_un = None
174 old_un = None
175 if edit:
175 if edit:
176 old_un = User.get(old_data.get('user_id')).username
176 old_un = User.get(old_data.get('user_id')).username
177
177
178 if old_un != value or not edit:
178 if old_un != value or not edit:
179 if User.get_by_username(value, case_insensitive=True):
179 if User.get_by_username(value, case_insensitive=True):
180 msg = M(self, 'username_exists', state, username=value)
180 msg = M(self, 'username_exists', state, username=value)
181 raise formencode.Invalid(msg, value, state)
181 raise formencode.Invalid(msg, value, state)
182
182
183 if (re.match(r'^[\w]{1}[\w\-\.]{0,254}$', value)
183 if (re.match(r'^[\w]{1}[\w\-\.]{0,254}$', value)
184 is None):
184 is None):
185 msg = M(self, 'invalid_username', state)
185 msg = M(self, 'invalid_username', state)
186 raise formencode.Invalid(msg, value, state)
186 raise formencode.Invalid(msg, value, state)
187 return _validator
187 return _validator
188
188
189
189
190 def ValidRepoUser(localizer, allow_disabled=False):
190 def ValidRepoUser(localizer, allow_disabled=False):
191 _ = localizer
191 _ = localizer
192
192
193 class _validator(formencode.validators.FancyValidator):
193 class _validator(formencode.validators.FancyValidator):
194 messages = {
194 messages = {
195 'invalid_username': _(u'Username %(username)s is not valid'),
195 'invalid_username': _(u'Username %(username)s is not valid'),
196 'disabled_username': _(u'Username %(username)s is disabled')
196 'disabled_username': _(u'Username %(username)s is disabled')
197 }
197 }
198
198
199 def validate_python(self, value, state):
199 def validate_python(self, value, state):
200 try:
200 try:
201 user = User.query().filter(User.username == value).one()
201 user = User.query().filter(User.username == value).one()
202 except Exception:
202 except Exception:
203 msg = M(self, 'invalid_username', state, username=value)
203 msg = M(self, 'invalid_username', state, username=value)
204 raise formencode.Invalid(
204 raise formencode.Invalid(
205 msg, value, state, error_dict={'username': msg}
205 msg, value, state, error_dict={'username': msg}
206 )
206 )
207 if user and (not allow_disabled and not user.active):
207 if user and (not allow_disabled and not user.active):
208 msg = M(self, 'disabled_username', state, username=value)
208 msg = M(self, 'disabled_username', state, username=value)
209 raise formencode.Invalid(
209 raise formencode.Invalid(
210 msg, value, state, error_dict={'username': msg}
210 msg, value, state, error_dict={'username': msg}
211 )
211 )
212 return _validator
212 return _validator
213
213
214
214
215 def ValidUserGroup(localizer, edit=False, old_data=None):
215 def ValidUserGroup(localizer, edit=False, old_data=None):
216 _ = localizer
216 _ = localizer
217 old_data = old_data or {}
217 old_data = old_data or {}
218
218
219 class _validator(formencode.validators.FancyValidator):
219 class _validator(formencode.validators.FancyValidator):
220 messages = {
220 messages = {
221 'invalid_group': _(u'Invalid user group name'),
221 'invalid_group': _(u'Invalid user group name'),
222 'group_exist': _(u'User group `%(usergroup)s` already exists'),
222 'group_exist': _(u'User group `%(usergroup)s` already exists'),
223 'invalid_usergroup_name':
223 'invalid_usergroup_name':
224 _(u'user group name may only contain alphanumeric '
224 _(u'user group name may only contain alphanumeric '
225 u'characters underscores, periods or dashes and must begin '
225 u'characters underscores, periods or dashes and must begin '
226 u'with alphanumeric character')
226 u'with alphanumeric character')
227 }
227 }
228
228
229 def validate_python(self, value, state):
229 def validate_python(self, value, state):
230 if value in ['default']:
230 if value in ['default']:
231 msg = M(self, 'invalid_group', state)
231 msg = M(self, 'invalid_group', state)
232 raise formencode.Invalid(
232 raise formencode.Invalid(
233 msg, value, state, error_dict={'users_group_name': msg}
233 msg, value, state, error_dict={'users_group_name': msg}
234 )
234 )
235 # check if group is unique
235 # check if group is unique
236 old_ugname = None
236 old_ugname = None
237 if edit:
237 if edit:
238 old_id = old_data.get('users_group_id')
238 old_id = old_data.get('users_group_id')
239 old_ugname = UserGroup.get(old_id).users_group_name
239 old_ugname = UserGroup.get(old_id).users_group_name
240
240
241 if old_ugname != value or not edit:
241 if old_ugname != value or not edit:
242 is_existing_group = UserGroup.get_by_group_name(
242 is_existing_group = UserGroup.get_by_group_name(
243 value, case_insensitive=True)
243 value, case_insensitive=True)
244 if is_existing_group:
244 if is_existing_group:
245 msg = M(self, 'group_exist', state, usergroup=value)
245 msg = M(self, 'group_exist', state, usergroup=value)
246 raise formencode.Invalid(
246 raise formencode.Invalid(
247 msg, value, state, error_dict={'users_group_name': msg}
247 msg, value, state, error_dict={'users_group_name': msg}
248 )
248 )
249
249
250 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
250 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
251 msg = M(self, 'invalid_usergroup_name', state)
251 msg = M(self, 'invalid_usergroup_name', state)
252 raise formencode.Invalid(
252 raise formencode.Invalid(
253 msg, value, state, error_dict={'users_group_name': msg}
253 msg, value, state, error_dict={'users_group_name': msg}
254 )
254 )
255 return _validator
255 return _validator
256
256
257
257
258 def ValidRepoGroup(localizer, edit=False, old_data=None, can_create_in_root=False):
258 def ValidRepoGroup(localizer, edit=False, old_data=None, can_create_in_root=False):
259 _ = localizer
259 _ = localizer
260 old_data = old_data or {}
260 old_data = old_data or {}
261
261
262 class _validator(formencode.validators.FancyValidator):
262 class _validator(formencode.validators.FancyValidator):
263 messages = {
263 messages = {
264 'group_parent_id': _(u'Cannot assign this group as parent'),
264 'group_parent_id': _(u'Cannot assign this group as parent'),
265 'group_exists': _(u'Group "%(group_name)s" already exists'),
265 'group_exists': _(u'Group "%(group_name)s" already exists'),
266 'repo_exists': _(u'Repository with name "%(group_name)s" '
266 'repo_exists': _(u'Repository with name "%(group_name)s" '
267 u'already exists'),
267 u'already exists'),
268 'permission_denied': _(u"no permission to store repository group"
268 'permission_denied': _(u"no permission to store repository group"
269 u"in this location"),
269 u"in this location"),
270 'permission_denied_root': _(
270 'permission_denied_root': _(
271 u"no permission to store repository group "
271 u"no permission to store repository group "
272 u"in root location")
272 u"in root location")
273 }
273 }
274
274
275 def _to_python(self, value, state):
275 def _to_python(self, value, state):
276 group_name = repo_name_slug(value.get('group_name', ''))
276 group_name = repo_name_slug(value.get('group_name', ''))
277 group_parent_id = safe_int(value.get('group_parent_id'))
277 group_parent_id = safe_int(value.get('group_parent_id'))
278 gr = RepoGroup.get(group_parent_id)
278 gr = RepoGroup.get(group_parent_id)
279 if gr:
279 if gr:
280 parent_group_path = gr.full_path
280 parent_group_path = gr.full_path
281 # value needs to be aware of group name in order to check
281 # value needs to be aware of group name in order to check
282 # db key This is an actual just the name to store in the
282 # db key This is an actual just the name to store in the
283 # database
283 # database
284 group_name_full = (
284 group_name_full = (
285 parent_group_path + RepoGroup.url_sep() + group_name)
285 parent_group_path + RepoGroup.url_sep() + group_name)
286 else:
286 else:
287 group_name_full = group_name
287 group_name_full = group_name
288
288
289 value['group_name'] = group_name
289 value['group_name'] = group_name
290 value['group_name_full'] = group_name_full
290 value['group_name_full'] = group_name_full
291 value['group_parent_id'] = group_parent_id
291 value['group_parent_id'] = group_parent_id
292 return value
292 return value
293
293
294 def validate_python(self, value, state):
294 def validate_python(self, value, state):
295
295
296 old_group_name = None
296 old_group_name = None
297 group_name = value.get('group_name')
297 group_name = value.get('group_name')
298 group_name_full = value.get('group_name_full')
298 group_name_full = value.get('group_name_full')
299 group_parent_id = safe_int(value.get('group_parent_id'))
299 group_parent_id = safe_int(value.get('group_parent_id'))
300 if group_parent_id == -1:
300 if group_parent_id == -1:
301 group_parent_id = None
301 group_parent_id = None
302
302
303 group_obj = RepoGroup.get(old_data.get('group_id'))
303 group_obj = RepoGroup.get(old_data.get('group_id'))
304 parent_group_changed = False
304 parent_group_changed = False
305 if edit:
305 if edit:
306 old_group_name = group_obj.group_name
306 old_group_name = group_obj.group_name
307 old_group_parent_id = group_obj.group_parent_id
307 old_group_parent_id = group_obj.group_parent_id
308
308
309 if group_parent_id != old_group_parent_id:
309 if group_parent_id != old_group_parent_id:
310 parent_group_changed = True
310 parent_group_changed = True
311
311
312 # TODO: mikhail: the following if statement is not reached
312 # TODO: mikhail: the following if statement is not reached
313 # since group_parent_id's OneOf validation fails before.
313 # since group_parent_id's OneOf validation fails before.
314 # Can be removed.
314 # Can be removed.
315
315
316 # check against setting a parent of self
316 # check against setting a parent of self
317 parent_of_self = (
317 parent_of_self = (
318 old_data['group_id'] == group_parent_id
318 old_data['group_id'] == group_parent_id
319 if group_parent_id else False
319 if group_parent_id else False
320 )
320 )
321 if parent_of_self:
321 if parent_of_self:
322 msg = M(self, 'group_parent_id', state)
322 msg = M(self, 'group_parent_id', state)
323 raise formencode.Invalid(
323 raise formencode.Invalid(
324 msg, value, state, error_dict={'group_parent_id': msg}
324 msg, value, state, error_dict={'group_parent_id': msg}
325 )
325 )
326
326
327 # group we're moving current group inside
327 # group we're moving current group inside
328 child_group = None
328 child_group = None
329 if group_parent_id:
329 if group_parent_id:
330 child_group = RepoGroup.query().filter(
330 child_group = RepoGroup.query().filter(
331 RepoGroup.group_id == group_parent_id).scalar()
331 RepoGroup.group_id == group_parent_id).scalar()
332
332
333 # do a special check that we cannot move a group to one of
333 # do a special check that we cannot move a group to one of
334 # it's children
334 # it's children
335 if edit and child_group:
335 if edit and child_group:
336 parents = [x.group_id for x in child_group.parents]
336 parents = [x.group_id for x in child_group.parents]
337 move_to_children = old_data['group_id'] in parents
337 move_to_children = old_data['group_id'] in parents
338 if move_to_children:
338 if move_to_children:
339 msg = M(self, 'group_parent_id', state)
339 msg = M(self, 'group_parent_id', state)
340 raise formencode.Invalid(
340 raise formencode.Invalid(
341 msg, value, state, error_dict={'group_parent_id': msg})
341 msg, value, state, error_dict={'group_parent_id': msg})
342
342
343 # Check if we have permission to store in the parent.
343 # Check if we have permission to store in the parent.
344 # Only check if the parent group changed.
344 # Only check if the parent group changed.
345 if parent_group_changed:
345 if parent_group_changed:
346 if child_group is None:
346 if child_group is None:
347 if not can_create_in_root:
347 if not can_create_in_root:
348 msg = M(self, 'permission_denied_root', state)
348 msg = M(self, 'permission_denied_root', state)
349 raise formencode.Invalid(
349 raise formencode.Invalid(
350 msg, value, state,
350 msg, value, state,
351 error_dict={'group_parent_id': msg})
351 error_dict={'group_parent_id': msg})
352 else:
352 else:
353 valid = HasRepoGroupPermissionAny('group.admin')
353 valid = HasRepoGroupPermissionAny('group.admin')
354 forbidden = not valid(
354 forbidden = not valid(
355 child_group.group_name, 'can create group validator')
355 child_group.group_name, 'can create group validator')
356 if forbidden:
356 if forbidden:
357 msg = M(self, 'permission_denied', state)
357 msg = M(self, 'permission_denied', state)
358 raise formencode.Invalid(
358 raise formencode.Invalid(
359 msg, value, state,
359 msg, value, state,
360 error_dict={'group_parent_id': msg})
360 error_dict={'group_parent_id': msg})
361
361
362 # if we change the name or it's new group, check for existing names
362 # if we change the name or it's new group, check for existing names
363 # or repositories with the same name
363 # or repositories with the same name
364 if old_group_name != group_name_full or not edit:
364 if old_group_name != group_name_full or not edit:
365 # check group
365 # check group
366 gr = RepoGroup.get_by_group_name(group_name_full)
366 gr = RepoGroup.get_by_group_name(group_name_full)
367 if gr:
367 if gr:
368 msg = M(self, 'group_exists', state, group_name=group_name)
368 msg = M(self, 'group_exists', state, group_name=group_name)
369 raise formencode.Invalid(
369 raise formencode.Invalid(
370 msg, value, state, error_dict={'group_name': msg})
370 msg, value, state, error_dict={'group_name': msg})
371
371
372 # check for same repo
372 # check for same repo
373 repo = Repository.get_by_repo_name(group_name_full)
373 repo = Repository.get_by_repo_name(group_name_full)
374 if repo:
374 if repo:
375 msg = M(self, 'repo_exists', state, group_name=group_name)
375 msg = M(self, 'repo_exists', state, group_name=group_name)
376 raise formencode.Invalid(
376 raise formencode.Invalid(
377 msg, value, state, error_dict={'group_name': msg})
377 msg, value, state, error_dict={'group_name': msg})
378 return _validator
378 return _validator
379
379
380
380
381 def ValidPassword(localizer):
381 def ValidPassword(localizer):
382 _ = localizer
382 _ = localizer
383
383
384 class _validator(formencode.validators.FancyValidator):
384 class _validator(formencode.validators.FancyValidator):
385 messages = {
385 messages = {
386 'invalid_password':
386 'invalid_password':
387 _(u'Invalid characters (non-ascii) in password')
387 _(u'Invalid characters (non-ascii) in password')
388 }
388 }
389
389
390 def validate_python(self, value, state):
390 def validate_python(self, value, state):
391 try:
391 try:
392 (value or '').decode('ascii')
392 (value or '').decode('ascii')
393 except UnicodeError:
393 except UnicodeError:
394 msg = M(self, 'invalid_password', state)
394 msg = M(self, 'invalid_password', state)
395 raise formencode.Invalid(msg, value, state,)
395 raise formencode.Invalid(msg, value, state,)
396 return _validator
396 return _validator
397
397
398
398
399 def ValidPasswordsMatch(
399 def ValidPasswordsMatch(
400 localizer, passwd='new_password',
400 localizer, passwd='new_password',
401 passwd_confirmation='password_confirmation'):
401 passwd_confirmation='password_confirmation'):
402 _ = localizer
402 _ = localizer
403
403
404 class _validator(formencode.validators.FancyValidator):
404 class _validator(formencode.validators.FancyValidator):
405 messages = {
405 messages = {
406 'password_mismatch': _(u'Passwords do not match'),
406 'password_mismatch': _(u'Passwords do not match'),
407 }
407 }
408
408
409 def validate_python(self, value, state):
409 def validate_python(self, value, state):
410
410
411 pass_val = value.get('password') or value.get(passwd)
411 pass_val = value.get('password') or value.get(passwd)
412 if pass_val != value[passwd_confirmation]:
412 if pass_val != value[passwd_confirmation]:
413 msg = M(self, 'password_mismatch', state)
413 msg = M(self, 'password_mismatch', state)
414 raise formencode.Invalid(
414 raise formencode.Invalid(
415 msg, value, state,
415 msg, value, state,
416 error_dict={passwd: msg, passwd_confirmation: msg}
416 error_dict={passwd: msg, passwd_confirmation: msg}
417 )
417 )
418 return _validator
418 return _validator
419
419
420
420
421 def ValidAuth(localizer):
421 def ValidAuth(localizer):
422 _ = localizer
422 _ = localizer
423
423
424 class _validator(formencode.validators.FancyValidator):
424 class _validator(formencode.validators.FancyValidator):
425 messages = {
425 messages = {
426 'invalid_password': _(u'invalid password'),
426 'invalid_password': _(u'invalid password'),
427 'invalid_username': _(u'invalid user name'),
427 'invalid_username': _(u'invalid user name'),
428 'disabled_account': _(u'Your account is disabled')
428 'disabled_account': _(u'Your account is disabled')
429 }
429 }
430
430
431 def validate_python(self, value, state):
431 def validate_python(self, value, state):
432 from rhodecode.authentication.base import authenticate, HTTP_TYPE
432 from rhodecode.authentication.base import authenticate, HTTP_TYPE
433
433
434 password = value['password']
434 password = value['password']
435 username = value['username']
435 username = value['username']
436
436
437 if not authenticate(username, password, '', HTTP_TYPE,
437 if not authenticate(username, password, '', HTTP_TYPE,
438 skip_missing=True):
438 skip_missing=True):
439 user = User.get_by_username(username)
439 user = User.get_by_username(username)
440 if user and not user.active:
440 if user and not user.active:
441 log.warning('user %s is disabled', username)
441 log.warning('user %s is disabled', username)
442 msg = M(self, 'disabled_account', state)
442 msg = M(self, 'disabled_account', state)
443 raise formencode.Invalid(
443 raise formencode.Invalid(
444 msg, value, state, error_dict={'username': msg}
444 msg, value, state, error_dict={'username': msg}
445 )
445 )
446 else:
446 else:
447 log.warning('user `%s` failed to authenticate', username)
447 log.warning('user `%s` failed to authenticate', username)
448 msg = M(self, 'invalid_username', state)
448 msg = M(self, 'invalid_username', state)
449 msg2 = M(self, 'invalid_password', state)
449 msg2 = M(self, 'invalid_password', state)
450 raise formencode.Invalid(
450 raise formencode.Invalid(
451 msg, value, state,
451 msg, value, state,
452 error_dict={'username': msg, 'password': msg2}
452 error_dict={'username': msg, 'password': msg2}
453 )
453 )
454 return _validator
454 return _validator
455
455
456
456
457 def ValidRepoName(localizer, edit=False, old_data=None):
457 def ValidRepoName(localizer, edit=False, old_data=None):
458 old_data = old_data or {}
458 old_data = old_data or {}
459 _ = localizer
459 _ = localizer
460
460
461 class _validator(formencode.validators.FancyValidator):
461 class _validator(formencode.validators.FancyValidator):
462 messages = {
462 messages = {
463 'invalid_repo_name':
463 'invalid_repo_name':
464 _(u'Repository name %(repo)s is disallowed'),
464 _(u'Repository name %(repo)s is disallowed'),
465 # top level
465 # top level
466 'repository_exists': _(u'Repository with name %(repo)s '
466 'repository_exists': _(u'Repository with name %(repo)s '
467 u'already exists'),
467 u'already exists'),
468 'group_exists': _(u'Repository group with name "%(repo)s" '
468 'group_exists': _(u'Repository group with name "%(repo)s" '
469 u'already exists'),
469 u'already exists'),
470 # inside a group
470 # inside a group
471 'repository_in_group_exists': _(u'Repository with name %(repo)s '
471 'repository_in_group_exists': _(u'Repository with name %(repo)s '
472 u'exists in group "%(group)s"'),
472 u'exists in group "%(group)s"'),
473 'group_in_group_exists': _(
473 'group_in_group_exists': _(
474 u'Repository group with name "%(repo)s" '
474 u'Repository group with name "%(repo)s" '
475 u'exists in group "%(group)s"'),
475 u'exists in group "%(group)s"'),
476 }
476 }
477
477
478 def _to_python(self, value, state):
478 def _to_python(self, value, state):
479 repo_name = repo_name_slug(value.get('repo_name', ''))
479 repo_name = repo_name_slug(value.get('repo_name', ''))
480 repo_group = value.get('repo_group')
480 repo_group = value.get('repo_group')
481 if repo_group:
481 if repo_group:
482 gr = RepoGroup.get(repo_group)
482 gr = RepoGroup.get(repo_group)
483 group_path = gr.full_path
483 group_path = gr.full_path
484 group_name = gr.group_name
484 group_name = gr.group_name
485 # value needs to be aware of group name in order to check
485 # value needs to be aware of group name in order to check
486 # db key This is an actual just the name to store in the
486 # db key This is an actual just the name to store in the
487 # database
487 # database
488 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
488 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
489 else:
489 else:
490 group_name = group_path = ''
490 group_name = group_path = ''
491 repo_name_full = repo_name
491 repo_name_full = repo_name
492
492
493 value['repo_name'] = repo_name
493 value['repo_name'] = repo_name
494 value['repo_name_full'] = repo_name_full
494 value['repo_name_full'] = repo_name_full
495 value['group_path'] = group_path
495 value['group_path'] = group_path
496 value['group_name'] = group_name
496 value['group_name'] = group_name
497 return value
497 return value
498
498
499 def validate_python(self, value, state):
499 def validate_python(self, value, state):
500
500
501 repo_name = value.get('repo_name')
501 repo_name = value.get('repo_name')
502 repo_name_full = value.get('repo_name_full')
502 repo_name_full = value.get('repo_name_full')
503 group_path = value.get('group_path')
503 group_path = value.get('group_path')
504 group_name = value.get('group_name')
504 group_name = value.get('group_name')
505
505
506 if repo_name in [ADMIN_PREFIX, '']:
506 if repo_name in [ADMIN_PREFIX, '']:
507 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
507 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
508 raise formencode.Invalid(
508 raise formencode.Invalid(
509 msg, value, state, error_dict={'repo_name': msg})
509 msg, value, state, error_dict={'repo_name': msg})
510
510
511 rename = old_data.get('repo_name') != repo_name_full
511 rename = old_data.get('repo_name') != repo_name_full
512 create = not edit
512 create = not edit
513 if rename or create:
513 if rename or create:
514
514
515 if group_path:
515 if group_path:
516 if Repository.get_by_repo_name(repo_name_full):
516 if Repository.get_by_repo_name(repo_name_full):
517 msg = M(self, 'repository_in_group_exists', state,
517 msg = M(self, 'repository_in_group_exists', state,
518 repo=repo_name, group=group_name)
518 repo=repo_name, group=group_name)
519 raise formencode.Invalid(
519 raise formencode.Invalid(
520 msg, value, state, error_dict={'repo_name': msg})
520 msg, value, state, error_dict={'repo_name': msg})
521 if RepoGroup.get_by_group_name(repo_name_full):
521 if RepoGroup.get_by_group_name(repo_name_full):
522 msg = M(self, 'group_in_group_exists', state,
522 msg = M(self, 'group_in_group_exists', state,
523 repo=repo_name, group=group_name)
523 repo=repo_name, group=group_name)
524 raise formencode.Invalid(
524 raise formencode.Invalid(
525 msg, value, state, error_dict={'repo_name': msg})
525 msg, value, state, error_dict={'repo_name': msg})
526 else:
526 else:
527 if RepoGroup.get_by_group_name(repo_name_full):
527 if RepoGroup.get_by_group_name(repo_name_full):
528 msg = M(self, 'group_exists', state, repo=repo_name)
528 msg = M(self, 'group_exists', state, repo=repo_name)
529 raise formencode.Invalid(
529 raise formencode.Invalid(
530 msg, value, state, error_dict={'repo_name': msg})
530 msg, value, state, error_dict={'repo_name': msg})
531
531
532 if Repository.get_by_repo_name(repo_name_full):
532 if Repository.get_by_repo_name(repo_name_full):
533 msg = M(
533 msg = M(
534 self, 'repository_exists', state, repo=repo_name)
534 self, 'repository_exists', state, repo=repo_name)
535 raise formencode.Invalid(
535 raise formencode.Invalid(
536 msg, value, state, error_dict={'repo_name': msg})
536 msg, value, state, error_dict={'repo_name': msg})
537 return value
537 return value
538 return _validator
538 return _validator
539
539
540
540
541 def ValidForkName(localizer, *args, **kwargs):
541 def ValidForkName(localizer, *args, **kwargs):
542 _ = localizer
542 _ = localizer
543
543
544 return ValidRepoName(localizer, *args, **kwargs)
544 return ValidRepoName(localizer, *args, **kwargs)
545
545
546
546
547 def SlugifyName(localizer):
547 def SlugifyName(localizer):
548 _ = localizer
548 _ = localizer
549
549
550 class _validator(formencode.validators.FancyValidator):
550 class _validator(formencode.validators.FancyValidator):
551
551
552 def _to_python(self, value, state):
552 def _to_python(self, value, state):
553 return repo_name_slug(value)
553 return repo_name_slug(value)
554
554
555 def validate_python(self, value, state):
555 def validate_python(self, value, state):
556 pass
556 pass
557 return _validator
557 return _validator
558
558
559
559
560 def CannotHaveGitSuffix(localizer):
560 def CannotHaveGitSuffix(localizer):
561 _ = localizer
561 _ = localizer
562
562
563 class _validator(formencode.validators.FancyValidator):
563 class _validator(formencode.validators.FancyValidator):
564 messages = {
564 messages = {
565 'has_git_suffix':
565 'has_git_suffix':
566 _(u'Repository name cannot end with .git'),
566 _(u'Repository name cannot end with .git'),
567 }
567 }
568
568
569 def _to_python(self, value, state):
569 def _to_python(self, value, state):
570 return value
570 return value
571
571
572 def validate_python(self, value, state):
572 def validate_python(self, value, state):
573 if value and value.endswith('.git'):
573 if value and value.endswith('.git'):
574 msg = M(
574 msg = M(
575 self, 'has_git_suffix', state)
575 self, 'has_git_suffix', state)
576 raise formencode.Invalid(
576 raise formencode.Invalid(
577 msg, value, state, error_dict={'repo_name': msg})
577 msg, value, state, error_dict={'repo_name': msg})
578 return _validator
578 return _validator
579
579
580
580
581 def ValidCloneUri(localizer):
581 def ValidCloneUri(localizer):
582 _ = localizer
582 _ = localizer
583
583
584 class InvalidCloneUrl(Exception):
584 class InvalidCloneUrl(Exception):
585 allowed_prefixes = ()
585 allowed_prefixes = ()
586
586
587 def url_handler(repo_type, url):
587 def url_handler(repo_type, url):
588 config = make_db_config(clear_session=False)
588 config = make_db_config(clear_session=False)
589 if repo_type == 'hg':
589 if repo_type == 'hg':
590 allowed_prefixes = ('http', 'svn+http', 'git+http')
590 allowed_prefixes = ('http', 'svn+http', 'git+http')
591
591
592 if 'http' in url[:4]:
592 if 'http' in url[:4]:
593 # initially check if it's at least the proper URL
593 # initially check if it's at least the proper URL
594 # or does it pass basic auth
594 # or does it pass basic auth
595 MercurialRepository.check_url(url, config)
595 MercurialRepository.check_url(url, config)
596 elif 'svn+http' in url[:8]: # svn->hg import
596 elif 'svn+http' in url[:8]: # svn->hg import
597 SubversionRepository.check_url(url, config)
597 SubversionRepository.check_url(url, config)
598 elif 'git+http' in url[:8]: # git->hg import
598 elif 'git+http' in url[:8]: # git->hg import
599 raise NotImplementedError()
599 raise NotImplementedError()
600 else:
600 else:
601 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
601 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
602 'Allowed url must start with one of %s'
602 'Allowed url must start with one of %s'
603 % (url, ','.join(allowed_prefixes)))
603 % (url, ','.join(allowed_prefixes)))
604 exc.allowed_prefixes = allowed_prefixes
604 exc.allowed_prefixes = allowed_prefixes
605 raise exc
605 raise exc
606
606
607 elif repo_type == 'git':
607 elif repo_type == 'git':
608 allowed_prefixes = ('http', 'svn+http', 'hg+http')
608 allowed_prefixes = ('http', 'svn+http', 'hg+http')
609 if 'http' in url[:4]:
609 if 'http' in url[:4]:
610 # initially check if it's at least the proper URL
610 # initially check if it's at least the proper URL
611 # or does it pass basic auth
611 # or does it pass basic auth
612 GitRepository.check_url(url, config)
612 GitRepository.check_url(url, config)
613 elif 'svn+http' in url[:8]: # svn->git import
613 elif 'svn+http' in url[:8]: # svn->git import
614 raise NotImplementedError()
614 raise NotImplementedError()
615 elif 'hg+http' in url[:8]: # hg->git import
615 elif 'hg+http' in url[:8]: # hg->git import
616 raise NotImplementedError()
616 raise NotImplementedError()
617 else:
617 else:
618 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
618 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
619 'Allowed url must start with one of %s'
619 'Allowed url must start with one of %s'
620 % (url, ','.join(allowed_prefixes)))
620 % (url, ','.join(allowed_prefixes)))
621 exc.allowed_prefixes = allowed_prefixes
621 exc.allowed_prefixes = allowed_prefixes
622 raise exc
622 raise exc
623
623
624 class _validator(formencode.validators.FancyValidator):
624 class _validator(formencode.validators.FancyValidator):
625 messages = {
625 messages = {
626 'clone_uri': _(u'invalid clone url for %(rtype)s repository'),
626 'clone_uri': _(u'invalid clone url for %(rtype)s repository'),
627 'invalid_clone_uri': _(
627 'invalid_clone_uri': _(
628 u'Invalid clone url, provide a valid clone '
628 u'Invalid clone url, provide a valid clone '
629 u'url starting with one of %(allowed_prefixes)s')
629 u'url starting with one of %(allowed_prefixes)s')
630 }
630 }
631
631
632 def validate_python(self, value, state):
632 def validate_python(self, value, state):
633 repo_type = value.get('repo_type')
633 repo_type = value.get('repo_type')
634 url = value.get('clone_uri')
634 url = value.get('clone_uri')
635
635
636 if url:
636 if url:
637 try:
637 try:
638 url_handler(repo_type, url)
638 url_handler(repo_type, url)
639 except InvalidCloneUrl as e:
639 except InvalidCloneUrl as e:
640 log.warning(e)
640 log.warning(e)
641 msg = M(self, 'invalid_clone_uri', state, rtype=repo_type,
641 msg = M(self, 'invalid_clone_uri', state, rtype=repo_type,
642 allowed_prefixes=','.join(e.allowed_prefixes))
642 allowed_prefixes=','.join(e.allowed_prefixes))
643 raise formencode.Invalid(msg, value, state,
643 raise formencode.Invalid(msg, value, state,
644 error_dict={'clone_uri': msg})
644 error_dict={'clone_uri': msg})
645 except Exception:
645 except Exception:
646 log.exception('Url validation failed')
646 log.exception('Url validation failed')
647 msg = M(self, 'clone_uri', state, rtype=repo_type)
647 msg = M(self, 'clone_uri', state, rtype=repo_type)
648 raise formencode.Invalid(msg, value, state,
648 raise formencode.Invalid(msg, value, state,
649 error_dict={'clone_uri': msg})
649 error_dict={'clone_uri': msg})
650 return _validator
650 return _validator
651
651
652
652
653 def ValidForkType(localizer, old_data=None):
653 def ValidForkType(localizer, old_data=None):
654 _ = localizer
654 _ = localizer
655 old_data = old_data or {}
655 old_data = old_data or {}
656
656
657 class _validator(formencode.validators.FancyValidator):
657 class _validator(formencode.validators.FancyValidator):
658 messages = {
658 messages = {
659 'invalid_fork_type': _(u'Fork have to be the same type as parent')
659 'invalid_fork_type': _(u'Fork have to be the same type as parent')
660 }
660 }
661
661
662 def validate_python(self, value, state):
662 def validate_python(self, value, state):
663 if old_data['repo_type'] != value:
663 if old_data['repo_type'] != value:
664 msg = M(self, 'invalid_fork_type', state)
664 msg = M(self, 'invalid_fork_type', state)
665 raise formencode.Invalid(
665 raise formencode.Invalid(
666 msg, value, state, error_dict={'repo_type': msg}
666 msg, value, state, error_dict={'repo_type': msg}
667 )
667 )
668 return _validator
668 return _validator
669
669
670
670
671 def CanWriteGroup(localizer, old_data=None):
671 def CanWriteGroup(localizer, old_data=None):
672 _ = localizer
672 _ = localizer
673
673
674 class _validator(formencode.validators.FancyValidator):
674 class _validator(formencode.validators.FancyValidator):
675 messages = {
675 messages = {
676 'permission_denied': _(
676 'permission_denied': _(
677 u"You do not have the permission "
677 u"You do not have the permission "
678 u"to create repositories in this group."),
678 u"to create repositories in this group."),
679 'permission_denied_root': _(
679 'permission_denied_root': _(
680 u"You do not have the permission to store repositories in "
680 u"You do not have the permission to store repositories in "
681 u"the root location.")
681 u"the root location.")
682 }
682 }
683
683
684 def _to_python(self, value, state):
684 def _to_python(self, value, state):
685 # root location
685 # root location
686 if value in [-1, "-1"]:
686 if value in [-1, "-1"]:
687 return None
687 return None
688 return value
688 return value
689
689
690 def validate_python(self, value, state):
690 def validate_python(self, value, state):
691 gr = RepoGroup.get(value)
691 gr = RepoGroup.get(value)
692 gr_name = gr.group_name if gr else None # None means ROOT location
692 gr_name = gr.group_name if gr else None # None means ROOT location
693 # create repositories with write permission on group is set to true
693 # create repositories with write permission on group is set to true
694 create_on_write = HasPermissionAny(
694 create_on_write = HasPermissionAny(
695 'hg.create.write_on_repogroup.true')()
695 'hg.create.write_on_repogroup.true')()
696 group_admin = HasRepoGroupPermissionAny('group.admin')(
696 group_admin = HasRepoGroupPermissionAny('group.admin')(
697 gr_name, 'can write into group validator')
697 gr_name, 'can write into group validator')
698 group_write = HasRepoGroupPermissionAny('group.write')(
698 group_write = HasRepoGroupPermissionAny('group.write')(
699 gr_name, 'can write into group validator')
699 gr_name, 'can write into group validator')
700 forbidden = not (group_admin or (group_write and create_on_write))
700 forbidden = not (group_admin or (group_write and create_on_write))
701 can_create_repos = HasPermissionAny(
701 can_create_repos = HasPermissionAny(
702 'hg.admin', 'hg.create.repository')
702 'hg.admin', 'hg.create.repository')
703 gid = (old_data['repo_group'].get('group_id')
703 gid = (old_data['repo_group'].get('group_id')
704 if (old_data and 'repo_group' in old_data) else None)
704 if (old_data and 'repo_group' in old_data) else None)
705 value_changed = gid != safe_int(value)
705 value_changed = gid != safe_int(value)
706 new = not old_data
706 new = not old_data
707 # do check if we changed the value, there's a case that someone got
707 # do check if we changed the value, there's a case that someone got
708 # revoked write permissions to a repository, he still created, we
708 # revoked write permissions to a repository, he still created, we
709 # don't need to check permission if he didn't change the value of
709 # don't need to check permission if he didn't change the value of
710 # groups in form box
710 # groups in form box
711 if value_changed or new:
711 if value_changed or new:
712 # parent group need to be existing
712 # parent group need to be existing
713 if gr and forbidden:
713 if gr and forbidden:
714 msg = M(self, 'permission_denied', state)
714 msg = M(self, 'permission_denied', state)
715 raise formencode.Invalid(
715 raise formencode.Invalid(
716 msg, value, state, error_dict={'repo_type': msg}
716 msg, value, state, error_dict={'repo_type': msg}
717 )
717 )
718 # check if we can write to root location !
718 # check if we can write to root location !
719 elif gr is None and not can_create_repos():
719 elif gr is None and not can_create_repos():
720 msg = M(self, 'permission_denied_root', state)
720 msg = M(self, 'permission_denied_root', state)
721 raise formencode.Invalid(
721 raise formencode.Invalid(
722 msg, value, state, error_dict={'repo_type': msg}
722 msg, value, state, error_dict={'repo_type': msg}
723 )
723 )
724 return _validator
724 return _validator
725
725
726
726
727 def ValidPerms(localizer, type_='repo'):
727 def ValidPerms(localizer, type_='repo'):
728 _ = localizer
728 _ = localizer
729 if type_ == 'repo_group':
729 if type_ == 'repo_group':
730 EMPTY_PERM = 'group.none'
730 EMPTY_PERM = 'group.none'
731 elif type_ == 'repo':
731 elif type_ == 'repo':
732 EMPTY_PERM = 'repository.none'
732 EMPTY_PERM = 'repository.none'
733 elif type_ == 'user_group':
733 elif type_ == 'user_group':
734 EMPTY_PERM = 'usergroup.none'
734 EMPTY_PERM = 'usergroup.none'
735
735
736 class _validator(formencode.validators.FancyValidator):
736 class _validator(formencode.validators.FancyValidator):
737 messages = {
737 messages = {
738 'perm_new_member_name':
738 'perm_new_member_name':
739 _(u'This username or user group name is not valid')
739 _(u'This username or user group name is not valid')
740 }
740 }
741
741
742 def _to_python(self, value, state):
742 def _to_python(self, value, state):
743 perm_updates = OrderedSet()
743 perm_updates = OrderedSet()
744 perm_additions = OrderedSet()
744 perm_additions = OrderedSet()
745 perm_deletions = OrderedSet()
745 perm_deletions = OrderedSet()
746 # build a list of permission to update/delete and new permission
746 # build a list of permission to update/delete and new permission
747
747
748 # Read the perm_new_member/perm_del_member attributes and group
748 # Read the perm_new_member/perm_del_member attributes and group
749 # them by they IDs
749 # them by they IDs
750 new_perms_group = collections.defaultdict(dict)
750 new_perms_group = collections.defaultdict(dict)
751 del_perms_group = collections.defaultdict(dict)
751 del_perms_group = collections.defaultdict(dict)
752 for k, v in value.copy().iteritems():
752 for k, v in value.copy().iteritems():
753 if k.startswith('perm_del_member'):
753 if k.startswith('perm_del_member'):
754 # delete from org storage so we don't process that later
754 # delete from org storage so we don't process that later
755 del value[k]
755 del value[k]
756 # part is `id`, `type`
756 # part is `id`, `type`
757 _type, part = k.split('perm_del_member_')
757 _type, part = k.split('perm_del_member_')
758 args = part.split('_')
758 args = part.split('_')
759 if len(args) == 2:
759 if len(args) == 2:
760 _key, pos = args
760 _key, pos = args
761 del_perms_group[pos][_key] = v
761 del_perms_group[pos][_key] = v
762 if k.startswith('perm_new_member'):
762 if k.startswith('perm_new_member'):
763 # delete from org storage so we don't process that later
763 # delete from org storage so we don't process that later
764 del value[k]
764 del value[k]
765 # part is `id`, `type`, `perm`
765 # part is `id`, `type`, `perm`
766 _type, part = k.split('perm_new_member_')
766 _type, part = k.split('perm_new_member_')
767 args = part.split('_')
767 args = part.split('_')
768 if len(args) == 2:
768 if len(args) == 2:
769 _key, pos = args
769 _key, pos = args
770 new_perms_group[pos][_key] = v
770 new_perms_group[pos][_key] = v
771
771
772 # store the deletes
772 # store the deletes
773 for k in sorted(del_perms_group.keys()):
773 for k in sorted(del_perms_group.keys()):
774 perm_dict = del_perms_group[k]
774 perm_dict = del_perms_group[k]
775 del_member = perm_dict.get('id')
775 del_member = perm_dict.get('id')
776 del_type = perm_dict.get('type')
776 del_type = perm_dict.get('type')
777 if del_member and del_type:
777 if del_member and del_type:
778 perm_deletions.add(
778 perm_deletions.add(
779 (del_member, None, del_type))
779 (del_member, None, del_type))
780
780
781 # store additions in order of how they were added in web form
781 # store additions in order of how they were added in web form
782 for k in sorted(new_perms_group.keys()):
782 for k in sorted(new_perms_group.keys()):
783 perm_dict = new_perms_group[k]
783 perm_dict = new_perms_group[k]
784 new_member = perm_dict.get('id')
784 new_member = perm_dict.get('id')
785 new_type = perm_dict.get('type')
785 new_type = perm_dict.get('type')
786 new_perm = perm_dict.get('perm')
786 new_perm = perm_dict.get('perm')
787 if new_member and new_perm and new_type:
787 if new_member and new_perm and new_type:
788 perm_additions.add(
788 perm_additions.add(
789 (new_member, new_perm, new_type))
789 (new_member, new_perm, new_type))
790
790
791 # get updates of permissions
791 # get updates of permissions
792 # (read the existing radio button states)
792 # (read the existing radio button states)
793 default_user_id = User.get_default_user().user_id
793 default_user_id = User.get_default_user().user_id
794
794
795 for k, update_value in value.iteritems():
795 for k, update_value in value.iteritems():
796 if k.startswith('u_perm_') or k.startswith('g_perm_'):
796 if k.startswith('u_perm_') or k.startswith('g_perm_'):
797 obj_type = k[0]
797 obj_type = k[0]
798 obj_id = k[7:]
798 obj_id = k[7:]
799 update_type = {'u': 'user',
799 update_type = {'u': 'user',
800 'g': 'users_group'}[obj_type]
800 'g': 'user_group'}[obj_type]
801
801
802 if obj_type == 'u' and safe_int(obj_id) == default_user_id:
802 if obj_type == 'u' and safe_int(obj_id) == default_user_id:
803 if str2bool(value.get('repo_private')):
803 if str2bool(value.get('repo_private')):
804 # prevent from updating default user permissions
804 # prevent from updating default user permissions
805 # when this repository is marked as private
805 # when this repository is marked as private
806 update_value = EMPTY_PERM
806 update_value = EMPTY_PERM
807
807
808 perm_updates.add(
808 perm_updates.add(
809 (obj_id, update_value, update_type))
809 (obj_id, update_value, update_type))
810
810
811 value['perm_additions'] = [] # propagated later
811 value['perm_additions'] = [] # propagated later
812 value['perm_updates'] = list(perm_updates)
812 value['perm_updates'] = list(perm_updates)
813 value['perm_deletions'] = list(perm_deletions)
813 value['perm_deletions'] = list(perm_deletions)
814
814
815 updates_map = dict(
815 updates_map = dict(
816 (x[0], (x[1], x[2])) for x in value['perm_updates'])
816 (x[0], (x[1], x[2])) for x in value['perm_updates'])
817 # make sure Additions don't override updates.
817 # make sure Additions don't override updates.
818 for member_id, perm, member_type in list(perm_additions):
818 for member_id, perm, member_type in list(perm_additions):
819 if member_id in updates_map:
819 if member_id in updates_map:
820 perm = updates_map[member_id][0]
820 perm = updates_map[member_id][0]
821 value['perm_additions'].append((member_id, perm, member_type))
821 value['perm_additions'].append((member_id, perm, member_type))
822
822
823 # on new entries validate users they exist and they are active !
823 # on new entries validate users they exist and they are active !
824 # this leaves feedback to the form
824 # this leaves feedback to the form
825 try:
825 try:
826 if member_type == 'user':
826 if member_type == 'user':
827 User.query()\
827 User.query()\
828 .filter(User.active == true())\
828 .filter(User.active == true())\
829 .filter(User.user_id == member_id).one()
829 .filter(User.user_id == member_id).one()
830 if member_type == 'users_group':
830 if member_type == 'user_group':
831 UserGroup.query()\
831 UserGroup.query()\
832 .filter(UserGroup.users_group_active == true())\
832 .filter(UserGroup.users_group_active == true())\
833 .filter(UserGroup.users_group_id == member_id)\
833 .filter(UserGroup.users_group_id == member_id)\
834 .one()
834 .one()
835
835
836 except Exception:
836 except Exception:
837 log.exception('Updated permission failed: org_exc:')
837 log.exception('Updated permission failed: org_exc:')
838 msg = M(self, 'perm_new_member_type', state)
838 msg = M(self, 'perm_new_member_type', state)
839 raise formencode.Invalid(
839 raise formencode.Invalid(
840 msg, value, state, error_dict={
840 msg, value, state, error_dict={
841 'perm_new_member_name': msg}
841 'perm_new_member_name': msg}
842 )
842 )
843 return value
843 return value
844 return _validator
844 return _validator
845
845
846
846
847 def ValidPath(localizer):
847 def ValidPath(localizer):
848 _ = localizer
848 _ = localizer
849
849
850 class _validator(formencode.validators.FancyValidator):
850 class _validator(formencode.validators.FancyValidator):
851 messages = {
851 messages = {
852 'invalid_path': _(u'This is not a valid path')
852 'invalid_path': _(u'This is not a valid path')
853 }
853 }
854
854
855 def validate_python(self, value, state):
855 def validate_python(self, value, state):
856 if not os.path.isdir(value):
856 if not os.path.isdir(value):
857 msg = M(self, 'invalid_path', state)
857 msg = M(self, 'invalid_path', state)
858 raise formencode.Invalid(
858 raise formencode.Invalid(
859 msg, value, state, error_dict={'paths_root_path': msg}
859 msg, value, state, error_dict={'paths_root_path': msg}
860 )
860 )
861 return _validator
861 return _validator
862
862
863
863
864 def UniqSystemEmail(localizer, old_data=None):
864 def UniqSystemEmail(localizer, old_data=None):
865 _ = localizer
865 _ = localizer
866 old_data = old_data or {}
866 old_data = old_data or {}
867
867
868 class _validator(formencode.validators.FancyValidator):
868 class _validator(formencode.validators.FancyValidator):
869 messages = {
869 messages = {
870 'email_taken': _(u'This e-mail address is already taken')
870 'email_taken': _(u'This e-mail address is already taken')
871 }
871 }
872
872
873 def _to_python(self, value, state):
873 def _to_python(self, value, state):
874 return value.lower()
874 return value.lower()
875
875
876 def validate_python(self, value, state):
876 def validate_python(self, value, state):
877 if (old_data.get('email') or '').lower() != value:
877 if (old_data.get('email') or '').lower() != value:
878 user = User.get_by_email(value, case_insensitive=True)
878 user = User.get_by_email(value, case_insensitive=True)
879 if user:
879 if user:
880 msg = M(self, 'email_taken', state)
880 msg = M(self, 'email_taken', state)
881 raise formencode.Invalid(
881 raise formencode.Invalid(
882 msg, value, state, error_dict={'email': msg}
882 msg, value, state, error_dict={'email': msg}
883 )
883 )
884 return _validator
884 return _validator
885
885
886
886
887 def ValidSystemEmail(localizer):
887 def ValidSystemEmail(localizer):
888 _ = localizer
888 _ = localizer
889
889
890 class _validator(formencode.validators.FancyValidator):
890 class _validator(formencode.validators.FancyValidator):
891 messages = {
891 messages = {
892 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
892 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
893 }
893 }
894
894
895 def _to_python(self, value, state):
895 def _to_python(self, value, state):
896 return value.lower()
896 return value.lower()
897
897
898 def validate_python(self, value, state):
898 def validate_python(self, value, state):
899 user = User.get_by_email(value, case_insensitive=True)
899 user = User.get_by_email(value, case_insensitive=True)
900 if user is None:
900 if user is None:
901 msg = M(self, 'non_existing_email', state, email=value)
901 msg = M(self, 'non_existing_email', state, email=value)
902 raise formencode.Invalid(
902 raise formencode.Invalid(
903 msg, value, state, error_dict={'email': msg}
903 msg, value, state, error_dict={'email': msg}
904 )
904 )
905 return _validator
905 return _validator
906
906
907
907
908 def NotReviewedRevisions(localizer, repo_id):
908 def NotReviewedRevisions(localizer, repo_id):
909 _ = localizer
909 _ = localizer
910 class _validator(formencode.validators.FancyValidator):
910 class _validator(formencode.validators.FancyValidator):
911 messages = {
911 messages = {
912 'rev_already_reviewed':
912 'rev_already_reviewed':
913 _(u'Revisions %(revs)s are already part of pull request '
913 _(u'Revisions %(revs)s are already part of pull request '
914 u'or have set status'),
914 u'or have set status'),
915 }
915 }
916
916
917 def validate_python(self, value, state):
917 def validate_python(self, value, state):
918 # check revisions if they are not reviewed, or a part of another
918 # check revisions if they are not reviewed, or a part of another
919 # pull request
919 # pull request
920 statuses = ChangesetStatus.query()\
920 statuses = ChangesetStatus.query()\
921 .filter(ChangesetStatus.revision.in_(value))\
921 .filter(ChangesetStatus.revision.in_(value))\
922 .filter(ChangesetStatus.repo_id == repo_id)\
922 .filter(ChangesetStatus.repo_id == repo_id)\
923 .all()
923 .all()
924
924
925 errors = []
925 errors = []
926 for status in statuses:
926 for status in statuses:
927 if status.pull_request_id:
927 if status.pull_request_id:
928 errors.append(['pull_req', status.revision[:12]])
928 errors.append(['pull_req', status.revision[:12]])
929 elif status.status:
929 elif status.status:
930 errors.append(['status', status.revision[:12]])
930 errors.append(['status', status.revision[:12]])
931
931
932 if errors:
932 if errors:
933 revs = ','.join([x[1] for x in errors])
933 revs = ','.join([x[1] for x in errors])
934 msg = M(self, 'rev_already_reviewed', state, revs=revs)
934 msg = M(self, 'rev_already_reviewed', state, revs=revs)
935 raise formencode.Invalid(
935 raise formencode.Invalid(
936 msg, value, state, error_dict={'revisions': revs})
936 msg, value, state, error_dict={'revisions': revs})
937
937
938 return _validator
938 return _validator
939
939
940
940
941 def ValidIp(localizer):
941 def ValidIp(localizer):
942 _ = localizer
942 _ = localizer
943
943
944 class _validator(CIDR):
944 class _validator(CIDR):
945 messages = {
945 messages = {
946 'badFormat': _(u'Please enter a valid IPv4 or IpV6 address'),
946 'badFormat': _(u'Please enter a valid IPv4 or IpV6 address'),
947 'illegalBits': _(
947 'illegalBits': _(
948 u'The network size (bits) must be within the range '
948 u'The network size (bits) must be within the range '
949 u'of 0-32 (not %(bits)r)'),
949 u'of 0-32 (not %(bits)r)'),
950 }
950 }
951
951
952 # we ovveride the default to_python() call
952 # we ovveride the default to_python() call
953 def to_python(self, value, state):
953 def to_python(self, value, state):
954 v = super(_validator, self).to_python(value, state)
954 v = super(_validator, self).to_python(value, state)
955 v = safe_unicode(v.strip())
955 v = safe_unicode(v.strip())
956 net = ipaddress.ip_network(address=v, strict=False)
956 net = ipaddress.ip_network(address=v, strict=False)
957 return str(net)
957 return str(net)
958
958
959 def validate_python(self, value, state):
959 def validate_python(self, value, state):
960 try:
960 try:
961 addr = safe_unicode(value.strip())
961 addr = safe_unicode(value.strip())
962 # this raises an ValueError if address is not IpV4 or IpV6
962 # this raises an ValueError if address is not IpV4 or IpV6
963 ipaddress.ip_network(addr, strict=False)
963 ipaddress.ip_network(addr, strict=False)
964 except ValueError:
964 except ValueError:
965 raise formencode.Invalid(self.message('badFormat', state),
965 raise formencode.Invalid(self.message('badFormat', state),
966 value, state)
966 value, state)
967 return _validator
967 return _validator
968
968
969
969
970 def FieldKey(localizer):
970 def FieldKey(localizer):
971 _ = localizer
971 _ = localizer
972
972
973 class _validator(formencode.validators.FancyValidator):
973 class _validator(formencode.validators.FancyValidator):
974 messages = {
974 messages = {
975 'badFormat': _(
975 'badFormat': _(
976 u'Key name can only consist of letters, '
976 u'Key name can only consist of letters, '
977 u'underscore, dash or numbers'),
977 u'underscore, dash or numbers'),
978 }
978 }
979
979
980 def validate_python(self, value, state):
980 def validate_python(self, value, state):
981 if not re.match('[a-zA-Z0-9_-]+$', value):
981 if not re.match('[a-zA-Z0-9_-]+$', value):
982 raise formencode.Invalid(self.message('badFormat', state),
982 raise formencode.Invalid(self.message('badFormat', state),
983 value, state)
983 value, state)
984 return _validator
984 return _validator
985
985
986
986
987 def ValidAuthPlugins(localizer):
987 def ValidAuthPlugins(localizer):
988 _ = localizer
988 _ = localizer
989
989
990 class _validator(formencode.validators.FancyValidator):
990 class _validator(formencode.validators.FancyValidator):
991 messages = {
991 messages = {
992 'import_duplicate': _(
992 'import_duplicate': _(
993 u'Plugins %(loaded)s and %(next_to_load)s '
993 u'Plugins %(loaded)s and %(next_to_load)s '
994 u'both export the same name'),
994 u'both export the same name'),
995 'missing_includeme': _(
995 'missing_includeme': _(
996 u'The plugin "%(plugin_id)s" is missing an includeme '
996 u'The plugin "%(plugin_id)s" is missing an includeme '
997 u'function.'),
997 u'function.'),
998 'import_error': _(
998 'import_error': _(
999 u'Can not load plugin "%(plugin_id)s"'),
999 u'Can not load plugin "%(plugin_id)s"'),
1000 'no_plugin': _(
1000 'no_plugin': _(
1001 u'No plugin available with ID "%(plugin_id)s"'),
1001 u'No plugin available with ID "%(plugin_id)s"'),
1002 }
1002 }
1003
1003
1004 def _to_python(self, value, state):
1004 def _to_python(self, value, state):
1005 # filter empty values
1005 # filter empty values
1006 return filter(lambda s: s not in [None, ''], value)
1006 return filter(lambda s: s not in [None, ''], value)
1007
1007
1008 def _validate_legacy_plugin_id(self, plugin_id, value, state):
1008 def _validate_legacy_plugin_id(self, plugin_id, value, state):
1009 """
1009 """
1010 Validates that the plugin import works. It also checks that the
1010 Validates that the plugin import works. It also checks that the
1011 plugin has an includeme attribute.
1011 plugin has an includeme attribute.
1012 """
1012 """
1013 try:
1013 try:
1014 plugin = _import_legacy_plugin(plugin_id)
1014 plugin = _import_legacy_plugin(plugin_id)
1015 except Exception as e:
1015 except Exception as e:
1016 log.exception(
1016 log.exception(
1017 'Exception during import of auth legacy plugin "{}"'
1017 'Exception during import of auth legacy plugin "{}"'
1018 .format(plugin_id))
1018 .format(plugin_id))
1019 msg = M(self, 'import_error', state, plugin_id=plugin_id)
1019 msg = M(self, 'import_error', state, plugin_id=plugin_id)
1020 raise formencode.Invalid(msg, value, state)
1020 raise formencode.Invalid(msg, value, state)
1021
1021
1022 if not hasattr(plugin, 'includeme'):
1022 if not hasattr(plugin, 'includeme'):
1023 msg = M(self, 'missing_includeme', state, plugin_id=plugin_id)
1023 msg = M(self, 'missing_includeme', state, plugin_id=plugin_id)
1024 raise formencode.Invalid(msg, value, state)
1024 raise formencode.Invalid(msg, value, state)
1025
1025
1026 return plugin
1026 return plugin
1027
1027
1028 def _validate_plugin_id(self, plugin_id, value, state):
1028 def _validate_plugin_id(self, plugin_id, value, state):
1029 """
1029 """
1030 Plugins are already imported during app start up. Therefore this
1030 Plugins are already imported during app start up. Therefore this
1031 validation only retrieves the plugin from the plugin registry and
1031 validation only retrieves the plugin from the plugin registry and
1032 if it returns something not None everything is OK.
1032 if it returns something not None everything is OK.
1033 """
1033 """
1034 plugin = loadplugin(plugin_id)
1034 plugin = loadplugin(plugin_id)
1035
1035
1036 if plugin is None:
1036 if plugin is None:
1037 msg = M(self, 'no_plugin', state, plugin_id=plugin_id)
1037 msg = M(self, 'no_plugin', state, plugin_id=plugin_id)
1038 raise formencode.Invalid(msg, value, state)
1038 raise formencode.Invalid(msg, value, state)
1039
1039
1040 return plugin
1040 return plugin
1041
1041
1042 def validate_python(self, value, state):
1042 def validate_python(self, value, state):
1043 unique_names = {}
1043 unique_names = {}
1044 for plugin_id in value:
1044 for plugin_id in value:
1045
1045
1046 # Validate legacy or normal plugin.
1046 # Validate legacy or normal plugin.
1047 if plugin_id.startswith(legacy_plugin_prefix):
1047 if plugin_id.startswith(legacy_plugin_prefix):
1048 plugin = self._validate_legacy_plugin_id(
1048 plugin = self._validate_legacy_plugin_id(
1049 plugin_id, value, state)
1049 plugin_id, value, state)
1050 else:
1050 else:
1051 plugin = self._validate_plugin_id(plugin_id, value, state)
1051 plugin = self._validate_plugin_id(plugin_id, value, state)
1052
1052
1053 # Only allow unique plugin names.
1053 # Only allow unique plugin names.
1054 if plugin.name in unique_names:
1054 if plugin.name in unique_names:
1055 msg = M(self, 'import_duplicate', state,
1055 msg = M(self, 'import_duplicate', state,
1056 loaded=unique_names[plugin.name],
1056 loaded=unique_names[plugin.name],
1057 next_to_load=plugin)
1057 next_to_load=plugin)
1058 raise formencode.Invalid(msg, value, state)
1058 raise formencode.Invalid(msg, value, state)
1059 unique_names[plugin.name] = plugin
1059 unique_names[plugin.name] = plugin
1060 return _validator
1060 return _validator
1061
1061
1062
1062
1063 def ValidPattern(localizer):
1063 def ValidPattern(localizer):
1064 _ = localizer
1064 _ = localizer
1065
1065
1066 class _validator(formencode.validators.FancyValidator):
1066 class _validator(formencode.validators.FancyValidator):
1067 messages = {
1067 messages = {
1068 'bad_format': _(u'Url must start with http or /'),
1068 'bad_format': _(u'Url must start with http or /'),
1069 }
1069 }
1070
1070
1071 def _to_python(self, value, state):
1071 def _to_python(self, value, state):
1072 patterns = []
1072 patterns = []
1073
1073
1074 prefix = 'new_pattern'
1074 prefix = 'new_pattern'
1075 for name, v in value.iteritems():
1075 for name, v in value.iteritems():
1076 pattern_name = '_'.join((prefix, 'pattern'))
1076 pattern_name = '_'.join((prefix, 'pattern'))
1077 if name.startswith(pattern_name):
1077 if name.startswith(pattern_name):
1078 new_item_id = name[len(pattern_name)+1:]
1078 new_item_id = name[len(pattern_name)+1:]
1079
1079
1080 def _field(name):
1080 def _field(name):
1081 return '%s_%s_%s' % (prefix, name, new_item_id)
1081 return '%s_%s_%s' % (prefix, name, new_item_id)
1082
1082
1083 values = {
1083 values = {
1084 'issuetracker_pat': value.get(_field('pattern')),
1084 'issuetracker_pat': value.get(_field('pattern')),
1085 'issuetracker_url': value.get(_field('url')),
1085 'issuetracker_url': value.get(_field('url')),
1086 'issuetracker_pref': value.get(_field('prefix')),
1086 'issuetracker_pref': value.get(_field('prefix')),
1087 'issuetracker_desc': value.get(_field('description'))
1087 'issuetracker_desc': value.get(_field('description'))
1088 }
1088 }
1089 new_uid = md5(values['issuetracker_pat'])
1089 new_uid = md5(values['issuetracker_pat'])
1090
1090
1091 has_required_fields = (
1091 has_required_fields = (
1092 values['issuetracker_pat']
1092 values['issuetracker_pat']
1093 and values['issuetracker_url'])
1093 and values['issuetracker_url'])
1094
1094
1095 if has_required_fields:
1095 if has_required_fields:
1096 # validate url that it starts with http or /
1096 # validate url that it starts with http or /
1097 # otherwise it can lead to JS injections
1097 # otherwise it can lead to JS injections
1098 # e.g specifig javascript:<malicios code>
1098 # e.g specifig javascript:<malicios code>
1099 if not values['issuetracker_url'].startswith(('http', '/')):
1099 if not values['issuetracker_url'].startswith(('http', '/')):
1100 raise formencode.Invalid(
1100 raise formencode.Invalid(
1101 self.message('bad_format', state),
1101 self.message('bad_format', state),
1102 value, state)
1102 value, state)
1103
1103
1104 settings = [
1104 settings = [
1105 ('_'.join((key, new_uid)), values[key], 'unicode')
1105 ('_'.join((key, new_uid)), values[key], 'unicode')
1106 for key in values]
1106 for key in values]
1107 patterns.append(settings)
1107 patterns.append(settings)
1108
1108
1109 value['patterns'] = patterns
1109 value['patterns'] = patterns
1110 delete_patterns = value.get('uid') or []
1110 delete_patterns = value.get('uid') or []
1111 if not isinstance(delete_patterns, (list, tuple)):
1111 if not isinstance(delete_patterns, (list, tuple)):
1112 delete_patterns = [delete_patterns]
1112 delete_patterns = [delete_patterns]
1113 value['delete_patterns'] = delete_patterns
1113 value['delete_patterns'] = delete_patterns
1114 return value
1114 return value
1115 return _validator
1115 return _validator
@@ -1,236 +1,236 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import functools
21 import functools
22
22
23 import pytest
23 import pytest
24
24
25 from rhodecode.model.db import RepoGroup
25 from rhodecode.model.db import RepoGroup
26 from rhodecode.model.meta import Session
26 from rhodecode.model.meta import Session
27 from rhodecode.model.repo_group import RepoGroupModel
27 from rhodecode.model.repo_group import RepoGroupModel
28 from rhodecode.model.user_group import UserGroupModel
28 from rhodecode.model.user_group import UserGroupModel
29 from rhodecode.tests.models.common import (
29 from rhodecode.tests.models.common import (
30 _create_project_tree, check_tree_perms, _get_perms, _check_expected_count,
30 _create_project_tree, check_tree_perms, _get_perms, _check_expected_count,
31 expected_count, _destroy_project_tree)
31 expected_count, _destroy_project_tree)
32 from rhodecode.tests.fixture import Fixture
32 from rhodecode.tests.fixture import Fixture
33
33
34
34
35 fixture = Fixture()
35 fixture = Fixture()
36
36
37 test_u2_id = None
37 test_u2_id = None
38 test_u2_gr_id = None
38 test_u2_gr_id = None
39 _get_repo_perms = None
39 _get_repo_perms = None
40 _get_group_perms = None
40 _get_group_perms = None
41
41
42
42
43 def permissions_setup_func_orig(
43 def permissions_setup_func_orig(
44 group_name='g0', perm='group.read', recursive='all'):
44 group_name='g0', perm='group.read', recursive='all'):
45 """
45 """
46 Resets all permissions to perm attribute
46 Resets all permissions to perm attribute
47 """
47 """
48 repo_group = RepoGroup.get_by_group_name(group_name=group_name)
48 repo_group = RepoGroup.get_by_group_name(group_name=group_name)
49 if not repo_group:
49 if not repo_group:
50 raise Exception('Cannot get group %s' % group_name)
50 raise Exception('Cannot get group %s' % group_name)
51 perm_updates = [[test_u2_gr_id, perm, 'users_group']]
51 perm_updates = [[test_u2_gr_id, perm, 'user_group']]
52 RepoGroupModel().update_permissions(repo_group,
52 RepoGroupModel().update_permissions(repo_group,
53 perm_updates=perm_updates,
53 perm_updates=perm_updates,
54 recursive=recursive, check_perms=False)
54 recursive=recursive, check_perms=False)
55 Session().commit()
55 Session().commit()
56
56
57
57
58 def permissions_setup_func(*args, **kwargs):
58 def permissions_setup_func(*args, **kwargs):
59 # TODO: workaround, remove once the generative tests have been adapted
59 # TODO: workaround, remove once the generative tests have been adapted
60 # to py.test's style
60 # to py.test's style
61 permissions_setup_func_orig()
61 permissions_setup_func_orig()
62 permissions_setup_func_orig(*args, **kwargs)
62 permissions_setup_func_orig(*args, **kwargs)
63
63
64
64
65 @pytest.fixture(scope='module', autouse=True)
65 @pytest.fixture(scope='module', autouse=True)
66 def prepare(request):
66 def prepare(request):
67 global test_u2_id, test_u2_gr_id, _get_repo_perms, _get_group_perms
67 global test_u2_id, test_u2_gr_id, _get_repo_perms, _get_group_perms
68 test_u2 = _create_project_tree()
68 test_u2 = _create_project_tree()
69 Session().commit()
69 Session().commit()
70 test_u2_id = test_u2.user_id
70 test_u2_id = test_u2.user_id
71
71
72 gr1 = fixture.create_user_group('perms_group_1')
72 gr1 = fixture.create_user_group('perms_group_1')
73 Session().commit()
73 Session().commit()
74 test_u2_gr_id = gr1.users_group_id
74 test_u2_gr_id = gr1.users_group_id
75 UserGroupModel().add_user_to_group(gr1, user=test_u2_id)
75 UserGroupModel().add_user_to_group(gr1, user=test_u2_id)
76 Session().commit()
76 Session().commit()
77
77
78 _get_repo_perms = functools.partial(_get_perms, key='repositories',
78 _get_repo_perms = functools.partial(_get_perms, key='repositories',
79 test_u1_id=test_u2_id)
79 test_u1_id=test_u2_id)
80 _get_group_perms = functools.partial(_get_perms, key='repositories_groups',
80 _get_group_perms = functools.partial(_get_perms, key='repositories_groups',
81 test_u1_id=test_u2_id)
81 test_u1_id=test_u2_id)
82
82
83 @request.addfinalizer
83 @request.addfinalizer
84 def cleanup():
84 def cleanup():
85 fixture.destroy_user_group('perms_group_1')
85 fixture.destroy_user_group('perms_group_1')
86 _destroy_project_tree(test_u2_id)
86 _destroy_project_tree(test_u2_id)
87
87
88
88
89 def test_user_permissions_on_group_without_recursive_mode():
89 def test_user_permissions_on_group_without_recursive_mode():
90 # set permission to g0 non-recursive mode
90 # set permission to g0 non-recursive mode
91 recursive = 'none'
91 recursive = 'none'
92 group = 'g0'
92 group = 'g0'
93 permissions_setup_func(group, 'group.write', recursive=recursive)
93 permissions_setup_func(group, 'group.write', recursive=recursive)
94
94
95 items = [x for x in _get_repo_perms(group, recursive)]
95 items = [x for x in _get_repo_perms(group, recursive)]
96 expected = 0
96 expected = 0
97 assert len(items) == expected, ' %s != %s' % (len(items), expected)
97 assert len(items) == expected, ' %s != %s' % (len(items), expected)
98 for name, perm in items:
98 for name, perm in items:
99 check_tree_perms(name, perm, group, 'repository.read')
99 check_tree_perms(name, perm, group, 'repository.read')
100
100
101 items = [x for x in _get_group_perms(group, recursive)]
101 items = [x for x in _get_group_perms(group, recursive)]
102 expected = 1
102 expected = 1
103 assert len(items) == expected, ' %s != %s' % (len(items), expected)
103 assert len(items) == expected, ' %s != %s' % (len(items), expected)
104 for name, perm in items:
104 for name, perm in items:
105 check_tree_perms(name, perm, group, 'group.write')
105 check_tree_perms(name, perm, group, 'group.write')
106
106
107
107
108 def test_user_permissions_on_group_without_recursive_mode_subgroup():
108 def test_user_permissions_on_group_without_recursive_mode_subgroup():
109 # set permission to g0 non-recursive mode
109 # set permission to g0 non-recursive mode
110 recursive = 'none'
110 recursive = 'none'
111 group = 'g0/g0_1'
111 group = 'g0/g0_1'
112 permissions_setup_func(group, 'group.write', recursive=recursive)
112 permissions_setup_func(group, 'group.write', recursive=recursive)
113
113
114 items = [x for x in _get_repo_perms(group, recursive)]
114 items = [x for x in _get_repo_perms(group, recursive)]
115 expected = 0
115 expected = 0
116 assert len(items) == expected, ' %s != %s' % (len(items), expected)
116 assert len(items) == expected, ' %s != %s' % (len(items), expected)
117 for name, perm in items:
117 for name, perm in items:
118 check_tree_perms(name, perm, group, 'repository.read')
118 check_tree_perms(name, perm, group, 'repository.read')
119
119
120 items = [x for x in _get_group_perms(group, recursive)]
120 items = [x for x in _get_group_perms(group, recursive)]
121 expected = 1
121 expected = 1
122 assert len(items) == expected, ' %s != %s' % (len(items), expected)
122 assert len(items) == expected, ' %s != %s' % (len(items), expected)
123 for name, perm in items:
123 for name, perm in items:
124 check_tree_perms(name, perm, group, 'group.write')
124 check_tree_perms(name, perm, group, 'group.write')
125
125
126
126
127 def test_user_permissions_on_group_with_recursive_mode():
127 def test_user_permissions_on_group_with_recursive_mode():
128
128
129 # set permission to g0 recursive mode, all children including
129 # set permission to g0 recursive mode, all children including
130 # other repos and groups should have this permission now set !
130 # other repos and groups should have this permission now set !
131 recursive = 'all'
131 recursive = 'all'
132 group = 'g0'
132 group = 'g0'
133 permissions_setup_func(group, 'group.write', recursive=recursive)
133 permissions_setup_func(group, 'group.write', recursive=recursive)
134
134
135 repo_items = [x for x in _get_repo_perms(group, recursive)]
135 repo_items = [x for x in _get_repo_perms(group, recursive)]
136 items = [x for x in _get_group_perms(group, recursive)]
136 items = [x for x in _get_group_perms(group, recursive)]
137 _check_expected_count(items, repo_items, expected_count(group, True))
137 _check_expected_count(items, repo_items, expected_count(group, True))
138
138
139 for name, perm in repo_items:
139 for name, perm in repo_items:
140 check_tree_perms(name, perm, group, 'repository.write')
140 check_tree_perms(name, perm, group, 'repository.write')
141
141
142 for name, perm in items:
142 for name, perm in items:
143 check_tree_perms(name, perm, group, 'group.write')
143 check_tree_perms(name, perm, group, 'group.write')
144
144
145
145
146 def test_user_permissions_on_group_with_recursive_mode_inner_group():
146 def test_user_permissions_on_group_with_recursive_mode_inner_group():
147 # set permission to g0_3 group to none
147 # set permission to g0_3 group to none
148 recursive = 'all'
148 recursive = 'all'
149 group = 'g0/g0_3'
149 group = 'g0/g0_3'
150 permissions_setup_func(group, 'group.none', recursive=recursive)
150 permissions_setup_func(group, 'group.none', recursive=recursive)
151
151
152 repo_items = [x for x in _get_repo_perms(group, recursive)]
152 repo_items = [x for x in _get_repo_perms(group, recursive)]
153 items = [x for x in _get_group_perms(group, recursive)]
153 items = [x for x in _get_group_perms(group, recursive)]
154 _check_expected_count(items, repo_items, expected_count(group, True))
154 _check_expected_count(items, repo_items, expected_count(group, True))
155
155
156 for name, perm in repo_items:
156 for name, perm in repo_items:
157 check_tree_perms(name, perm, group, 'repository.none')
157 check_tree_perms(name, perm, group, 'repository.none')
158
158
159 for name, perm in items:
159 for name, perm in items:
160 check_tree_perms(name, perm, group, 'group.none')
160 check_tree_perms(name, perm, group, 'group.none')
161
161
162
162
163 def test_user_permissions_on_group_with_recursive_mode_deepest():
163 def test_user_permissions_on_group_with_recursive_mode_deepest():
164 # set permission to g0_3 group to none
164 # set permission to g0_3 group to none
165 recursive = 'all'
165 recursive = 'all'
166 group = 'g0/g0_1/g0_1_1'
166 group = 'g0/g0_1/g0_1_1'
167 permissions_setup_func(group, 'group.write', recursive=recursive)
167 permissions_setup_func(group, 'group.write', recursive=recursive)
168
168
169 repo_items = [x for x in _get_repo_perms(group, recursive)]
169 repo_items = [x for x in _get_repo_perms(group, recursive)]
170 items = [x for x in _get_group_perms(group, recursive)]
170 items = [x for x in _get_group_perms(group, recursive)]
171 _check_expected_count(items, repo_items, expected_count(group, True))
171 _check_expected_count(items, repo_items, expected_count(group, True))
172
172
173 for name, perm in repo_items:
173 for name, perm in repo_items:
174 check_tree_perms(name, perm, group, 'repository.write')
174 check_tree_perms(name, perm, group, 'repository.write')
175
175
176 for name, perm in items:
176 for name, perm in items:
177 check_tree_perms(name, perm, group, 'group.write')
177 check_tree_perms(name, perm, group, 'group.write')
178
178
179
179
180 def test_user_permissions_on_group_with_recursive_mode_only_with_repos():
180 def test_user_permissions_on_group_with_recursive_mode_only_with_repos():
181 # set permission to g0_3 group to none
181 # set permission to g0_3 group to none
182 recursive = 'all'
182 recursive = 'all'
183 group = 'g0/g0_2'
183 group = 'g0/g0_2'
184 permissions_setup_func(group, 'group.admin', recursive=recursive)
184 permissions_setup_func(group, 'group.admin', recursive=recursive)
185
185
186 repo_items = [x for x in _get_repo_perms(group, recursive)]
186 repo_items = [x for x in _get_repo_perms(group, recursive)]
187 items = [x for x in _get_group_perms(group, recursive)]
187 items = [x for x in _get_group_perms(group, recursive)]
188 _check_expected_count(items, repo_items, expected_count(group, True))
188 _check_expected_count(items, repo_items, expected_count(group, True))
189
189
190 for name, perm in repo_items:
190 for name, perm in repo_items:
191 check_tree_perms(name, perm, group, 'repository.admin')
191 check_tree_perms(name, perm, group, 'repository.admin')
192
192
193 for name, perm in items:
193 for name, perm in items:
194 check_tree_perms(name, perm, group, 'group.admin')
194 check_tree_perms(name, perm, group, 'group.admin')
195
195
196
196
197 def test_user_permissions_on_group_with_recursive_mode_on_repos():
197 def test_user_permissions_on_group_with_recursive_mode_on_repos():
198 # set permission to g0/g0_1 with recursive mode on just repositories
198 # set permission to g0/g0_1 with recursive mode on just repositories
199 recursive = 'repos'
199 recursive = 'repos'
200 group = 'g0/g0_1'
200 group = 'g0/g0_1'
201 perm = 'group.write'
201 perm = 'group.write'
202 permissions_setup_func(group, perm, recursive=recursive)
202 permissions_setup_func(group, perm, recursive=recursive)
203
203
204 repo_items = [x for x in _get_repo_perms(group, recursive)]
204 repo_items = [x for x in _get_repo_perms(group, recursive)]
205 items = [x for x in _get_group_perms(group, recursive)]
205 items = [x for x in _get_group_perms(group, recursive)]
206 _check_expected_count(items, repo_items, expected_count(group, True))
206 _check_expected_count(items, repo_items, expected_count(group, True))
207
207
208 for name, perm in repo_items:
208 for name, perm in repo_items:
209 check_tree_perms(name, perm, group, 'repository.write')
209 check_tree_perms(name, perm, group, 'repository.write')
210
210
211 for name, perm in items:
211 for name, perm in items:
212 # permission is set with repos only mode, but we also change the
212 # permission is set with repos only mode, but we also change the
213 # permission on the group we trigger the apply to children from, thus
213 # permission on the group we trigger the apply to children from, thus
214 # we need to change its permission check
214 # we need to change its permission check
215 old_perm = 'group.read'
215 old_perm = 'group.read'
216 if name == group:
216 if name == group:
217 old_perm = perm
217 old_perm = perm
218 check_tree_perms(name, perm, group, old_perm)
218 check_tree_perms(name, perm, group, old_perm)
219
219
220
220
221 def test_user_permissions_on_group_with_recursive_mode_on_repo_groups():
221 def test_user_permissions_on_group_with_recursive_mode_on_repo_groups():
222 # set permission to g0/g0_1 with recursive mode on just repository groups
222 # set permission to g0/g0_1 with recursive mode on just repository groups
223 recursive = 'groups'
223 recursive = 'groups'
224 group = 'g0/g0_1'
224 group = 'g0/g0_1'
225 perm = 'group.none'
225 perm = 'group.none'
226 permissions_setup_func(group, perm, recursive=recursive)
226 permissions_setup_func(group, perm, recursive=recursive)
227
227
228 repo_items = [x for x in _get_repo_perms(group, recursive)]
228 repo_items = [x for x in _get_repo_perms(group, recursive)]
229 items = [x for x in _get_group_perms(group, recursive)]
229 items = [x for x in _get_group_perms(group, recursive)]
230 _check_expected_count(items, repo_items, expected_count(group, True))
230 _check_expected_count(items, repo_items, expected_count(group, True))
231
231
232 for name, perm in repo_items:
232 for name, perm in repo_items:
233 check_tree_perms(name, perm, group, 'repository.read')
233 check_tree_perms(name, perm, group, 'repository.read')
234
234
235 for name, perm in items:
235 for name, perm in items:
236 check_tree_perms(name, perm, group, 'group.none')
236 check_tree_perms(name, perm, group, 'group.none')
@@ -1,429 +1,458 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import threading
21 import threading
22 import time
22 import time
23 import logging
23 import logging
24 import os.path
24 import os.path
25 import subprocess32
25 import subprocess32
26 import tempfile
26 import tempfile
27 import urllib2
27 import urllib2
28 from lxml.html import fromstring, tostring
28 from lxml.html import fromstring, tostring
29 from lxml.cssselect import CSSSelector
29 from lxml.cssselect import CSSSelector
30 from urlparse import urlparse, parse_qsl
30 from urlparse import urlparse, parse_qsl
31 from urllib import unquote_plus
31 from urllib import unquote_plus
32 import webob
32 import webob
33
33
34 from webtest.app import TestResponse, TestApp, string_types
34 from webtest.app import TestResponse, TestApp, string_types
35 from webtest.compat import print_stderr
35 from webtest.compat import print_stderr
36
36
37 import pytest
37 import pytest
38 import rc_testdata
38 import rc_testdata
39
39
40 from rhodecode.model.db import User, Repository
40 from rhodecode.model.db import User, Repository
41 from rhodecode.model.meta import Session
41 from rhodecode.model.meta import Session
42 from rhodecode.model.scm import ScmModel
42 from rhodecode.model.scm import ScmModel
43 from rhodecode.lib.vcs.backends.svn.repository import SubversionRepository
43 from rhodecode.lib.vcs.backends.svn.repository import SubversionRepository
44 from rhodecode.lib.vcs.backends.base import EmptyCommit
44 from rhodecode.lib.vcs.backends.base import EmptyCommit
45 from rhodecode.tests import login_user_session
45 from rhodecode.tests import login_user_session
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 class CustomTestResponse(TestResponse):
50 class CustomTestResponse(TestResponse):
51 def _save_output(self, out):
51 def _save_output(self, out):
52 f = tempfile.NamedTemporaryFile(
52 f = tempfile.NamedTemporaryFile(
53 delete=False, prefix='rc-test-', suffix='.html')
53 delete=False, prefix='rc-test-', suffix='.html')
54 f.write(out)
54 f.write(out)
55 return f.name
55 return f.name
56
56
57 def mustcontain(self, *strings, **kw):
57 def mustcontain(self, *strings, **kw):
58 """
58 """
59 Assert that the response contains all of the strings passed
59 Assert that the response contains all of the strings passed
60 in as arguments.
60 in as arguments.
61
61
62 Equivalent to::
62 Equivalent to::
63
63
64 assert string in res
64 assert string in res
65 """
65 """
66 if 'no' in kw:
66 if 'no' in kw:
67 no = kw['no']
67 no = kw['no']
68 del kw['no']
68 del kw['no']
69 if isinstance(no, string_types):
69 if isinstance(no, string_types):
70 no = [no]
70 no = [no]
71 else:
71 else:
72 no = []
72 no = []
73 if kw:
73 if kw:
74 raise TypeError(
74 raise TypeError(
75 "The only keyword argument allowed is 'no' got %s" % kw)
75 "The only keyword argument allowed is 'no' got %s" % kw)
76
76
77 f = self._save_output(str(self))
77 f = self._save_output(str(self))
78
78
79 for s in strings:
79 for s in strings:
80 if not s in self:
80 if not s in self:
81 print_stderr("Actual response (no %r):" % s)
81 print_stderr("Actual response (no %r):" % s)
82 print_stderr(str(self))
82 print_stderr(str(self))
83 raise IndexError(
83 raise IndexError(
84 "Body does not contain string %r, output saved as %s" % (
84 "Body does not contain string %r, output saved as %s" % (
85 s, f))
85 s, f))
86
86
87 for no_s in no:
87 for no_s in no:
88 if no_s in self:
88 if no_s in self:
89 print_stderr("Actual response (has %r)" % no_s)
89 print_stderr("Actual response (has %r)" % no_s)
90 print_stderr(str(self))
90 print_stderr(str(self))
91 raise IndexError(
91 raise IndexError(
92 "Body contains bad string %r, output saved as %s" % (
92 "Body contains bad string %r, output saved as %s" % (
93 no_s, f))
93 no_s, f))
94
94
95 def assert_response(self):
95 def assert_response(self):
96 return AssertResponse(self)
96 return AssertResponse(self)
97
97
98 def get_session_from_response(self):
98 def get_session_from_response(self):
99 """
99 """
100 This returns the session from a response object.
100 This returns the session from a response object.
101 """
101 """
102
102
103 from pyramid_beaker import session_factory_from_settings
103 from pyramid_beaker import session_factory_from_settings
104 session = session_factory_from_settings(
104 session = session_factory_from_settings(
105 self.test_app.app.config.get_settings())
105 self.test_app.app.config.get_settings())
106 return session(self.request)
106 return session(self.request)
107
107
108
108
109 class TestRequest(webob.BaseRequest):
109 class TestRequest(webob.BaseRequest):
110
110
111 # for py.test
111 # for py.test
112 disabled = True
112 disabled = True
113 ResponseClass = CustomTestResponse
113 ResponseClass = CustomTestResponse
114
114
115 def add_response_callback(self, callback):
115 def add_response_callback(self, callback):
116 pass
116 pass
117
117
118
118
119 class CustomTestApp(TestApp):
119 class CustomTestApp(TestApp):
120 """
120 """
121 Custom app to make mustcontain more usefull, and extract special methods
121 Custom app to make mustcontain more usefull, and extract special methods
122 """
122 """
123 RequestClass = TestRequest
123 RequestClass = TestRequest
124 rc_login_data = {}
124 rc_login_data = {}
125 rc_current_session = None
125 rc_current_session = None
126
126
127 def login(self, username=None, password=None):
127 def login(self, username=None, password=None):
128 from rhodecode.lib import auth
128 from rhodecode.lib import auth
129
129
130 if username and password:
130 if username and password:
131 session = login_user_session(self, username, password)
131 session = login_user_session(self, username, password)
132 else:
132 else:
133 session = login_user_session(self)
133 session = login_user_session(self)
134
134
135 self.rc_login_data['csrf_token'] = auth.get_csrf_token(session)
135 self.rc_login_data['csrf_token'] = auth.get_csrf_token(session)
136 self.rc_current_session = session
136 self.rc_current_session = session
137 return session['rhodecode_user']
137 return session['rhodecode_user']
138
138
139 @property
139 @property
140 def csrf_token(self):
140 def csrf_token(self):
141 return self.rc_login_data['csrf_token']
141 return self.rc_login_data['csrf_token']
142
142
143
143
144 def set_anonymous_access(enabled):
144 def set_anonymous_access(enabled):
145 """(Dis)allows anonymous access depending on parameter `enabled`"""
145 """(Dis)allows anonymous access depending on parameter `enabled`"""
146 user = User.get_default_user()
146 user = User.get_default_user()
147 user.active = enabled
147 user.active = enabled
148 Session().add(user)
148 Session().add(user)
149 Session().commit()
149 Session().commit()
150 time.sleep(1.5) # must sleep for cache (1s to expire)
150 time.sleep(1.5) # must sleep for cache (1s to expire)
151 log.info('anonymous access is now: %s', enabled)
151 log.info('anonymous access is now: %s', enabled)
152 assert enabled == User.get_default_user().active, (
152 assert enabled == User.get_default_user().active, (
153 'Cannot set anonymous access')
153 'Cannot set anonymous access')
154
154
155
155
156 def check_xfail_backends(node, backend_alias):
156 def check_xfail_backends(node, backend_alias):
157 # Using "xfail_backends" here intentionally, since this marks work
157 # Using "xfail_backends" here intentionally, since this marks work
158 # which is "to be done" soon.
158 # which is "to be done" soon.
159 skip_marker = node.get_marker('xfail_backends')
159 skip_marker = node.get_marker('xfail_backends')
160 if skip_marker and backend_alias in skip_marker.args:
160 if skip_marker and backend_alias in skip_marker.args:
161 msg = "Support for backend %s to be developed." % (backend_alias, )
161 msg = "Support for backend %s to be developed." % (backend_alias, )
162 msg = skip_marker.kwargs.get('reason', msg)
162 msg = skip_marker.kwargs.get('reason', msg)
163 pytest.xfail(msg)
163 pytest.xfail(msg)
164
164
165
165
166 def check_skip_backends(node, backend_alias):
166 def check_skip_backends(node, backend_alias):
167 # Using "skip_backends" here intentionally, since this marks work which is
167 # Using "skip_backends" here intentionally, since this marks work which is
168 # not supported.
168 # not supported.
169 skip_marker = node.get_marker('skip_backends')
169 skip_marker = node.get_marker('skip_backends')
170 if skip_marker and backend_alias in skip_marker.args:
170 if skip_marker and backend_alias in skip_marker.args:
171 msg = "Feature not supported for backend %s." % (backend_alias, )
171 msg = "Feature not supported for backend %s." % (backend_alias, )
172 msg = skip_marker.kwargs.get('reason', msg)
172 msg = skip_marker.kwargs.get('reason', msg)
173 pytest.skip(msg)
173 pytest.skip(msg)
174
174
175
175
176 def extract_git_repo_from_dump(dump_name, repo_name):
176 def extract_git_repo_from_dump(dump_name, repo_name):
177 """Create git repo `repo_name` from dump `dump_name`."""
177 """Create git repo `repo_name` from dump `dump_name`."""
178 repos_path = ScmModel().repos_path
178 repos_path = ScmModel().repos_path
179 target_path = os.path.join(repos_path, repo_name)
179 target_path = os.path.join(repos_path, repo_name)
180 rc_testdata.extract_git_dump(dump_name, target_path)
180 rc_testdata.extract_git_dump(dump_name, target_path)
181 return target_path
181 return target_path
182
182
183
183
184 def extract_hg_repo_from_dump(dump_name, repo_name):
184 def extract_hg_repo_from_dump(dump_name, repo_name):
185 """Create hg repo `repo_name` from dump `dump_name`."""
185 """Create hg repo `repo_name` from dump `dump_name`."""
186 repos_path = ScmModel().repos_path
186 repos_path = ScmModel().repos_path
187 target_path = os.path.join(repos_path, repo_name)
187 target_path = os.path.join(repos_path, repo_name)
188 rc_testdata.extract_hg_dump(dump_name, target_path)
188 rc_testdata.extract_hg_dump(dump_name, target_path)
189 return target_path
189 return target_path
190
190
191
191
192 def extract_svn_repo_from_dump(dump_name, repo_name):
192 def extract_svn_repo_from_dump(dump_name, repo_name):
193 """Create a svn repo `repo_name` from dump `dump_name`."""
193 """Create a svn repo `repo_name` from dump `dump_name`."""
194 repos_path = ScmModel().repos_path
194 repos_path = ScmModel().repos_path
195 target_path = os.path.join(repos_path, repo_name)
195 target_path = os.path.join(repos_path, repo_name)
196 SubversionRepository(target_path, create=True)
196 SubversionRepository(target_path, create=True)
197 _load_svn_dump_into_repo(dump_name, target_path)
197 _load_svn_dump_into_repo(dump_name, target_path)
198 return target_path
198 return target_path
199
199
200
200
201 def assert_message_in_log(log_records, message, levelno, module):
201 def assert_message_in_log(log_records, message, levelno, module):
202 messages = [
202 messages = [
203 r.message for r in log_records
203 r.message for r in log_records
204 if r.module == module and r.levelno == levelno
204 if r.module == module and r.levelno == levelno
205 ]
205 ]
206 assert message in messages
206 assert message in messages
207
207
208
208
209 def _load_svn_dump_into_repo(dump_name, repo_path):
209 def _load_svn_dump_into_repo(dump_name, repo_path):
210 """
210 """
211 Utility to populate a svn repository with a named dump
211 Utility to populate a svn repository with a named dump
212
212
213 Currently the dumps are in rc_testdata. They might later on be
213 Currently the dumps are in rc_testdata. They might later on be
214 integrated with the main repository once they stabilize more.
214 integrated with the main repository once they stabilize more.
215 """
215 """
216 dump = rc_testdata.load_svn_dump(dump_name)
216 dump = rc_testdata.load_svn_dump(dump_name)
217 load_dump = subprocess32.Popen(
217 load_dump = subprocess32.Popen(
218 ['svnadmin', 'load', repo_path],
218 ['svnadmin', 'load', repo_path],
219 stdin=subprocess32.PIPE, stdout=subprocess32.PIPE,
219 stdin=subprocess32.PIPE, stdout=subprocess32.PIPE,
220 stderr=subprocess32.PIPE)
220 stderr=subprocess32.PIPE)
221 out, err = load_dump.communicate(dump)
221 out, err = load_dump.communicate(dump)
222 if load_dump.returncode != 0:
222 if load_dump.returncode != 0:
223 log.error("Output of load_dump command: %s", out)
223 log.error("Output of load_dump command: %s", out)
224 log.error("Error output of load_dump command: %s", err)
224 log.error("Error output of load_dump command: %s", err)
225 raise Exception(
225 raise Exception(
226 'Failed to load dump "%s" into repository at path "%s".'
226 'Failed to load dump "%s" into repository at path "%s".'
227 % (dump_name, repo_path))
227 % (dump_name, repo_path))
228
228
229
229
230 class AssertResponse(object):
230 class AssertResponse(object):
231 """
231 """
232 Utility that helps to assert things about a given HTML response.
232 Utility that helps to assert things about a given HTML response.
233 """
233 """
234
234
235 def __init__(self, response):
235 def __init__(self, response):
236 self.response = response
236 self.response = response
237
237
238 def get_imports(self):
238 def get_imports(self):
239 return fromstring, tostring, CSSSelector
239 return fromstring, tostring, CSSSelector
240
240
241 def one_element_exists(self, css_selector):
241 def one_element_exists(self, css_selector):
242 self.get_element(css_selector)
242 self.get_element(css_selector)
243
243
244 def no_element_exists(self, css_selector):
244 def no_element_exists(self, css_selector):
245 assert not self._get_elements(css_selector)
245 assert not self._get_elements(css_selector)
246
246
247 def element_equals_to(self, css_selector, expected_content):
247 def element_equals_to(self, css_selector, expected_content):
248 element = self.get_element(css_selector)
248 element = self.get_element(css_selector)
249 element_text = self._element_to_string(element)
249 element_text = self._element_to_string(element)
250 assert expected_content in element_text
250 assert expected_content in element_text
251
251
252 def element_contains(self, css_selector, expected_content):
252 def element_contains(self, css_selector, expected_content):
253 element = self.get_element(css_selector)
253 element = self.get_element(css_selector)
254 assert expected_content in element.text_content()
254 assert expected_content in element.text_content()
255
255
256 def element_value_contains(self, css_selector, expected_content):
256 def element_value_contains(self, css_selector, expected_content):
257 element = self.get_element(css_selector)
257 element = self.get_element(css_selector)
258 assert expected_content in element.value
258 assert expected_content in element.value
259
259
260 def contains_one_link(self, link_text, href):
260 def contains_one_link(self, link_text, href):
261 fromstring, tostring, CSSSelector = self.get_imports()
261 fromstring, tostring, CSSSelector = self.get_imports()
262 doc = fromstring(self.response.body)
262 doc = fromstring(self.response.body)
263 sel = CSSSelector('a[href]')
263 sel = CSSSelector('a[href]')
264 elements = [
264 elements = [
265 e for e in sel(doc) if e.text_content().strip() == link_text]
265 e for e in sel(doc) if e.text_content().strip() == link_text]
266 assert len(elements) == 1, "Did not find link or found multiple links"
266 assert len(elements) == 1, "Did not find link or found multiple links"
267 self._ensure_url_equal(elements[0].attrib.get('href'), href)
267 self._ensure_url_equal(elements[0].attrib.get('href'), href)
268
268
269 def contains_one_anchor(self, anchor_id):
269 def contains_one_anchor(self, anchor_id):
270 fromstring, tostring, CSSSelector = self.get_imports()
270 fromstring, tostring, CSSSelector = self.get_imports()
271 doc = fromstring(self.response.body)
271 doc = fromstring(self.response.body)
272 sel = CSSSelector('#' + anchor_id)
272 sel = CSSSelector('#' + anchor_id)
273 elements = sel(doc)
273 elements = sel(doc)
274 assert len(elements) == 1, 'cannot find 1 element {}'.format(anchor_id)
274 assert len(elements) == 1, 'cannot find 1 element {}'.format(anchor_id)
275
275
276 def _ensure_url_equal(self, found, expected):
276 def _ensure_url_equal(self, found, expected):
277 assert _Url(found) == _Url(expected)
277 assert _Url(found) == _Url(expected)
278
278
279 def get_element(self, css_selector):
279 def get_element(self, css_selector):
280 elements = self._get_elements(css_selector)
280 elements = self._get_elements(css_selector)
281 assert len(elements) == 1, 'cannot find 1 element {}'.format(css_selector)
281 assert len(elements) == 1, 'cannot find 1 element {}'.format(css_selector)
282 return elements[0]
282 return elements[0]
283
283
284 def get_elements(self, css_selector):
284 def get_elements(self, css_selector):
285 return self._get_elements(css_selector)
285 return self._get_elements(css_selector)
286
286
287 def _get_elements(self, css_selector):
287 def _get_elements(self, css_selector):
288 fromstring, tostring, CSSSelector = self.get_imports()
288 fromstring, tostring, CSSSelector = self.get_imports()
289 doc = fromstring(self.response.body)
289 doc = fromstring(self.response.body)
290 sel = CSSSelector(css_selector)
290 sel = CSSSelector(css_selector)
291 elements = sel(doc)
291 elements = sel(doc)
292 return elements
292 return elements
293
293
294 def _element_to_string(self, element):
294 def _element_to_string(self, element):
295 fromstring, tostring, CSSSelector = self.get_imports()
295 fromstring, tostring, CSSSelector = self.get_imports()
296 return tostring(element)
296 return tostring(element)
297
297
298
298
299 class _Url(object):
299 class _Url(object):
300 """
300 """
301 A url object that can be compared with other url orbjects
301 A url object that can be compared with other url orbjects
302 without regard to the vagaries of encoding, escaping, and ordering
302 without regard to the vagaries of encoding, escaping, and ordering
303 of parameters in query strings.
303 of parameters in query strings.
304
304
305 Inspired by
305 Inspired by
306 http://stackoverflow.com/questions/5371992/comparing-two-urls-in-python
306 http://stackoverflow.com/questions/5371992/comparing-two-urls-in-python
307 """
307 """
308
308
309 def __init__(self, url):
309 def __init__(self, url):
310 parts = urlparse(url)
310 parts = urlparse(url)
311 _query = frozenset(parse_qsl(parts.query))
311 _query = frozenset(parse_qsl(parts.query))
312 _path = unquote_plus(parts.path)
312 _path = unquote_plus(parts.path)
313 parts = parts._replace(query=_query, path=_path)
313 parts = parts._replace(query=_query, path=_path)
314 self.parts = parts
314 self.parts = parts
315
315
316 def __eq__(self, other):
316 def __eq__(self, other):
317 return self.parts == other.parts
317 return self.parts == other.parts
318
318
319 def __hash__(self):
319 def __hash__(self):
320 return hash(self.parts)
320 return hash(self.parts)
321
321
322
322
323 def run_test_concurrently(times, raise_catched_exc=True):
323 def run_test_concurrently(times, raise_catched_exc=True):
324 """
324 """
325 Add this decorator to small pieces of code that you want to test
325 Add this decorator to small pieces of code that you want to test
326 concurrently
326 concurrently
327
327
328 ex:
328 ex:
329
329
330 @test_concurrently(25)
330 @test_concurrently(25)
331 def my_test_function():
331 def my_test_function():
332 ...
332 ...
333 """
333 """
334 def test_concurrently_decorator(test_func):
334 def test_concurrently_decorator(test_func):
335 def wrapper(*args, **kwargs):
335 def wrapper(*args, **kwargs):
336 exceptions = []
336 exceptions = []
337
337
338 def call_test_func():
338 def call_test_func():
339 try:
339 try:
340 test_func(*args, **kwargs)
340 test_func(*args, **kwargs)
341 except Exception as e:
341 except Exception as e:
342 exceptions.append(e)
342 exceptions.append(e)
343 if raise_catched_exc:
343 if raise_catched_exc:
344 raise
344 raise
345 threads = []
345 threads = []
346 for i in range(times):
346 for i in range(times):
347 threads.append(threading.Thread(target=call_test_func))
347 threads.append(threading.Thread(target=call_test_func))
348 for t in threads:
348 for t in threads:
349 t.start()
349 t.start()
350 for t in threads:
350 for t in threads:
351 t.join()
351 t.join()
352 if exceptions:
352 if exceptions:
353 raise Exception(
353 raise Exception(
354 'test_concurrently intercepted %s exceptions: %s' % (
354 'test_concurrently intercepted %s exceptions: %s' % (
355 len(exceptions), exceptions))
355 len(exceptions), exceptions))
356 return wrapper
356 return wrapper
357 return test_concurrently_decorator
357 return test_concurrently_decorator
358
358
359
359
360 def wait_for_url(url, timeout=10):
360 def wait_for_url(url, timeout=10):
361 """
361 """
362 Wait until URL becomes reachable.
362 Wait until URL becomes reachable.
363
363
364 It polls the URL until the timeout is reached or it became reachable.
364 It polls the URL until the timeout is reached or it became reachable.
365 If will call to `py.test.fail` in case the URL is not reachable.
365 If will call to `py.test.fail` in case the URL is not reachable.
366 """
366 """
367 timeout = time.time() + timeout
367 timeout = time.time() + timeout
368 last = 0
368 last = 0
369 wait = 0.1
369 wait = 0.1
370
370
371 while timeout > last:
371 while timeout > last:
372 last = time.time()
372 last = time.time()
373 if is_url_reachable(url):
373 if is_url_reachable(url):
374 break
374 break
375 elif (last + wait) > time.time():
375 elif (last + wait) > time.time():
376 # Go to sleep because not enough time has passed since last check.
376 # Go to sleep because not enough time has passed since last check.
377 time.sleep(wait)
377 time.sleep(wait)
378 else:
378 else:
379 pytest.fail("Timeout while waiting for URL {}".format(url))
379 pytest.fail("Timeout while waiting for URL {}".format(url))
380
380
381
381
382 def is_url_reachable(url):
382 def is_url_reachable(url):
383 try:
383 try:
384 urllib2.urlopen(url)
384 urllib2.urlopen(url)
385 except urllib2.URLError:
385 except urllib2.URLError:
386 return False
386 return False
387 return True
387 return True
388
388
389
389
390 def repo_on_filesystem(repo_name):
390 def repo_on_filesystem(repo_name):
391 from rhodecode.lib import vcs
391 from rhodecode.lib import vcs
392 from rhodecode.tests import TESTS_TMP_PATH
392 from rhodecode.tests import TESTS_TMP_PATH
393 repo = vcs.get_vcs_instance(
393 repo = vcs.get_vcs_instance(
394 os.path.join(TESTS_TMP_PATH, repo_name), create=False)
394 os.path.join(TESTS_TMP_PATH, repo_name), create=False)
395 return repo is not None
395 return repo is not None
396
396
397
397
398 def commit_change(
398 def commit_change(
399 repo, filename, content, message, vcs_type, parent=None, newfile=False):
399 repo, filename, content, message, vcs_type, parent=None, newfile=False):
400 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
400 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
401
401
402 repo = Repository.get_by_repo_name(repo)
402 repo = Repository.get_by_repo_name(repo)
403 _commit = parent
403 _commit = parent
404 if not parent:
404 if not parent:
405 _commit = EmptyCommit(alias=vcs_type)
405 _commit = EmptyCommit(alias=vcs_type)
406
406
407 if newfile:
407 if newfile:
408 nodes = {
408 nodes = {
409 filename: {
409 filename: {
410 'content': content
410 'content': content
411 }
411 }
412 }
412 }
413 commit = ScmModel().create_nodes(
413 commit = ScmModel().create_nodes(
414 user=TEST_USER_ADMIN_LOGIN, repo=repo,
414 user=TEST_USER_ADMIN_LOGIN, repo=repo,
415 message=message,
415 message=message,
416 nodes=nodes,
416 nodes=nodes,
417 parent_commit=_commit,
417 parent_commit=_commit,
418 author=TEST_USER_ADMIN_LOGIN,
418 author=TEST_USER_ADMIN_LOGIN,
419 )
419 )
420 else:
420 else:
421 commit = ScmModel().commit_change(
421 commit = ScmModel().commit_change(
422 repo=repo.scm_instance(), repo_name=repo.repo_name,
422 repo=repo.scm_instance(), repo_name=repo.repo_name,
423 commit=parent, user=TEST_USER_ADMIN_LOGIN,
423 commit=parent, user=TEST_USER_ADMIN_LOGIN,
424 author=TEST_USER_ADMIN_LOGIN,
424 author=TEST_USER_ADMIN_LOGIN,
425 message=message,
425 message=message,
426 content=content,
426 content=content,
427 f_path=filename
427 f_path=filename
428 )
428 )
429 return commit
429 return commit
430
431
432 def permission_update_data_generator(csrf_token, default=None, grant=None, revoke=None):
433 if not default:
434 raise ValueError('Permission for default user must be given')
435 form_data = [(
436 'csrf_token', csrf_token
437 )]
438 # add default
439 form_data.extend([
440 ('u_perm_1', default)
441 ])
442
443 if grant:
444 for cnt, (obj_id, perm, obj_name, obj_type) in enumerate(grant, 1):
445 form_data.extend([
446 ('perm_new_member_perm_new{}'.format(cnt), perm),
447 ('perm_new_member_id_new{}'.format(cnt), obj_id),
448 ('perm_new_member_name_new{}'.format(cnt), obj_name),
449 ('perm_new_member_type_new{}'.format(cnt), obj_type),
450
451 ])
452 if revoke:
453 for obj_id, obj_type in revoke:
454 form_data.extend([
455 ('perm_del_member_id_{}'.format(obj_id), obj_id),
456 ('perm_del_member_type_{}'.format(obj_id), obj_type),
457 ])
458 return form_data
General Comments 0
You need to be logged in to leave comments. Login now