##// END OF EJS Templates
repo-permissions: moved permissions into pyramid....
marcink -
r1734:ddacc559 default
parent child Browse files
Show More
@@ -0,0 +1,98 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2017 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 logging
22
23 import deform
24 from pyramid.httpexceptions import HTTPFound
25 from pyramid.view import view_config
26
27 from rhodecode.apps._base import RepoAppView
28 from rhodecode.forms import RcForm
29 from rhodecode.lib import helpers as h
30 from rhodecode.lib import audit_logger
31 from rhodecode.lib.auth import (
32 LoginRequired, HasRepoPermissionAnyDecorator,
33 HasRepoPermissionAllDecorator, CSRFRequired)
34 from rhodecode.model.db import RepositoryField, RepoGroup
35 from rhodecode.model.forms import RepoPermsForm
36 from rhodecode.model.meta import Session
37 from rhodecode.model.repo import RepoModel
38 from rhodecode.model.scm import RepoGroupList, ScmModel
39 from rhodecode.model.validation_schema.schemas import repo_schema
40
41 log = logging.getLogger(__name__)
42
43
44 class RepoSettingsPermissionsView(RepoAppView):
45
46 def load_default_context(self):
47 c = self._get_local_tmpl_context()
48
49 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
50 c.repo_info = self.db_repo
51
52 self._register_global_c(c)
53 return c
54
55 @LoginRequired()
56 @HasRepoPermissionAnyDecorator('repository.admin')
57 @view_config(
58 route_name='edit_repo_perms', request_method='GET',
59 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
60 def edit_permissions(self):
61 c = self.load_default_context()
62 c.active = 'permissions'
63 return self._get_template_context(c)
64
65 @LoginRequired()
66 @HasRepoPermissionAllDecorator('repository.admin')
67 @CSRFRequired()
68 @view_config(
69 route_name='edit_repo_perms', request_method='POST',
70 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
71 def edit_permissions_update(self):
72 _ = self.request.translate
73 c = self.load_default_context()
74 c.active = 'permissions'
75 data = self.request.POST
76 # store private flag outside of HTML to verify if we can modify
77 # default user permissions, prevents submition of FAKE post data
78 # into the form for private repos
79 data['repo_private'] = self.db_repo.private
80 form = RepoPermsForm()().to_python(data)
81 changes = RepoModel().update_permissions(
82 self.db_repo_name, form['perm_additions'], form['perm_updates'],
83 form['perm_deletions'])
84
85 action_data = {
86 'added': changes['added'],
87 'updated': changes['updated'],
88 'deleted': changes['deleted'],
89 }
90 audit_logger.store(
91 'repo.edit.permissions', action_data=action_data,
92 user=self._rhodecode_user, repo=self.db_repo)
93
94 Session().commit()
95 h.flash(_('Repository permissions updated'), category='success')
96
97 raise HTTPFound(
98 self.request.route_path('edit_repo_perms', repo_name=self.db_repo_name))
@@ -1,61 +1,66 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 def includeme(config):
23 23
24 24 # Settings
25 25 config.add_route(
26 26 name='edit_repo',
27 27 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
28 28
29 29 # Caches
30 30 config.add_route(
31 31 name='edit_repo_caches',
32 32 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
33 33
34 # Permissions
35 config.add_route(
36 name='edit_repo_perms',
37 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
38
34 39 # Repo Review Rules
35 40 config.add_route(
36 41 name='repo_reviewers',
37 42 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
38 43
39 44 # Maintenance
40 45 config.add_route(
41 46 name='repo_maintenance',
42 47 pattern='/{repo_name:.*?[^/]}/maintenance', repo_route=True)
43 48
44 49 config.add_route(
45 50 name='repo_maintenance_execute',
46 51 pattern='/{repo_name:.*?[^/]}/maintenance/execute', repo_route=True)
47 52
48 53 # Strip
49 54 config.add_route(
50 55 name='strip',
51 56 pattern='/{repo_name:.*?[^/]}/strip', repo_route=True)
52 57
53 58 config.add_route(
54 59 name='strip_check',
55 60 pattern='/{repo_name:.*?[^/]}/strip_check', repo_route=True)
56 61
57 62 config.add_route(
58 63 name='strip_execute',
59 64 pattern='/{repo_name:.*?[^/]}/strip_execute', repo_route=True)
60 65 # Scan module for configuration decorators.
61 66 config.scan()
@@ -1,231 +1,232 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import mock
22 22 import pytest
23 23
24 24 from rhodecode.lib.utils2 import str2bool
25 25 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
26 26 from rhodecode.model.db import Repository, UserRepoToPerm, Permission, User
27 27 from rhodecode.model.meta import Session
28 28 from rhodecode.tests import (
29 29 url, HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN,
30 30 assert_session_flash)
31 31 from rhodecode.tests.fixture import Fixture
32 32
33 33 fixture = Fixture()
34 34
35 35
36 36 def route_path(name, params=None, **kwargs):
37 37 import urllib
38 38
39 39 base_url = {
40 40 'edit_repo': '/{repo_name}/settings',
41 41 'edit_repo_caches': '/{repo_name}/settings/caches',
42 'edit_repo_perms': '/{repo_name}/settings/permissions',
42 43 }[name].format(**kwargs)
43 44
44 45 if params:
45 46 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
46 47 return base_url
47 48
48 49
49 50 def _get_permission_for_user(user, repo):
50 51 perm = UserRepoToPerm.query()\
51 52 .filter(UserRepoToPerm.repository ==
52 53 Repository.get_by_repo_name(repo))\
53 54 .filter(UserRepoToPerm.user == User.get_by_username(user))\
54 55 .all()
55 56 return perm
56 57
57 58
58 59 @pytest.mark.usefixtures('autologin_user', 'app')
59 60 class TestAdminRepoSettings(object):
60 61 @pytest.mark.parametrize('urlname', [
61 62 'edit_repo',
62 'edit_repo_caches'
63 'edit_repo_caches',
64 'edit_repo_perms',
63 65 ])
64 66 def test_show_page(self, urlname, app, backend):
65 67 app.get(route_path(urlname, repo_name=backend.repo_name), status=200)
66 68
67 69 def test_edit_accessible_when_missing_requirements(
68 70 self, backend_hg, autologin_user):
69 71 scm_patcher = mock.patch.object(
70 72 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
71 73 with scm_patcher:
72 74 self.app.get(route_path('edit_repo', repo_name=backend_hg.repo_name))
73 75
74 76 @pytest.mark.parametrize('urlname', [
75 'edit_repo_perms',
76 77 'edit_repo_advanced',
77 78 'repo_vcs_settings',
78 79 'edit_repo_fields',
79 80 'repo_settings_issuetracker',
80 81 'edit_repo_remote',
81 82 'edit_repo_statistics',
82 83 ])
83 84 def test_show_page_pylons(self, urlname, app):
84 85 app.get(url(urlname, repo_name=HG_REPO))
85 86
86 87 @pytest.mark.parametrize('update_settings', [
87 88 {'repo_description': 'alter-desc'},
88 89 {'repo_owner': TEST_USER_REGULAR_LOGIN},
89 90 {'repo_private': 'true'},
90 91 {'repo_enable_locking': 'true'},
91 92 {'repo_enable_downloads': 'true'},
92 93 ])
93 94 def test_update_repo_settings(self, update_settings, csrf_token, backend, user_util):
94 95 repo = user_util.create_repo(repo_type=backend.alias)
95 96 repo_name = repo.repo_name
96 97
97 98 params = fixture._get_repo_create_params(
98 99 csrf_token=csrf_token,
99 100 repo_name=repo_name,
100 101 repo_type=backend.alias,
101 102 repo_owner=TEST_USER_ADMIN_LOGIN,
102 103 repo_description='DESC',
103 104
104 105 repo_private='false',
105 106 repo_enable_locking='false',
106 107 repo_enable_downloads='false')
107 108 params.update(update_settings)
108 109 self.app.post(
109 110 route_path('edit_repo', repo_name=repo_name),
110 111 params=params, status=302)
111 112
112 113 repo = Repository.get_by_repo_name(repo_name)
113 114 assert repo.user.username == \
114 115 update_settings.get('repo_owner', repo.user.username)
115 116
116 117 assert repo.description == \
117 118 update_settings.get('repo_description', repo.description)
118 119
119 120 assert repo.private == \
120 121 str2bool(update_settings.get(
121 122 'repo_private', repo.private))
122 123
123 124 assert repo.enable_locking == \
124 125 str2bool(update_settings.get(
125 126 'repo_enable_locking', repo.enable_locking))
126 127
127 128 assert repo.enable_downloads == \
128 129 str2bool(update_settings.get(
129 130 'repo_enable_downloads', repo.enable_downloads))
130 131
131 132 def test_update_repo_name_via_settings(self, csrf_token, user_util, backend):
132 133 repo = user_util.create_repo(repo_type=backend.alias)
133 134 repo_name = repo.repo_name
134 135
135 136 repo_group = user_util.create_repo_group()
136 137 repo_group_name = repo_group.group_name
137 138 new_name = repo_group_name + '_' + repo_name
138 139
139 140 params = fixture._get_repo_create_params(
140 141 csrf_token=csrf_token,
141 142 repo_name=new_name,
142 143 repo_type=backend.alias,
143 144 repo_owner=TEST_USER_ADMIN_LOGIN,
144 145 repo_description='DESC',
145 146 repo_private='false',
146 147 repo_enable_locking='false',
147 148 repo_enable_downloads='false')
148 149 self.app.post(
149 150 route_path('edit_repo', repo_name=repo_name),
150 151 params=params, status=302)
151 152 repo = Repository.get_by_repo_name(new_name)
152 153 assert repo.repo_name == new_name
153 154
154 155 def test_update_repo_group_via_settings(self, csrf_token, user_util, backend):
155 156 repo = user_util.create_repo(repo_type=backend.alias)
156 157 repo_name = repo.repo_name
157 158
158 159 repo_group = user_util.create_repo_group()
159 160 repo_group_name = repo_group.group_name
160 161 repo_group_id = repo_group.group_id
161 162
162 163 new_name = repo_group_name + '/' + repo_name
163 164 params = fixture._get_repo_create_params(
164 165 csrf_token=csrf_token,
165 166 repo_name=repo_name,
166 167 repo_type=backend.alias,
167 168 repo_owner=TEST_USER_ADMIN_LOGIN,
168 169 repo_description='DESC',
169 170 repo_group=repo_group_id,
170 171 repo_private='false',
171 172 repo_enable_locking='false',
172 173 repo_enable_downloads='false')
173 174 self.app.post(
174 175 route_path('edit_repo', repo_name=repo_name),
175 176 params=params, status=302)
176 177 repo = Repository.get_by_repo_name(new_name)
177 178 assert repo.repo_name == new_name
178 179
179 180 def test_set_private_flag_sets_default_user_permissions_to_none(
180 181 self, autologin_user, backend, csrf_token):
181 182
182 183 # initially repository perm should be read
183 184 perm = _get_permission_for_user(user='default', repo=backend.repo_name)
184 185 assert len(perm) == 1
185 186 assert perm[0].permission.permission_name == 'repository.read'
186 187 assert not backend.repo.private
187 188
188 189 response = self.app.post(
189 190 route_path('edit_repo', repo_name=backend.repo_name),
190 191 params=fixture._get_repo_create_params(
191 192 repo_private='true',
192 193 repo_name=backend.repo_name,
193 194 repo_type=backend.alias,
194 195 repo_owner=TEST_USER_ADMIN_LOGIN,
195 196 csrf_token=csrf_token), status=302)
196 197
197 198 assert_session_flash(
198 199 response,
199 200 msg='Repository %s updated successfully' % (backend.repo_name))
200 201
201 202 repo = Repository.get_by_repo_name(backend.repo_name)
202 203 assert repo.private is True
203 204
204 205 # now the repo default permission should be None
205 206 perm = _get_permission_for_user(user='default', repo=backend.repo_name)
206 207 assert len(perm) == 1
207 208 assert perm[0].permission.permission_name == 'repository.none'
208 209
209 210 response = self.app.post(
210 211 route_path('edit_repo', repo_name=backend.repo_name),
211 212 params=fixture._get_repo_create_params(
212 213 repo_private='false',
213 214 repo_name=backend.repo_name,
214 215 repo_type=backend.alias,
215 216 repo_owner=TEST_USER_ADMIN_LOGIN,
216 217 csrf_token=csrf_token), status=302)
217 218
218 219 assert_session_flash(
219 220 response,
220 221 msg='Repository %s updated successfully' % (backend.repo_name))
221 222 assert backend.repo.private is False
222 223
223 224 # we turn off private now the repo default permission should stay None
224 225 perm = _get_permission_for_user(user='default', repo=backend.repo_name)
225 226 assert len(perm) == 1
226 227 assert perm[0].permission.permission_name == 'repository.none'
227 228
228 229 # update this permission back
229 230 perm[0].permission = Permission.get_by_key('repository.read')
230 231 Session().add(perm[0])
231 232 Session().commit()
@@ -1,1098 +1,1088 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Routes configuration
23 23
24 24 The more specific and detailed routes should be defined first so they
25 25 may take precedent over the more generic routes. For more information
26 26 refer to the routes manual at http://routes.groovie.org/docs/
27 27
28 28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 29 and _route_name variable which uses some of stored naming here to do redirects.
30 30 """
31 31 import os
32 32 import re
33 33 from routes import Mapper
34 34
35 35 # prefix for non repository related links needs to be prefixed with `/`
36 36 ADMIN_PREFIX = '/_admin'
37 37 STATIC_FILE_PREFIX = '/_static'
38 38
39 39 # Default requirements for URL parts
40 40 URL_NAME_REQUIREMENTS = {
41 41 # group name can have a slash in them, but they must not end with a slash
42 42 'group_name': r'.*?[^/]',
43 43 'repo_group_name': r'.*?[^/]',
44 44 # repo names can have a slash in them, but they must not end with a slash
45 45 'repo_name': r'.*?[^/]',
46 46 # file path eats up everything at the end
47 47 'f_path': r'.*',
48 48 # reference types
49 49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
50 50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
51 51 }
52 52
53 53
54 54 def add_route_requirements(route_path, requirements):
55 55 """
56 56 Adds regex requirements to pyramid routes using a mapping dict
57 57
58 58 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
59 59 '/{action}/{id:\d+}'
60 60
61 61 """
62 62 for key, regex in requirements.items():
63 63 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
64 64 return route_path
65 65
66 66
67 67 class JSRoutesMapper(Mapper):
68 68 """
69 69 Wrapper for routes.Mapper to make pyroutes compatible url definitions
70 70 """
71 71 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
72 72 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
73 73 def __init__(self, *args, **kw):
74 74 super(JSRoutesMapper, self).__init__(*args, **kw)
75 75 self._jsroutes = []
76 76
77 77 def connect(self, *args, **kw):
78 78 """
79 79 Wrapper for connect to take an extra argument jsroute=True
80 80
81 81 :param jsroute: boolean, if True will add the route to the pyroutes list
82 82 """
83 83 if kw.pop('jsroute', False):
84 84 if not self._named_route_regex.match(args[0]):
85 85 raise Exception('only named routes can be added to pyroutes')
86 86 self._jsroutes.append(args[0])
87 87
88 88 super(JSRoutesMapper, self).connect(*args, **kw)
89 89
90 90 def _extract_route_information(self, route):
91 91 """
92 92 Convert a route into tuple(name, path, args), eg:
93 93 ('show_user', '/profile/%(username)s', ['username'])
94 94 """
95 95 routepath = route.routepath
96 96 def replace(matchobj):
97 97 if matchobj.group(1):
98 98 return "%%(%s)s" % matchobj.group(1).split(':')[0]
99 99 else:
100 100 return "%%(%s)s" % matchobj.group(2)
101 101
102 102 routepath = self._argument_prog.sub(replace, routepath)
103 103 return (
104 104 route.name,
105 105 routepath,
106 106 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
107 107 for arg in self._argument_prog.findall(route.routepath)]
108 108 )
109 109
110 110 def jsroutes(self):
111 111 """
112 112 Return a list of pyroutes.js compatible routes
113 113 """
114 114 for route_name in self._jsroutes:
115 115 yield self._extract_route_information(self._routenames[route_name])
116 116
117 117
118 118 def make_map(config):
119 119 """Create, configure and return the routes Mapper"""
120 120 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
121 121 always_scan=config['debug'])
122 122 rmap.minimization = False
123 123 rmap.explicit = False
124 124
125 125 from rhodecode.lib.utils2 import str2bool
126 126 from rhodecode.model import repo, repo_group
127 127
128 128 def check_repo(environ, match_dict):
129 129 """
130 130 check for valid repository for proper 404 handling
131 131
132 132 :param environ:
133 133 :param match_dict:
134 134 """
135 135 repo_name = match_dict.get('repo_name')
136 136
137 137 if match_dict.get('f_path'):
138 138 # fix for multiple initial slashes that causes errors
139 139 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
140 140 repo_model = repo.RepoModel()
141 141 by_name_match = repo_model.get_by_repo_name(repo_name)
142 142 # if we match quickly from database, short circuit the operation,
143 143 # and validate repo based on the type.
144 144 if by_name_match:
145 145 return True
146 146
147 147 by_id_match = repo_model.get_repo_by_id(repo_name)
148 148 if by_id_match:
149 149 repo_name = by_id_match.repo_name
150 150 match_dict['repo_name'] = repo_name
151 151 return True
152 152
153 153 return False
154 154
155 155 def check_group(environ, match_dict):
156 156 """
157 157 check for valid repository group path for proper 404 handling
158 158
159 159 :param environ:
160 160 :param match_dict:
161 161 """
162 162 repo_group_name = match_dict.get('group_name')
163 163 repo_group_model = repo_group.RepoGroupModel()
164 164 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
165 165 if by_name_match:
166 166 return True
167 167
168 168 return False
169 169
170 170 def check_user_group(environ, match_dict):
171 171 """
172 172 check for valid user group for proper 404 handling
173 173
174 174 :param environ:
175 175 :param match_dict:
176 176 """
177 177 return True
178 178
179 179 def check_int(environ, match_dict):
180 180 return match_dict.get('id').isdigit()
181 181
182 182
183 183 #==========================================================================
184 184 # CUSTOM ROUTES HERE
185 185 #==========================================================================
186 186
187 187 # MAIN PAGE
188 188 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
189 189
190 190 # ping and pylons error test
191 191 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
192 192 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
193 193
194 194 # ADMIN REPOSITORY ROUTES
195 195 with rmap.submapper(path_prefix=ADMIN_PREFIX,
196 196 controller='admin/repos') as m:
197 197 m.connect('repos', '/repos',
198 198 action='create', conditions={'method': ['POST']})
199 199 m.connect('repos', '/repos',
200 200 action='index', conditions={'method': ['GET']})
201 201 m.connect('new_repo', '/create_repository', jsroute=True,
202 202 action='create_repository', conditions={'method': ['GET']})
203 203 m.connect('delete_repo', '/repos/{repo_name}',
204 204 action='delete', conditions={'method': ['DELETE']},
205 205 requirements=URL_NAME_REQUIREMENTS)
206 206 m.connect('repo', '/repos/{repo_name}',
207 207 action='show', conditions={'method': ['GET'],
208 208 'function': check_repo},
209 209 requirements=URL_NAME_REQUIREMENTS)
210 210
211 211 # ADMIN REPOSITORY GROUPS ROUTES
212 212 with rmap.submapper(path_prefix=ADMIN_PREFIX,
213 213 controller='admin/repo_groups') as m:
214 214 m.connect('repo_groups', '/repo_groups',
215 215 action='create', conditions={'method': ['POST']})
216 216 m.connect('repo_groups', '/repo_groups',
217 217 action='index', conditions={'method': ['GET']})
218 218 m.connect('new_repo_group', '/repo_groups/new',
219 219 action='new', conditions={'method': ['GET']})
220 220 m.connect('update_repo_group', '/repo_groups/{group_name}',
221 221 action='update', conditions={'method': ['PUT'],
222 222 'function': check_group},
223 223 requirements=URL_NAME_REQUIREMENTS)
224 224
225 225 # EXTRAS REPO GROUP ROUTES
226 226 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
227 227 action='edit',
228 228 conditions={'method': ['GET'], 'function': check_group},
229 229 requirements=URL_NAME_REQUIREMENTS)
230 230 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
231 231 action='edit',
232 232 conditions={'method': ['PUT'], 'function': check_group},
233 233 requirements=URL_NAME_REQUIREMENTS)
234 234
235 235 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
236 236 action='edit_repo_group_advanced',
237 237 conditions={'method': ['GET'], 'function': check_group},
238 238 requirements=URL_NAME_REQUIREMENTS)
239 239 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
240 240 action='edit_repo_group_advanced',
241 241 conditions={'method': ['PUT'], 'function': check_group},
242 242 requirements=URL_NAME_REQUIREMENTS)
243 243
244 244 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
245 245 action='edit_repo_group_perms',
246 246 conditions={'method': ['GET'], 'function': check_group},
247 247 requirements=URL_NAME_REQUIREMENTS)
248 248 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
249 249 action='update_perms',
250 250 conditions={'method': ['PUT'], 'function': check_group},
251 251 requirements=URL_NAME_REQUIREMENTS)
252 252
253 253 m.connect('delete_repo_group', '/repo_groups/{group_name}',
254 254 action='delete', conditions={'method': ['DELETE'],
255 255 'function': check_group},
256 256 requirements=URL_NAME_REQUIREMENTS)
257 257
258 258 # ADMIN USER ROUTES
259 259 with rmap.submapper(path_prefix=ADMIN_PREFIX,
260 260 controller='admin/users') as m:
261 261 m.connect('users', '/users',
262 262 action='create', conditions={'method': ['POST']})
263 263 m.connect('new_user', '/users/new',
264 264 action='new', conditions={'method': ['GET']})
265 265 m.connect('update_user', '/users/{user_id}',
266 266 action='update', conditions={'method': ['PUT']})
267 267 m.connect('delete_user', '/users/{user_id}',
268 268 action='delete', conditions={'method': ['DELETE']})
269 269 m.connect('edit_user', '/users/{user_id}/edit',
270 270 action='edit', conditions={'method': ['GET']}, jsroute=True)
271 271 m.connect('user', '/users/{user_id}',
272 272 action='show', conditions={'method': ['GET']})
273 273 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
274 274 action='reset_password', conditions={'method': ['POST']})
275 275 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
276 276 action='create_personal_repo_group', conditions={'method': ['POST']})
277 277
278 278 # EXTRAS USER ROUTES
279 279 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
280 280 action='edit_advanced', conditions={'method': ['GET']})
281 281 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
282 282 action='update_advanced', conditions={'method': ['PUT']})
283 283
284 284 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
285 285 action='edit_global_perms', conditions={'method': ['GET']})
286 286 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
287 287 action='update_global_perms', conditions={'method': ['PUT']})
288 288
289 289 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
290 290 action='edit_perms_summary', conditions={'method': ['GET']})
291 291
292 292 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
293 293 action='edit_emails', conditions={'method': ['GET']})
294 294 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
295 295 action='add_email', conditions={'method': ['PUT']})
296 296 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
297 297 action='delete_email', conditions={'method': ['DELETE']})
298 298
299 299 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
300 300 action='edit_ips', conditions={'method': ['GET']})
301 301 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
302 302 action='add_ip', conditions={'method': ['PUT']})
303 303 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
304 304 action='delete_ip', conditions={'method': ['DELETE']})
305 305
306 306 # ADMIN USER GROUPS REST ROUTES
307 307 with rmap.submapper(path_prefix=ADMIN_PREFIX,
308 308 controller='admin/user_groups') as m:
309 309 m.connect('users_groups', '/user_groups',
310 310 action='create', conditions={'method': ['POST']})
311 311 m.connect('users_groups', '/user_groups',
312 312 action='index', conditions={'method': ['GET']})
313 313 m.connect('new_users_group', '/user_groups/new',
314 314 action='new', conditions={'method': ['GET']})
315 315 m.connect('update_users_group', '/user_groups/{user_group_id}',
316 316 action='update', conditions={'method': ['PUT']})
317 317 m.connect('delete_users_group', '/user_groups/{user_group_id}',
318 318 action='delete', conditions={'method': ['DELETE']})
319 319 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
320 320 action='edit', conditions={'method': ['GET']},
321 321 function=check_user_group)
322 322
323 323 # EXTRAS USER GROUP ROUTES
324 324 m.connect('edit_user_group_global_perms',
325 325 '/user_groups/{user_group_id}/edit/global_permissions',
326 326 action='edit_global_perms', conditions={'method': ['GET']})
327 327 m.connect('edit_user_group_global_perms',
328 328 '/user_groups/{user_group_id}/edit/global_permissions',
329 329 action='update_global_perms', conditions={'method': ['PUT']})
330 330 m.connect('edit_user_group_perms_summary',
331 331 '/user_groups/{user_group_id}/edit/permissions_summary',
332 332 action='edit_perms_summary', conditions={'method': ['GET']})
333 333
334 334 m.connect('edit_user_group_perms',
335 335 '/user_groups/{user_group_id}/edit/permissions',
336 336 action='edit_perms', conditions={'method': ['GET']})
337 337 m.connect('edit_user_group_perms',
338 338 '/user_groups/{user_group_id}/edit/permissions',
339 339 action='update_perms', conditions={'method': ['PUT']})
340 340
341 341 m.connect('edit_user_group_advanced',
342 342 '/user_groups/{user_group_id}/edit/advanced',
343 343 action='edit_advanced', conditions={'method': ['GET']})
344 344
345 345 m.connect('edit_user_group_advanced_sync',
346 346 '/user_groups/{user_group_id}/edit/advanced/sync',
347 347 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
348 348
349 349 m.connect('edit_user_group_members',
350 350 '/user_groups/{user_group_id}/edit/members', jsroute=True,
351 351 action='user_group_members', conditions={'method': ['GET']})
352 352
353 353 # ADMIN PERMISSIONS ROUTES
354 354 with rmap.submapper(path_prefix=ADMIN_PREFIX,
355 355 controller='admin/permissions') as m:
356 356 m.connect('admin_permissions_application', '/permissions/application',
357 357 action='permission_application_update', conditions={'method': ['POST']})
358 358 m.connect('admin_permissions_application', '/permissions/application',
359 359 action='permission_application', conditions={'method': ['GET']})
360 360
361 361 m.connect('admin_permissions_global', '/permissions/global',
362 362 action='permission_global_update', conditions={'method': ['POST']})
363 363 m.connect('admin_permissions_global', '/permissions/global',
364 364 action='permission_global', conditions={'method': ['GET']})
365 365
366 366 m.connect('admin_permissions_object', '/permissions/object',
367 367 action='permission_objects_update', conditions={'method': ['POST']})
368 368 m.connect('admin_permissions_object', '/permissions/object',
369 369 action='permission_objects', conditions={'method': ['GET']})
370 370
371 371 m.connect('admin_permissions_ips', '/permissions/ips',
372 372 action='permission_ips', conditions={'method': ['POST']})
373 373 m.connect('admin_permissions_ips', '/permissions/ips',
374 374 action='permission_ips', conditions={'method': ['GET']})
375 375
376 376 m.connect('admin_permissions_overview', '/permissions/overview',
377 377 action='permission_perms', conditions={'method': ['GET']})
378 378
379 379 # ADMIN DEFAULTS REST ROUTES
380 380 with rmap.submapper(path_prefix=ADMIN_PREFIX,
381 381 controller='admin/defaults') as m:
382 382 m.connect('admin_defaults_repositories', '/defaults/repositories',
383 383 action='update_repository_defaults', conditions={'method': ['POST']})
384 384 m.connect('admin_defaults_repositories', '/defaults/repositories',
385 385 action='index', conditions={'method': ['GET']})
386 386
387 387 # ADMIN DEBUG STYLE ROUTES
388 388 if str2bool(config.get('debug_style')):
389 389 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
390 390 controller='debug_style') as m:
391 391 m.connect('debug_style_home', '',
392 392 action='index', conditions={'method': ['GET']})
393 393 m.connect('debug_style_template', '/t/{t_path}',
394 394 action='template', conditions={'method': ['GET']})
395 395
396 396 # ADMIN SETTINGS ROUTES
397 397 with rmap.submapper(path_prefix=ADMIN_PREFIX,
398 398 controller='admin/settings') as m:
399 399
400 400 # default
401 401 m.connect('admin_settings', '/settings',
402 402 action='settings_global_update',
403 403 conditions={'method': ['POST']})
404 404 m.connect('admin_settings', '/settings',
405 405 action='settings_global', conditions={'method': ['GET']})
406 406
407 407 m.connect('admin_settings_vcs', '/settings/vcs',
408 408 action='settings_vcs_update',
409 409 conditions={'method': ['POST']})
410 410 m.connect('admin_settings_vcs', '/settings/vcs',
411 411 action='settings_vcs',
412 412 conditions={'method': ['GET']})
413 413 m.connect('admin_settings_vcs', '/settings/vcs',
414 414 action='delete_svn_pattern',
415 415 conditions={'method': ['DELETE']})
416 416
417 417 m.connect('admin_settings_mapping', '/settings/mapping',
418 418 action='settings_mapping_update',
419 419 conditions={'method': ['POST']})
420 420 m.connect('admin_settings_mapping', '/settings/mapping',
421 421 action='settings_mapping', conditions={'method': ['GET']})
422 422
423 423 m.connect('admin_settings_global', '/settings/global',
424 424 action='settings_global_update',
425 425 conditions={'method': ['POST']})
426 426 m.connect('admin_settings_global', '/settings/global',
427 427 action='settings_global', conditions={'method': ['GET']})
428 428
429 429 m.connect('admin_settings_visual', '/settings/visual',
430 430 action='settings_visual_update',
431 431 conditions={'method': ['POST']})
432 432 m.connect('admin_settings_visual', '/settings/visual',
433 433 action='settings_visual', conditions={'method': ['GET']})
434 434
435 435 m.connect('admin_settings_issuetracker',
436 436 '/settings/issue-tracker', action='settings_issuetracker',
437 437 conditions={'method': ['GET']})
438 438 m.connect('admin_settings_issuetracker_save',
439 439 '/settings/issue-tracker/save',
440 440 action='settings_issuetracker_save',
441 441 conditions={'method': ['POST']})
442 442 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
443 443 action='settings_issuetracker_test',
444 444 conditions={'method': ['POST']})
445 445 m.connect('admin_issuetracker_delete',
446 446 '/settings/issue-tracker/delete',
447 447 action='settings_issuetracker_delete',
448 448 conditions={'method': ['DELETE']})
449 449
450 450 m.connect('admin_settings_email', '/settings/email',
451 451 action='settings_email_update',
452 452 conditions={'method': ['POST']})
453 453 m.connect('admin_settings_email', '/settings/email',
454 454 action='settings_email', conditions={'method': ['GET']})
455 455
456 456 m.connect('admin_settings_hooks', '/settings/hooks',
457 457 action='settings_hooks_update',
458 458 conditions={'method': ['POST', 'DELETE']})
459 459 m.connect('admin_settings_hooks', '/settings/hooks',
460 460 action='settings_hooks', conditions={'method': ['GET']})
461 461
462 462 m.connect('admin_settings_search', '/settings/search',
463 463 action='settings_search', conditions={'method': ['GET']})
464 464
465 465 m.connect('admin_settings_supervisor', '/settings/supervisor',
466 466 action='settings_supervisor', conditions={'method': ['GET']})
467 467 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
468 468 action='settings_supervisor_log', conditions={'method': ['GET']})
469 469
470 470 m.connect('admin_settings_labs', '/settings/labs',
471 471 action='settings_labs_update',
472 472 conditions={'method': ['POST']})
473 473 m.connect('admin_settings_labs', '/settings/labs',
474 474 action='settings_labs', conditions={'method': ['GET']})
475 475
476 476 # ADMIN MY ACCOUNT
477 477 with rmap.submapper(path_prefix=ADMIN_PREFIX,
478 478 controller='admin/my_account') as m:
479 479
480 480 m.connect('my_account_edit', '/my_account/edit',
481 481 action='my_account_edit', conditions={'method': ['GET']})
482 482 m.connect('my_account', '/my_account/update',
483 483 action='my_account_update', conditions={'method': ['POST']})
484 484
485 485 # NOTE(marcink): this needs to be kept for password force flag to be
486 486 # handler, remove after migration to pyramid
487 487 m.connect('my_account_password', '/my_account/password',
488 488 action='my_account_password', conditions={'method': ['GET']})
489 489
490 490 m.connect('my_account_repos', '/my_account/repos',
491 491 action='my_account_repos', conditions={'method': ['GET']})
492 492
493 493 m.connect('my_account_watched', '/my_account/watched',
494 494 action='my_account_watched', conditions={'method': ['GET']})
495 495
496 496 m.connect('my_account_pullrequests', '/my_account/pull_requests',
497 497 action='my_account_pullrequests', conditions={'method': ['GET']})
498 498
499 499 m.connect('my_account_perms', '/my_account/perms',
500 500 action='my_account_perms', conditions={'method': ['GET']})
501 501
502 502 m.connect('my_account_emails', '/my_account/emails',
503 503 action='my_account_emails', conditions={'method': ['GET']})
504 504 m.connect('my_account_emails', '/my_account/emails',
505 505 action='my_account_emails_add', conditions={'method': ['POST']})
506 506 m.connect('my_account_emails', '/my_account/emails',
507 507 action='my_account_emails_delete', conditions={'method': ['DELETE']})
508 508
509 509 m.connect('my_account_notifications', '/my_account/notifications',
510 510 action='my_notifications',
511 511 conditions={'method': ['GET']})
512 512 m.connect('my_account_notifications_toggle_visibility',
513 513 '/my_account/toggle_visibility',
514 514 action='my_notifications_toggle_visibility',
515 515 conditions={'method': ['POST']})
516 516 m.connect('my_account_notifications_test_channelstream',
517 517 '/my_account/test_channelstream',
518 518 action='my_account_notifications_test_channelstream',
519 519 conditions={'method': ['POST']})
520 520
521 521 # NOTIFICATION REST ROUTES
522 522 with rmap.submapper(path_prefix=ADMIN_PREFIX,
523 523 controller='admin/notifications') as m:
524 524 m.connect('notifications', '/notifications',
525 525 action='index', conditions={'method': ['GET']})
526 526 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
527 527 action='mark_all_read', conditions={'method': ['POST']})
528 528 m.connect('/notifications/{notification_id}',
529 529 action='update', conditions={'method': ['PUT']})
530 530 m.connect('/notifications/{notification_id}',
531 531 action='delete', conditions={'method': ['DELETE']})
532 532 m.connect('notification', '/notifications/{notification_id}',
533 533 action='show', conditions={'method': ['GET']})
534 534
535 535 # ADMIN GIST
536 536 with rmap.submapper(path_prefix=ADMIN_PREFIX,
537 537 controller='admin/gists') as m:
538 538 m.connect('gists', '/gists',
539 539 action='create', conditions={'method': ['POST']})
540 540 m.connect('gists', '/gists', jsroute=True,
541 541 action='index', conditions={'method': ['GET']})
542 542 m.connect('new_gist', '/gists/new', jsroute=True,
543 543 action='new', conditions={'method': ['GET']})
544 544
545 545 m.connect('/gists/{gist_id}',
546 546 action='delete', conditions={'method': ['DELETE']})
547 547 m.connect('edit_gist', '/gists/{gist_id}/edit',
548 548 action='edit_form', conditions={'method': ['GET']})
549 549 m.connect('edit_gist', '/gists/{gist_id}/edit',
550 550 action='edit', conditions={'method': ['POST']})
551 551 m.connect(
552 552 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
553 553 action='check_revision', conditions={'method': ['GET']})
554 554
555 555 m.connect('gist', '/gists/{gist_id}',
556 556 action='show', conditions={'method': ['GET']})
557 557 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
558 558 revision='tip',
559 559 action='show', conditions={'method': ['GET']})
560 560 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
561 561 revision='tip',
562 562 action='show', conditions={'method': ['GET']})
563 563 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
564 564 revision='tip',
565 565 action='show', conditions={'method': ['GET']},
566 566 requirements=URL_NAME_REQUIREMENTS)
567 567
568 568 # ADMIN MAIN PAGES
569 569 with rmap.submapper(path_prefix=ADMIN_PREFIX,
570 570 controller='admin/admin') as m:
571 571 m.connect('admin_home', '', action='index')
572 572 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
573 573 action='add_repo')
574 574 m.connect(
575 575 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
576 576 action='pull_requests')
577 577 m.connect(
578 578 'pull_requests_global_1', '/pull-requests/{pull_request_id:[0-9]+}',
579 579 action='pull_requests')
580 580 m.connect(
581 581 'pull_requests_global', '/pull-request/{pull_request_id:[0-9]+}',
582 582 action='pull_requests')
583 583
584 584 # USER JOURNAL
585 585 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
586 586 controller='journal', action='index')
587 587 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
588 588 controller='journal', action='journal_rss')
589 589 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
590 590 controller='journal', action='journal_atom')
591 591
592 592 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
593 593 controller='journal', action='public_journal')
594 594
595 595 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
596 596 controller='journal', action='public_journal_rss')
597 597
598 598 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
599 599 controller='journal', action='public_journal_rss')
600 600
601 601 rmap.connect('public_journal_atom',
602 602 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
603 603 action='public_journal_atom')
604 604
605 605 rmap.connect('public_journal_atom_old',
606 606 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
607 607 action='public_journal_atom')
608 608
609 609 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
610 610 controller='journal', action='toggle_following', jsroute=True,
611 611 conditions={'method': ['POST']})
612 612
613 613 # FEEDS
614 614 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
615 615 controller='feed', action='rss',
616 616 conditions={'function': check_repo},
617 617 requirements=URL_NAME_REQUIREMENTS)
618 618
619 619 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
620 620 controller='feed', action='atom',
621 621 conditions={'function': check_repo},
622 622 requirements=URL_NAME_REQUIREMENTS)
623 623
624 624 #==========================================================================
625 625 # REPOSITORY ROUTES
626 626 #==========================================================================
627 627
628 628 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
629 629 controller='admin/repos', action='repo_creating',
630 630 requirements=URL_NAME_REQUIREMENTS)
631 631 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
632 632 controller='admin/repos', action='repo_check',
633 633 requirements=URL_NAME_REQUIREMENTS)
634 634
635 635 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
636 636 controller='summary', action='repo_stats',
637 637 conditions={'function': check_repo},
638 638 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
639 639
640 640 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
641 641 controller='summary', action='repo_refs_data',
642 642 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
643 643 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
644 644 controller='summary', action='repo_refs_changelog_data',
645 645 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
646 646 rmap.connect('repo_default_reviewers_data', '/{repo_name}/default-reviewers',
647 647 controller='summary', action='repo_default_reviewers_data',
648 648 jsroute=True, requirements=URL_NAME_REQUIREMENTS)
649 649
650 650 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
651 651 controller='changeset', revision='tip',
652 652 conditions={'function': check_repo},
653 653 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
654 654 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
655 655 controller='changeset', revision='tip', action='changeset_children',
656 656 conditions={'function': check_repo},
657 657 requirements=URL_NAME_REQUIREMENTS)
658 658 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
659 659 controller='changeset', revision='tip', action='changeset_parents',
660 660 conditions={'function': check_repo},
661 661 requirements=URL_NAME_REQUIREMENTS)
662 662
663 663 # repo edit options
664 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
665 jsroute=True,
666 controller='admin/repos', action='edit_permissions',
667 conditions={'method': ['GET'], 'function': check_repo},
668 requirements=URL_NAME_REQUIREMENTS)
669 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
670 controller='admin/repos', action='edit_permissions_update',
671 conditions={'method': ['PUT'], 'function': check_repo},
672 requirements=URL_NAME_REQUIREMENTS)
673
674 664 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
675 665 controller='admin/repos', action='edit_fields',
676 666 conditions={'method': ['GET'], 'function': check_repo},
677 667 requirements=URL_NAME_REQUIREMENTS)
678 668 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
679 669 controller='admin/repos', action='create_repo_field',
680 670 conditions={'method': ['PUT'], 'function': check_repo},
681 671 requirements=URL_NAME_REQUIREMENTS)
682 672 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
683 673 controller='admin/repos', action='delete_repo_field',
684 674 conditions={'method': ['DELETE'], 'function': check_repo},
685 675 requirements=URL_NAME_REQUIREMENTS)
686 676
687 677 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
688 678 controller='admin/repos', action='edit_advanced',
689 679 conditions={'method': ['GET'], 'function': check_repo},
690 680 requirements=URL_NAME_REQUIREMENTS)
691 681
692 682 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
693 683 controller='admin/repos', action='edit_advanced_locking',
694 684 conditions={'method': ['PUT'], 'function': check_repo},
695 685 requirements=URL_NAME_REQUIREMENTS)
696 686 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
697 687 controller='admin/repos', action='toggle_locking',
698 688 conditions={'method': ['GET'], 'function': check_repo},
699 689 requirements=URL_NAME_REQUIREMENTS)
700 690
701 691 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
702 692 controller='admin/repos', action='edit_advanced_journal',
703 693 conditions={'method': ['PUT'], 'function': check_repo},
704 694 requirements=URL_NAME_REQUIREMENTS)
705 695
706 696 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
707 697 controller='admin/repos', action='edit_advanced_fork',
708 698 conditions={'method': ['PUT'], 'function': check_repo},
709 699 requirements=URL_NAME_REQUIREMENTS)
710 700
711 701 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
712 702 controller='admin/repos', action='edit_remote_form',
713 703 conditions={'method': ['GET'], 'function': check_repo},
714 704 requirements=URL_NAME_REQUIREMENTS)
715 705 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
716 706 controller='admin/repos', action='edit_remote',
717 707 conditions={'method': ['PUT'], 'function': check_repo},
718 708 requirements=URL_NAME_REQUIREMENTS)
719 709
720 710 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
721 711 controller='admin/repos', action='edit_statistics_form',
722 712 conditions={'method': ['GET'], 'function': check_repo},
723 713 requirements=URL_NAME_REQUIREMENTS)
724 714 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
725 715 controller='admin/repos', action='edit_statistics',
726 716 conditions={'method': ['PUT'], 'function': check_repo},
727 717 requirements=URL_NAME_REQUIREMENTS)
728 718 rmap.connect('repo_settings_issuetracker',
729 719 '/{repo_name}/settings/issue-tracker',
730 720 controller='admin/repos', action='repo_issuetracker',
731 721 conditions={'method': ['GET'], 'function': check_repo},
732 722 requirements=URL_NAME_REQUIREMENTS)
733 723 rmap.connect('repo_issuetracker_test',
734 724 '/{repo_name}/settings/issue-tracker/test',
735 725 controller='admin/repos', action='repo_issuetracker_test',
736 726 conditions={'method': ['POST'], 'function': check_repo},
737 727 requirements=URL_NAME_REQUIREMENTS)
738 728 rmap.connect('repo_issuetracker_delete',
739 729 '/{repo_name}/settings/issue-tracker/delete',
740 730 controller='admin/repos', action='repo_issuetracker_delete',
741 731 conditions={'method': ['DELETE'], 'function': check_repo},
742 732 requirements=URL_NAME_REQUIREMENTS)
743 733 rmap.connect('repo_issuetracker_save',
744 734 '/{repo_name}/settings/issue-tracker/save',
745 735 controller='admin/repos', action='repo_issuetracker_save',
746 736 conditions={'method': ['POST'], 'function': check_repo},
747 737 requirements=URL_NAME_REQUIREMENTS)
748 738 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
749 739 controller='admin/repos', action='repo_settings_vcs_update',
750 740 conditions={'method': ['POST'], 'function': check_repo},
751 741 requirements=URL_NAME_REQUIREMENTS)
752 742 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
753 743 controller='admin/repos', action='repo_settings_vcs',
754 744 conditions={'method': ['GET'], 'function': check_repo},
755 745 requirements=URL_NAME_REQUIREMENTS)
756 746 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
757 747 controller='admin/repos', action='repo_delete_svn_pattern',
758 748 conditions={'method': ['DELETE'], 'function': check_repo},
759 749 requirements=URL_NAME_REQUIREMENTS)
760 750 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
761 751 controller='admin/repos', action='repo_settings_pullrequest',
762 752 conditions={'method': ['GET', 'POST'], 'function': check_repo},
763 753 requirements=URL_NAME_REQUIREMENTS)
764 754
765 755 # still working url for backward compat.
766 756 rmap.connect('raw_changeset_home_depraced',
767 757 '/{repo_name}/raw-changeset/{revision}',
768 758 controller='changeset', action='changeset_raw',
769 759 revision='tip', conditions={'function': check_repo},
770 760 requirements=URL_NAME_REQUIREMENTS)
771 761
772 762 # new URLs
773 763 rmap.connect('changeset_raw_home',
774 764 '/{repo_name}/changeset-diff/{revision}',
775 765 controller='changeset', action='changeset_raw',
776 766 revision='tip', conditions={'function': check_repo},
777 767 requirements=URL_NAME_REQUIREMENTS)
778 768
779 769 rmap.connect('changeset_patch_home',
780 770 '/{repo_name}/changeset-patch/{revision}',
781 771 controller='changeset', action='changeset_patch',
782 772 revision='tip', conditions={'function': check_repo},
783 773 requirements=URL_NAME_REQUIREMENTS)
784 774
785 775 rmap.connect('changeset_download_home',
786 776 '/{repo_name}/changeset-download/{revision}',
787 777 controller='changeset', action='changeset_download',
788 778 revision='tip', conditions={'function': check_repo},
789 779 requirements=URL_NAME_REQUIREMENTS)
790 780
791 781 rmap.connect('changeset_comment',
792 782 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
793 783 controller='changeset', revision='tip', action='comment',
794 784 conditions={'function': check_repo},
795 785 requirements=URL_NAME_REQUIREMENTS)
796 786
797 787 rmap.connect('changeset_comment_preview',
798 788 '/{repo_name}/changeset/comment/preview', jsroute=True,
799 789 controller='changeset', action='preview_comment',
800 790 conditions={'function': check_repo, 'method': ['POST']},
801 791 requirements=URL_NAME_REQUIREMENTS)
802 792
803 793 rmap.connect('changeset_comment_delete',
804 794 '/{repo_name}/changeset/comment/{comment_id}/delete',
805 795 controller='changeset', action='delete_comment',
806 796 conditions={'function': check_repo, 'method': ['DELETE']},
807 797 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
808 798
809 799 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
810 800 controller='changeset', action='changeset_info',
811 801 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
812 802
813 803 rmap.connect('compare_home',
814 804 '/{repo_name}/compare',
815 805 controller='compare', action='index',
816 806 conditions={'function': check_repo},
817 807 requirements=URL_NAME_REQUIREMENTS)
818 808
819 809 rmap.connect('compare_url',
820 810 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
821 811 controller='compare', action='compare',
822 812 conditions={'function': check_repo},
823 813 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
824 814
825 815 rmap.connect('pullrequest_home',
826 816 '/{repo_name}/pull-request/new', controller='pullrequests',
827 817 action='index', conditions={'function': check_repo,
828 818 'method': ['GET']},
829 819 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
830 820
831 821 rmap.connect('pullrequest',
832 822 '/{repo_name}/pull-request/new', controller='pullrequests',
833 823 action='create', conditions={'function': check_repo,
834 824 'method': ['POST']},
835 825 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
836 826
837 827 rmap.connect('pullrequest_repo_refs',
838 828 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
839 829 controller='pullrequests',
840 830 action='get_repo_refs',
841 831 conditions={'function': check_repo, 'method': ['GET']},
842 832 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
843 833
844 834 rmap.connect('pullrequest_repo_destinations',
845 835 '/{repo_name}/pull-request/repo-destinations',
846 836 controller='pullrequests',
847 837 action='get_repo_destinations',
848 838 conditions={'function': check_repo, 'method': ['GET']},
849 839 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
850 840
851 841 rmap.connect('pullrequest_show',
852 842 '/{repo_name}/pull-request/{pull_request_id}',
853 843 controller='pullrequests',
854 844 action='show', conditions={'function': check_repo,
855 845 'method': ['GET']},
856 846 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
857 847
858 848 rmap.connect('pullrequest_update',
859 849 '/{repo_name}/pull-request/{pull_request_id}',
860 850 controller='pullrequests',
861 851 action='update', conditions={'function': check_repo,
862 852 'method': ['PUT']},
863 853 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
864 854
865 855 rmap.connect('pullrequest_merge',
866 856 '/{repo_name}/pull-request/{pull_request_id}',
867 857 controller='pullrequests',
868 858 action='merge', conditions={'function': check_repo,
869 859 'method': ['POST']},
870 860 requirements=URL_NAME_REQUIREMENTS)
871 861
872 862 rmap.connect('pullrequest_delete',
873 863 '/{repo_name}/pull-request/{pull_request_id}',
874 864 controller='pullrequests',
875 865 action='delete', conditions={'function': check_repo,
876 866 'method': ['DELETE']},
877 867 requirements=URL_NAME_REQUIREMENTS)
878 868
879 869 rmap.connect('pullrequest_show_all',
880 870 '/{repo_name}/pull-request',
881 871 controller='pullrequests',
882 872 action='show_all', conditions={'function': check_repo,
883 873 'method': ['GET']},
884 874 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
885 875
886 876 rmap.connect('pullrequest_comment',
887 877 '/{repo_name}/pull-request-comment/{pull_request_id}',
888 878 controller='pullrequests',
889 879 action='comment', conditions={'function': check_repo,
890 880 'method': ['POST']},
891 881 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
892 882
893 883 rmap.connect('pullrequest_comment_delete',
894 884 '/{repo_name}/pull-request-comment/{comment_id}/delete',
895 885 controller='pullrequests', action='delete_comment',
896 886 conditions={'function': check_repo, 'method': ['DELETE']},
897 887 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
898 888
899 889 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
900 890 controller='summary', conditions={'function': check_repo},
901 891 requirements=URL_NAME_REQUIREMENTS)
902 892
903 893 rmap.connect('branches_home', '/{repo_name}/branches',
904 894 controller='branches', conditions={'function': check_repo},
905 895 requirements=URL_NAME_REQUIREMENTS)
906 896
907 897 rmap.connect('tags_home', '/{repo_name}/tags',
908 898 controller='tags', conditions={'function': check_repo},
909 899 requirements=URL_NAME_REQUIREMENTS)
910 900
911 901 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
912 902 controller='bookmarks', conditions={'function': check_repo},
913 903 requirements=URL_NAME_REQUIREMENTS)
914 904
915 905 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
916 906 controller='changelog', conditions={'function': check_repo},
917 907 requirements=URL_NAME_REQUIREMENTS)
918 908
919 909 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
920 910 controller='changelog', action='changelog_summary',
921 911 conditions={'function': check_repo},
922 912 requirements=URL_NAME_REQUIREMENTS)
923 913
924 914 rmap.connect('changelog_file_home',
925 915 '/{repo_name}/changelog/{revision}/{f_path}',
926 916 controller='changelog', f_path=None,
927 917 conditions={'function': check_repo},
928 918 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
929 919
930 920 rmap.connect('changelog_elements', '/{repo_name}/changelog_details',
931 921 controller='changelog', action='changelog_elements',
932 922 conditions={'function': check_repo},
933 923 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
934 924
935 925 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
936 926 controller='files', revision='tip', f_path='',
937 927 conditions={'function': check_repo},
938 928 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
939 929
940 930 rmap.connect('files_home_simple_catchrev',
941 931 '/{repo_name}/files/{revision}',
942 932 controller='files', revision='tip', f_path='',
943 933 conditions={'function': check_repo},
944 934 requirements=URL_NAME_REQUIREMENTS)
945 935
946 936 rmap.connect('files_home_simple_catchall',
947 937 '/{repo_name}/files',
948 938 controller='files', revision='tip', f_path='',
949 939 conditions={'function': check_repo},
950 940 requirements=URL_NAME_REQUIREMENTS)
951 941
952 942 rmap.connect('files_history_home',
953 943 '/{repo_name}/history/{revision}/{f_path}',
954 944 controller='files', action='history', revision='tip', f_path='',
955 945 conditions={'function': check_repo},
956 946 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
957 947
958 948 rmap.connect('files_authors_home',
959 949 '/{repo_name}/authors/{revision}/{f_path}',
960 950 controller='files', action='authors', revision='tip', f_path='',
961 951 conditions={'function': check_repo},
962 952 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
963 953
964 954 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
965 955 controller='files', action='diff', f_path='',
966 956 conditions={'function': check_repo},
967 957 requirements=URL_NAME_REQUIREMENTS)
968 958
969 959 rmap.connect('files_diff_2way_home',
970 960 '/{repo_name}/diff-2way/{f_path}',
971 961 controller='files', action='diff_2way', f_path='',
972 962 conditions={'function': check_repo},
973 963 requirements=URL_NAME_REQUIREMENTS)
974 964
975 965 rmap.connect('files_rawfile_home',
976 966 '/{repo_name}/rawfile/{revision}/{f_path}',
977 967 controller='files', action='rawfile', revision='tip',
978 968 f_path='', conditions={'function': check_repo},
979 969 requirements=URL_NAME_REQUIREMENTS)
980 970
981 971 rmap.connect('files_raw_home',
982 972 '/{repo_name}/raw/{revision}/{f_path}',
983 973 controller='files', action='raw', revision='tip', f_path='',
984 974 conditions={'function': check_repo},
985 975 requirements=URL_NAME_REQUIREMENTS)
986 976
987 977 rmap.connect('files_render_home',
988 978 '/{repo_name}/render/{revision}/{f_path}',
989 979 controller='files', action='index', revision='tip', f_path='',
990 980 rendered=True, conditions={'function': check_repo},
991 981 requirements=URL_NAME_REQUIREMENTS)
992 982
993 983 rmap.connect('files_annotate_home',
994 984 '/{repo_name}/annotate/{revision}/{f_path}',
995 985 controller='files', action='index', revision='tip',
996 986 f_path='', annotate=True, conditions={'function': check_repo},
997 987 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
998 988
999 989 rmap.connect('files_annotate_previous',
1000 990 '/{repo_name}/annotate-previous/{revision}/{f_path}',
1001 991 controller='files', action='annotate_previous', revision='tip',
1002 992 f_path='', annotate=True, conditions={'function': check_repo},
1003 993 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1004 994
1005 995 rmap.connect('files_edit',
1006 996 '/{repo_name}/edit/{revision}/{f_path}',
1007 997 controller='files', action='edit', revision='tip',
1008 998 f_path='',
1009 999 conditions={'function': check_repo, 'method': ['POST']},
1010 1000 requirements=URL_NAME_REQUIREMENTS)
1011 1001
1012 1002 rmap.connect('files_edit_home',
1013 1003 '/{repo_name}/edit/{revision}/{f_path}',
1014 1004 controller='files', action='edit_home', revision='tip',
1015 1005 f_path='', conditions={'function': check_repo},
1016 1006 requirements=URL_NAME_REQUIREMENTS)
1017 1007
1018 1008 rmap.connect('files_add',
1019 1009 '/{repo_name}/add/{revision}/{f_path}',
1020 1010 controller='files', action='add', revision='tip',
1021 1011 f_path='',
1022 1012 conditions={'function': check_repo, 'method': ['POST']},
1023 1013 requirements=URL_NAME_REQUIREMENTS)
1024 1014
1025 1015 rmap.connect('files_add_home',
1026 1016 '/{repo_name}/add/{revision}/{f_path}',
1027 1017 controller='files', action='add_home', revision='tip',
1028 1018 f_path='', conditions={'function': check_repo},
1029 1019 requirements=URL_NAME_REQUIREMENTS)
1030 1020
1031 1021 rmap.connect('files_delete',
1032 1022 '/{repo_name}/delete/{revision}/{f_path}',
1033 1023 controller='files', action='delete', revision='tip',
1034 1024 f_path='',
1035 1025 conditions={'function': check_repo, 'method': ['POST']},
1036 1026 requirements=URL_NAME_REQUIREMENTS)
1037 1027
1038 1028 rmap.connect('files_delete_home',
1039 1029 '/{repo_name}/delete/{revision}/{f_path}',
1040 1030 controller='files', action='delete_home', revision='tip',
1041 1031 f_path='', conditions={'function': check_repo},
1042 1032 requirements=URL_NAME_REQUIREMENTS)
1043 1033
1044 1034 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1045 1035 controller='files', action='archivefile',
1046 1036 conditions={'function': check_repo},
1047 1037 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1048 1038
1049 1039 rmap.connect('files_nodelist_home',
1050 1040 '/{repo_name}/nodelist/{revision}/{f_path}',
1051 1041 controller='files', action='nodelist',
1052 1042 conditions={'function': check_repo},
1053 1043 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1054 1044
1055 1045 rmap.connect('files_nodetree_full',
1056 1046 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1057 1047 controller='files', action='nodetree_full',
1058 1048 conditions={'function': check_repo},
1059 1049 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1060 1050
1061 1051 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1062 1052 controller='forks', action='fork_create',
1063 1053 conditions={'function': check_repo, 'method': ['POST']},
1064 1054 requirements=URL_NAME_REQUIREMENTS)
1065 1055
1066 1056 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1067 1057 controller='forks', action='fork',
1068 1058 conditions={'function': check_repo},
1069 1059 requirements=URL_NAME_REQUIREMENTS)
1070 1060
1071 1061 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1072 1062 controller='forks', action='forks',
1073 1063 conditions={'function': check_repo},
1074 1064 requirements=URL_NAME_REQUIREMENTS)
1075 1065
1076 1066 # must be here for proper group/repo catching pattern
1077 1067 _connect_with_slash(
1078 1068 rmap, 'repo_group_home', '/{group_name}',
1079 1069 controller='home', action='index_repo_group',
1080 1070 conditions={'function': check_group},
1081 1071 requirements=URL_NAME_REQUIREMENTS)
1082 1072
1083 1073 # catch all, at the end
1084 1074 _connect_with_slash(
1085 1075 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1086 1076 controller='summary', action='index',
1087 1077 conditions={'function': check_repo},
1088 1078 requirements=URL_NAME_REQUIREMENTS)
1089 1079
1090 1080 return rmap
1091 1081
1092 1082
1093 1083 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1094 1084 """
1095 1085 Connect a route with an optional trailing slash in `path`.
1096 1086 """
1097 1087 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1098 1088 mapper.connect(name, path, *args, **kwargs)
@@ -1,781 +1,754 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2013-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 Repositories controller for RhodeCode
24 24 """
25 25
26 26 import logging
27 27 import traceback
28 28
29 29 import formencode
30 30 from formencode import htmlfill
31 31 from pylons import request, tmpl_context as c, url
32 32 from pylons.controllers.util import redirect
33 33 from pylons.i18n.translation import _
34 34 from webob.exc import HTTPForbidden, HTTPNotFound, HTTPBadRequest
35 35
36 36 import rhodecode
37 37 from rhodecode.lib import auth, helpers as h
38 38 from rhodecode.lib.auth import (
39 39 LoginRequired, HasPermissionAllDecorator,
40 40 HasRepoPermissionAllDecorator, NotAnonymous, HasPermissionAny,
41 41 HasRepoGroupPermissionAny, HasRepoPermissionAnyDecorator)
42 42 from rhodecode.lib.base import BaseRepoController, render
43 43 from rhodecode.lib.ext_json import json
44 44 from rhodecode.lib.exceptions import AttachedForksError
45 45 from rhodecode.lib.utils import action_logger, repo_name_slug, jsonify
46 46 from rhodecode.lib.utils2 import safe_int, str2bool
47 47 from rhodecode.lib.vcs import RepositoryError
48 48 from rhodecode.model.db import (
49 49 User, Repository, UserFollowing, RepoGroup, RepositoryField)
50 50 from rhodecode.model.forms import (
51 51 RepoForm, RepoFieldForm, RepoPermsForm, RepoVcsSettingsForm,
52 52 IssueTrackerPatternsForm)
53 53 from rhodecode.model.meta import Session
54 54 from rhodecode.model.repo import RepoModel
55 55 from rhodecode.model.scm import ScmModel, RepoGroupList, RepoList
56 56 from rhodecode.model.settings import (
57 57 SettingsModel, IssueTrackerSettingsModel, VcsSettingsModel,
58 58 SettingNotFound)
59 59
60 60 log = logging.getLogger(__name__)
61 61
62 62
63 63 class ReposController(BaseRepoController):
64 64 """
65 65 REST Controller styled on the Atom Publishing Protocol"""
66 66 # To properly map this controller, ensure your config/routing.py
67 67 # file has a resource setup:
68 68 # map.resource('repo', 'repos')
69 69
70 70 @LoginRequired()
71 71 def __before__(self):
72 72 super(ReposController, self).__before__()
73 73
74 74 def _load_repo(self, repo_name):
75 75 repo_obj = Repository.get_by_repo_name(repo_name)
76 76
77 77 if repo_obj is None:
78 78 h.not_mapped_error(repo_name)
79 79 return redirect(url('repos'))
80 80
81 81 return repo_obj
82 82
83 83 def __load_defaults(self, repo=None):
84 84 acl_groups = RepoGroupList(RepoGroup.query().all(),
85 85 perm_set=['group.write', 'group.admin'])
86 86 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
87 87 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
88 88
89 89 # in case someone no longer have a group.write access to a repository
90 90 # pre fill the list with this entry, we don't care if this is the same
91 91 # but it will allow saving repo data properly.
92 92
93 93 repo_group = None
94 94 if repo:
95 95 repo_group = repo.group
96 96 if repo_group and unicode(repo_group.group_id) not in c.repo_groups_choices:
97 97 c.repo_groups_choices.append(unicode(repo_group.group_id))
98 98 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
99 99
100 100 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
101 101 c.landing_revs_choices = choices
102 102
103 103 def __load_data(self, repo_name=None):
104 104 """
105 105 Load defaults settings for edit, and update
106 106
107 107 :param repo_name:
108 108 """
109 109 c.repo_info = self._load_repo(repo_name)
110 110 self.__load_defaults(c.repo_info)
111 111
112 112 # override defaults for exact repo info here git/hg etc
113 113 if not c.repository_requirements_missing:
114 114 choices, c.landing_revs = ScmModel().get_repo_landing_revs(
115 115 c.repo_info)
116 116 c.landing_revs_choices = choices
117 117 defaults = RepoModel()._get_defaults(repo_name)
118 118
119 119 return defaults
120 120
121 121 def _log_creation_exception(self, e, repo_name):
122 122 reason = None
123 123 if len(e.args) == 2:
124 124 reason = e.args[1]
125 125
126 126 if reason == 'INVALID_CERTIFICATE':
127 127 log.exception(
128 128 'Exception creating a repository: invalid certificate')
129 129 msg = (_('Error creating repository %s: invalid certificate')
130 130 % repo_name)
131 131 else:
132 132 log.exception("Exception creating a repository")
133 133 msg = (_('Error creating repository %s')
134 134 % repo_name)
135 135
136 136 return msg
137 137
138 138 @NotAnonymous()
139 139 def index(self, format='html'):
140 140 """GET /repos: All items in the collection"""
141 141 # url('repos')
142 142
143 143 repo_list = Repository.get_all_repos()
144 144 c.repo_list = RepoList(repo_list, perm_set=['repository.admin'])
145 145 repos_data = RepoModel().get_repos_as_dict(
146 146 repo_list=c.repo_list, admin=True, super_user_actions=True)
147 147 # json used to render the grid
148 148 c.data = json.dumps(repos_data)
149 149
150 150 return render('admin/repos/repos.mako')
151 151
152 152 # perms check inside
153 153 @NotAnonymous()
154 154 @auth.CSRFRequired()
155 155 def create(self):
156 156 """
157 157 POST /repos: Create a new item"""
158 158 # url('repos')
159 159
160 160 self.__load_defaults()
161 161 form_result = {}
162 162 task_id = None
163 163 c.personal_repo_group = c.rhodecode_user.personal_repo_group
164 164 try:
165 165 # CanWriteToGroup validators checks permissions of this POST
166 166 form_result = RepoForm(repo_groups=c.repo_groups_choices,
167 167 landing_revs=c.landing_revs_choices)()\
168 168 .to_python(dict(request.POST))
169 169
170 170 # create is done sometimes async on celery, db transaction
171 171 # management is handled there.
172 172 task = RepoModel().create(form_result, c.rhodecode_user.user_id)
173 173 from celery.result import BaseAsyncResult
174 174 if isinstance(task, BaseAsyncResult):
175 175 task_id = task.task_id
176 176 except formencode.Invalid as errors:
177 177 return htmlfill.render(
178 178 render('admin/repos/repo_add.mako'),
179 179 defaults=errors.value,
180 180 errors=errors.error_dict or {},
181 181 prefix_error=False,
182 182 encoding="UTF-8",
183 183 force_defaults=False)
184 184
185 185 except Exception as e:
186 186 msg = self._log_creation_exception(e, form_result.get('repo_name'))
187 187 h.flash(msg, category='error')
188 188 return redirect(url('home'))
189 189
190 190 return redirect(h.url('repo_creating_home',
191 191 repo_name=form_result['repo_name_full'],
192 192 task_id=task_id))
193 193
194 194 # perms check inside
195 195 @NotAnonymous()
196 196 def create_repository(self):
197 197 """GET /_admin/create_repository: Form to create a new item"""
198 198 new_repo = request.GET.get('repo', '')
199 199 parent_group = safe_int(request.GET.get('parent_group'))
200 200 _gr = RepoGroup.get(parent_group)
201 201
202 202 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
203 203 # you're not super admin nor have global create permissions,
204 204 # but maybe you have at least write permission to a parent group ?
205 205
206 206 gr_name = _gr.group_name if _gr else None
207 207 # create repositories with write permission on group is set to true
208 208 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
209 209 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
210 210 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
211 211 if not (group_admin or (group_write and create_on_write)):
212 212 raise HTTPForbidden
213 213
214 214 acl_groups = RepoGroupList(RepoGroup.query().all(),
215 215 perm_set=['group.write', 'group.admin'])
216 216 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
217 217 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
218 218 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
219 219 c.personal_repo_group = c.rhodecode_user.personal_repo_group
220 220 c.new_repo = repo_name_slug(new_repo)
221 221
222 222 # apply the defaults from defaults page
223 223 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
224 224 # set checkbox to autochecked
225 225 defaults['repo_copy_permissions'] = True
226 226
227 227 parent_group_choice = '-1'
228 228 if not c.rhodecode_user.is_admin and c.rhodecode_user.personal_repo_group:
229 229 parent_group_choice = c.rhodecode_user.personal_repo_group
230 230
231 231 if parent_group and _gr:
232 232 if parent_group in [x[0] for x in c.repo_groups]:
233 233 parent_group_choice = unicode(parent_group)
234 234
235 235 defaults.update({'repo_group': parent_group_choice})
236 236
237 237 return htmlfill.render(
238 238 render('admin/repos/repo_add.mako'),
239 239 defaults=defaults,
240 240 errors={},
241 241 prefix_error=False,
242 242 encoding="UTF-8",
243 243 force_defaults=False
244 244 )
245 245
246 246 @NotAnonymous()
247 247 def repo_creating(self, repo_name):
248 248 c.repo = repo_name
249 249 c.task_id = request.GET.get('task_id')
250 250 if not c.repo:
251 251 raise HTTPNotFound()
252 252 return render('admin/repos/repo_creating.mako')
253 253
254 254 @NotAnonymous()
255 255 @jsonify
256 256 def repo_check(self, repo_name):
257 257 c.repo = repo_name
258 258 task_id = request.GET.get('task_id')
259 259
260 260 if task_id and task_id not in ['None']:
261 261 import rhodecode
262 262 from celery.result import AsyncResult
263 263 if rhodecode.CELERY_ENABLED:
264 264 task = AsyncResult(task_id)
265 265 if task.failed():
266 266 msg = self._log_creation_exception(task.result, c.repo)
267 267 h.flash(msg, category='error')
268 268 return redirect(url('home'), code=501)
269 269
270 270 repo = Repository.get_by_repo_name(repo_name)
271 271 if repo and repo.repo_state == Repository.STATE_CREATED:
272 272 if repo.clone_uri:
273 273 clone_uri = repo.clone_uri_hidden
274 274 h.flash(_('Created repository %s from %s')
275 275 % (repo.repo_name, clone_uri), category='success')
276 276 else:
277 277 repo_url = h.link_to(repo.repo_name,
278 278 h.url('summary_home',
279 279 repo_name=repo.repo_name))
280 280 fork = repo.fork
281 281 if fork:
282 282 fork_name = fork.repo_name
283 283 h.flash(h.literal(_('Forked repository %s as %s')
284 284 % (fork_name, repo_url)), category='success')
285 285 else:
286 286 h.flash(h.literal(_('Created repository %s') % repo_url),
287 287 category='success')
288 288 return {'result': True}
289 289 return {'result': False}
290 290
291 291 @HasRepoPermissionAllDecorator('repository.admin')
292 292 @auth.CSRFRequired()
293 293 def delete(self, repo_name):
294 294 """
295 295 DELETE /repos/repo_name: Delete an existing item"""
296 296 # Forms posted to this method should contain a hidden field:
297 297 # <input type="hidden" name="_method" value="DELETE" />
298 298 # Or using helpers:
299 299 # h.form(url('repo', repo_name=ID),
300 300 # method='delete')
301 301 # url('repo', repo_name=ID)
302 302
303 303 repo_model = RepoModel()
304 304 repo = repo_model.get_by_repo_name(repo_name)
305 305 if not repo:
306 306 h.not_mapped_error(repo_name)
307 307 return redirect(url('repos'))
308 308 try:
309 309 _forks = repo.forks.count()
310 310 handle_forks = None
311 311 if _forks and request.POST.get('forks'):
312 312 do = request.POST['forks']
313 313 if do == 'detach_forks':
314 314 handle_forks = 'detach'
315 315 h.flash(_('Detached %s forks') % _forks, category='success')
316 316 elif do == 'delete_forks':
317 317 handle_forks = 'delete'
318 318 h.flash(_('Deleted %s forks') % _forks, category='success')
319 319 repo_model.delete(repo, forks=handle_forks)
320 320 action_logger(c.rhodecode_user, 'admin_deleted_repo',
321 321 repo_name, self.ip_addr, self.sa)
322 322 ScmModel().mark_for_invalidation(repo_name)
323 323 h.flash(_('Deleted repository %s') % repo_name, category='success')
324 324 Session().commit()
325 325 except AttachedForksError:
326 326 h.flash(_('Cannot delete %s it still contains attached forks')
327 327 % repo_name, category='warning')
328 328
329 329 except Exception:
330 330 log.exception("Exception during deletion of repository")
331 331 h.flash(_('An error occurred during deletion of %s') % repo_name,
332 332 category='error')
333 333
334 334 return redirect(url('repos'))
335 335
336 336 @HasPermissionAllDecorator('hg.admin')
337 337 def show(self, repo_name, format='html'):
338 338 """GET /repos/repo_name: Show a specific item"""
339 339 # url('repo', repo_name=ID)
340 340
341 341 @HasRepoPermissionAllDecorator('repository.admin')
342 def edit_permissions(self, repo_name):
343 """GET /repo_name/settings: Form to edit an existing item"""
344 c.repo_info = self._load_repo(repo_name)
345 c.active = 'permissions'
346 defaults = RepoModel()._get_defaults(repo_name)
347
348 return htmlfill.render(
349 render('admin/repos/repo_edit.mako'),
350 defaults=defaults,
351 encoding="UTF-8",
352 force_defaults=False)
353
354 @HasRepoPermissionAllDecorator('repository.admin')
355 @auth.CSRFRequired()
356 def edit_permissions_update(self, repo_name):
357 form = RepoPermsForm()().to_python(request.POST)
358 RepoModel().update_permissions(repo_name,
359 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
360
361 #TODO: implement this
362 #action_logger(c.rhodecode_user, 'admin_changed_repo_permissions',
363 # repo_name, self.ip_addr, self.sa)
364 Session().commit()
365 h.flash(_('Repository permissions updated'), category='success')
366 return redirect(url('edit_repo_perms', repo_name=repo_name))
367
368 @HasRepoPermissionAllDecorator('repository.admin')
369 342 def edit_fields(self, repo_name):
370 343 """GET /repo_name/settings: Form to edit an existing item"""
371 344 c.repo_info = self._load_repo(repo_name)
372 345 c.repo_fields = RepositoryField.query()\
373 346 .filter(RepositoryField.repository == c.repo_info).all()
374 347 c.active = 'fields'
375 348 if request.POST:
376 349
377 350 return redirect(url('repo_edit_fields'))
378 351 return render('admin/repos/repo_edit.mako')
379 352
380 353 @HasRepoPermissionAllDecorator('repository.admin')
381 354 @auth.CSRFRequired()
382 355 def create_repo_field(self, repo_name):
383 356 try:
384 357 form_result = RepoFieldForm()().to_python(dict(request.POST))
385 358 RepoModel().add_repo_field(
386 359 repo_name, form_result['new_field_key'],
387 360 field_type=form_result['new_field_type'],
388 361 field_value=form_result['new_field_value'],
389 362 field_label=form_result['new_field_label'],
390 363 field_desc=form_result['new_field_desc'])
391 364
392 365 Session().commit()
393 366 except Exception as e:
394 367 log.exception("Exception creating field")
395 368 msg = _('An error occurred during creation of field')
396 369 if isinstance(e, formencode.Invalid):
397 370 msg += ". " + e.msg
398 371 h.flash(msg, category='error')
399 372 return redirect(url('edit_repo_fields', repo_name=repo_name))
400 373
401 374 @HasRepoPermissionAllDecorator('repository.admin')
402 375 @auth.CSRFRequired()
403 376 def delete_repo_field(self, repo_name, field_id):
404 377 field = RepositoryField.get_or_404(field_id)
405 378 try:
406 379 RepoModel().delete_repo_field(repo_name, field.field_key)
407 380 Session().commit()
408 381 except Exception as e:
409 382 log.exception("Exception during removal of field")
410 383 msg = _('An error occurred during removal of field')
411 384 h.flash(msg, category='error')
412 385 return redirect(url('edit_repo_fields', repo_name=repo_name))
413 386
414 387 @HasRepoPermissionAllDecorator('repository.admin')
415 388 def edit_advanced(self, repo_name):
416 389 """GET /repo_name/settings: Form to edit an existing item"""
417 390 c.repo_info = self._load_repo(repo_name)
418 391 c.default_user_id = User.get_default_user().user_id
419 392 c.in_public_journal = UserFollowing.query()\
420 393 .filter(UserFollowing.user_id == c.default_user_id)\
421 394 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
422 395
423 396 c.active = 'advanced'
424 397 c.has_origin_repo_read_perm = False
425 398 if c.repo_info.fork:
426 399 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
427 400 'repository.write', 'repository.read', 'repository.admin')(
428 401 c.repo_info.fork.repo_name, 'repo set as fork page')
429 402
430 403 if request.POST:
431 404 return redirect(url('repo_edit_advanced'))
432 405 return render('admin/repos/repo_edit.mako')
433 406
434 407 @HasRepoPermissionAllDecorator('repository.admin')
435 408 @auth.CSRFRequired()
436 409 def edit_advanced_journal(self, repo_name):
437 410 """
438 411 Set's this repository to be visible in public journal,
439 412 in other words assing default user to follow this repo
440 413
441 414 :param repo_name:
442 415 """
443 416
444 417 try:
445 418 repo_id = Repository.get_by_repo_name(repo_name).repo_id
446 419 user_id = User.get_default_user().user_id
447 420 self.scm_model.toggle_following_repo(repo_id, user_id)
448 421 h.flash(_('Updated repository visibility in public journal'),
449 422 category='success')
450 423 Session().commit()
451 424 except Exception:
452 425 h.flash(_('An error occurred during setting this'
453 426 ' repository in public journal'),
454 427 category='error')
455 428
456 429 return redirect(url('edit_repo_advanced', repo_name=repo_name))
457 430
458 431 @HasRepoPermissionAllDecorator('repository.admin')
459 432 @auth.CSRFRequired()
460 433 def edit_advanced_fork(self, repo_name):
461 434 """
462 435 Mark given repository as a fork of another
463 436
464 437 :param repo_name:
465 438 """
466 439
467 440 new_fork_id = request.POST.get('id_fork_of')
468 441 try:
469 442
470 443 if new_fork_id and not new_fork_id.isdigit():
471 444 log.error('Given fork id %s is not an INT', new_fork_id)
472 445
473 446 fork_id = safe_int(new_fork_id)
474 447 repo = ScmModel().mark_as_fork(repo_name, fork_id,
475 448 c.rhodecode_user.username)
476 449 fork = repo.fork.repo_name if repo.fork else _('Nothing')
477 450 Session().commit()
478 451 h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
479 452 category='success')
480 453 except RepositoryError as e:
481 454 log.exception("Repository Error occurred")
482 455 h.flash(str(e), category='error')
483 456 except Exception as e:
484 457 log.exception("Exception while editing fork")
485 458 h.flash(_('An error occurred during this operation'),
486 459 category='error')
487 460
488 461 return redirect(url('edit_repo_advanced', repo_name=repo_name))
489 462
490 463 @HasRepoPermissionAllDecorator('repository.admin')
491 464 @auth.CSRFRequired()
492 465 def edit_advanced_locking(self, repo_name):
493 466 """
494 467 Unlock repository when it is locked !
495 468
496 469 :param repo_name:
497 470 """
498 471 try:
499 472 repo = Repository.get_by_repo_name(repo_name)
500 473 if request.POST.get('set_lock'):
501 474 Repository.lock(repo, c.rhodecode_user.user_id,
502 475 lock_reason=Repository.LOCK_WEB)
503 476 h.flash(_('Locked repository'), category='success')
504 477 elif request.POST.get('set_unlock'):
505 478 Repository.unlock(repo)
506 479 h.flash(_('Unlocked repository'), category='success')
507 480 except Exception as e:
508 481 log.exception("Exception during unlocking")
509 482 h.flash(_('An error occurred during unlocking'),
510 483 category='error')
511 484 return redirect(url('edit_repo_advanced', repo_name=repo_name))
512 485
513 486 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
514 487 @auth.CSRFRequired()
515 488 def toggle_locking(self, repo_name):
516 489 """
517 490 Toggle locking of repository by simple GET call to url
518 491
519 492 :param repo_name:
520 493 """
521 494
522 495 try:
523 496 repo = Repository.get_by_repo_name(repo_name)
524 497
525 498 if repo.enable_locking:
526 499 if repo.locked[0]:
527 500 Repository.unlock(repo)
528 501 action = _('Unlocked')
529 502 else:
530 503 Repository.lock(repo, c.rhodecode_user.user_id,
531 504 lock_reason=Repository.LOCK_WEB)
532 505 action = _('Locked')
533 506
534 507 h.flash(_('Repository has been %s') % action,
535 508 category='success')
536 509 except Exception:
537 510 log.exception("Exception during unlocking")
538 511 h.flash(_('An error occurred during unlocking'),
539 512 category='error')
540 513 return redirect(url('summary_home', repo_name=repo_name))
541 514
542 515 @HasRepoPermissionAllDecorator('repository.admin')
543 516 @auth.CSRFRequired()
544 517 def edit_remote(self, repo_name):
545 518 """PUT /{repo_name}/settings/remote: edit the repo remote."""
546 519 try:
547 520 ScmModel().pull_changes(repo_name, c.rhodecode_user.username)
548 521 h.flash(_('Pulled from remote location'), category='success')
549 522 except Exception:
550 523 log.exception("Exception during pull from remote")
551 524 h.flash(_('An error occurred during pull from remote location'),
552 525 category='error')
553 526 return redirect(url('edit_repo_remote', repo_name=c.repo_name))
554 527
555 528 @HasRepoPermissionAllDecorator('repository.admin')
556 529 def edit_remote_form(self, repo_name):
557 530 """GET /repo_name/settings: Form to edit an existing item"""
558 531 c.repo_info = self._load_repo(repo_name)
559 532 c.active = 'remote'
560 533
561 534 return render('admin/repos/repo_edit.mako')
562 535
563 536 @HasRepoPermissionAllDecorator('repository.admin')
564 537 @auth.CSRFRequired()
565 538 def edit_statistics(self, repo_name):
566 539 """PUT /{repo_name}/settings/statistics: reset the repo statistics."""
567 540 try:
568 541 RepoModel().delete_stats(repo_name)
569 542 Session().commit()
570 543 except Exception as e:
571 544 log.error(traceback.format_exc())
572 545 h.flash(_('An error occurred during deletion of repository stats'),
573 546 category='error')
574 547 return redirect(url('edit_repo_statistics', repo_name=c.repo_name))
575 548
576 549 @HasRepoPermissionAllDecorator('repository.admin')
577 550 def edit_statistics_form(self, repo_name):
578 551 """GET /repo_name/settings: Form to edit an existing item"""
579 552 c.repo_info = self._load_repo(repo_name)
580 553 repo = c.repo_info.scm_instance()
581 554
582 555 if c.repo_info.stats:
583 556 # this is on what revision we ended up so we add +1 for count
584 557 last_rev = c.repo_info.stats.stat_on_revision + 1
585 558 else:
586 559 last_rev = 0
587 560 c.stats_revision = last_rev
588 561
589 562 c.repo_last_rev = repo.count()
590 563
591 564 if last_rev == 0 or c.repo_last_rev == 0:
592 565 c.stats_percentage = 0
593 566 else:
594 567 c.stats_percentage = '%.2f' % ((float((last_rev)) / c.repo_last_rev) * 100)
595 568
596 569 c.active = 'statistics'
597 570
598 571 return render('admin/repos/repo_edit.mako')
599 572
600 573 @HasRepoPermissionAllDecorator('repository.admin')
601 574 @auth.CSRFRequired()
602 575 def repo_issuetracker_test(self, repo_name):
603 576 if request.is_xhr:
604 577 return h.urlify_commit_message(
605 578 request.POST.get('test_text', ''),
606 579 repo_name)
607 580 else:
608 581 raise HTTPBadRequest()
609 582
610 583 @HasRepoPermissionAllDecorator('repository.admin')
611 584 @auth.CSRFRequired()
612 585 def repo_issuetracker_delete(self, repo_name):
613 586 uid = request.POST.get('uid')
614 587 repo_settings = IssueTrackerSettingsModel(repo=repo_name)
615 588 try:
616 589 repo_settings.delete_entries(uid)
617 590 except Exception:
618 591 h.flash(_('Error occurred during deleting issue tracker entry'),
619 592 category='error')
620 593 else:
621 594 h.flash(_('Removed issue tracker entry'), category='success')
622 595 return redirect(url('repo_settings_issuetracker',
623 596 repo_name=repo_name))
624 597
625 598 def _update_patterns(self, form, repo_settings):
626 599 for uid in form['delete_patterns']:
627 600 repo_settings.delete_entries(uid)
628 601
629 602 for pattern in form['patterns']:
630 603 for setting, value, type_ in pattern:
631 604 sett = repo_settings.create_or_update_setting(
632 605 setting, value, type_)
633 606 Session().add(sett)
634 607
635 608 Session().commit()
636 609
637 610 @HasRepoPermissionAllDecorator('repository.admin')
638 611 @auth.CSRFRequired()
639 612 def repo_issuetracker_save(self, repo_name):
640 613 # Save inheritance
641 614 repo_settings = IssueTrackerSettingsModel(repo=repo_name)
642 615 inherited = (request.POST.get('inherit_global_issuetracker')
643 616 == "inherited")
644 617 repo_settings.inherit_global_settings = inherited
645 618 Session().commit()
646 619
647 620 form = IssueTrackerPatternsForm()().to_python(request.POST)
648 621 if form:
649 622 self._update_patterns(form, repo_settings)
650 623
651 624 h.flash(_('Updated issue tracker entries'), category='success')
652 625 return redirect(url('repo_settings_issuetracker',
653 626 repo_name=repo_name))
654 627
655 628 @HasRepoPermissionAllDecorator('repository.admin')
656 629 def repo_issuetracker(self, repo_name):
657 630 """GET /admin/settings/issue-tracker: All items in the collection"""
658 631 c.active = 'issuetracker'
659 632 c.data = 'data'
660 633 c.repo_info = self._load_repo(repo_name)
661 634
662 635 repo = Repository.get_by_repo_name(repo_name)
663 636 c.settings_model = IssueTrackerSettingsModel(repo=repo)
664 637 c.global_patterns = c.settings_model.get_global_settings()
665 638 c.repo_patterns = c.settings_model.get_repo_settings()
666 639
667 640 return render('admin/repos/repo_edit.mako')
668 641
669 642 @HasRepoPermissionAllDecorator('repository.admin')
670 643 def repo_settings_vcs(self, repo_name):
671 644 """GET /{repo_name}/settings/vcs/: All items in the collection"""
672 645
673 646 model = VcsSettingsModel(repo=repo_name)
674 647
675 648 c.active = 'vcs'
676 649 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
677 650 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
678 651 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
679 652 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
680 653 c.repo_info = self._load_repo(repo_name)
681 654 defaults = self._vcs_form_defaults(repo_name)
682 655 c.inherit_global_settings = defaults['inherit_global_settings']
683 656 c.labs_active = str2bool(
684 657 rhodecode.CONFIG.get('labs_settings_active', 'true'))
685 658
686 659 return htmlfill.render(
687 660 render('admin/repos/repo_edit.mako'),
688 661 defaults=defaults,
689 662 encoding="UTF-8",
690 663 force_defaults=False)
691 664
692 665 @HasRepoPermissionAllDecorator('repository.admin')
693 666 @auth.CSRFRequired()
694 667 def repo_settings_vcs_update(self, repo_name):
695 668 """POST /{repo_name}/settings/vcs/: All items in the collection"""
696 669 c.active = 'vcs'
697 670
698 671 model = VcsSettingsModel(repo=repo_name)
699 672 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
700 673 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
701 674 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
702 675 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
703 676 c.repo_info = self._load_repo(repo_name)
704 677 defaults = self._vcs_form_defaults(repo_name)
705 678 c.inherit_global_settings = defaults['inherit_global_settings']
706 679
707 680 application_form = RepoVcsSettingsForm(repo_name)()
708 681 try:
709 682 form_result = application_form.to_python(dict(request.POST))
710 683 except formencode.Invalid as errors:
711 684 h.flash(
712 685 _("Some form inputs contain invalid data."),
713 686 category='error')
714 687 return htmlfill.render(
715 688 render('admin/repos/repo_edit.mako'),
716 689 defaults=errors.value,
717 690 errors=errors.error_dict or {},
718 691 prefix_error=False,
719 692 encoding="UTF-8",
720 693 force_defaults=False
721 694 )
722 695
723 696 try:
724 697 inherit_global_settings = form_result['inherit_global_settings']
725 698 model.create_or_update_repo_settings(
726 699 form_result, inherit_global_settings=inherit_global_settings)
727 700 except Exception:
728 701 log.exception("Exception while updating settings")
729 702 h.flash(
730 703 _('Error occurred during updating repository VCS settings'),
731 704 category='error')
732 705 else:
733 706 Session().commit()
734 707 h.flash(_('Updated VCS settings'), category='success')
735 708 return redirect(url('repo_vcs_settings', repo_name=repo_name))
736 709
737 710 return htmlfill.render(
738 711 render('admin/repos/repo_edit.mako'),
739 712 defaults=self._vcs_form_defaults(repo_name),
740 713 encoding="UTF-8",
741 714 force_defaults=False)
742 715
743 716 @HasRepoPermissionAllDecorator('repository.admin')
744 717 @auth.CSRFRequired()
745 718 @jsonify
746 719 def repo_delete_svn_pattern(self, repo_name):
747 720 if not request.is_xhr:
748 721 return False
749 722
750 723 delete_pattern_id = request.POST.get('delete_svn_pattern')
751 724 model = VcsSettingsModel(repo=repo_name)
752 725 try:
753 726 model.delete_repo_svn_pattern(delete_pattern_id)
754 727 except SettingNotFound:
755 728 raise HTTPBadRequest()
756 729
757 730 Session().commit()
758 731 return True
759 732
760 733 def _vcs_form_defaults(self, repo_name):
761 734 model = VcsSettingsModel(repo=repo_name)
762 735 global_defaults = model.get_global_settings()
763 736
764 737 repo_defaults = {}
765 738 repo_defaults.update(global_defaults)
766 739 repo_defaults.update(model.get_repo_settings())
767 740
768 741 global_defaults = {
769 742 '{}_inherited'.format(k): global_defaults[k]
770 743 for k in global_defaults}
771 744
772 745 defaults = {
773 746 'inherit_global_settings': model.inherit_global_settings
774 747 }
775 748 defaults.update(global_defaults)
776 749 defaults.update(repo_defaults)
777 750 defaults.update({
778 751 'new_svn_branch': '',
779 752 'new_svn_tag': '',
780 753 })
781 754 return defaults
@@ -1,147 +1,148 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2017-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import datetime
23 23
24 24 from rhodecode.model import meta
25 25 from rhodecode.model.db import User, UserLog
26 26
27 27
28 28 log = logging.getLogger(__name__)
29 29
30 30
31 31 ACTIONS = {
32 32 'user.login.success': {},
33 33 'user.login.failure': {},
34 34 'user.logout': {},
35 35 'user.password.reset_request': {},
36 36
37 37 'repo.add': {},
38 38 'repo.edit': {},
39 'repo.edit.permissions': {},
39 40 'repo.commit.strip': {}
40 41 }
41 42
42 43
43 44 class UserWrap(object):
44 45 """
45 46 Fake object used to imitate AuthUser
46 47 """
47 48
48 49 def __init__(self, user_id=None, username=None, ip_addr=None):
49 50 self.user_id = user_id
50 51 self.username = username
51 52 self.ip_addr = ip_addr
52 53
53 54
54 55 def _store_log(action_name, action_data, user_id, username, user_data,
55 56 ip_address, repository_id, repository_name):
56 57 user_log = UserLog()
57 58 user_log.version = UserLog.VERSION_2
58 59
59 60 user_log.action = action_name
60 61 user_log.action_data = action_data
61 62
62 63 user_log.user_ip = ip_address
63 64
64 65 user_log.user_id = user_id
65 66 user_log.username = username
66 67 user_log.user_data = user_data
67 68
68 69 user_log.repository_id = repository_id
69 70 user_log.repository_name = repository_name
70 71
71 72 user_log.action_date = datetime.datetime.now()
72 73
73 74 log.info('AUDIT: Logging action: `%s` by user:id:%s[%s] ip:%s',
74 75 action_name, user_id, username, ip_address)
75 76
76 77 return user_log
77 78
78 79
79 80 def store(
80 81 action, user, action_data=None, user_data=None, ip_addr=None,
81 82 repo=None, sa_session=None, commit=False):
82 83 """
83 84 Audit logger for various actions made by users, typically this results in a call such::
84 85
85 86 from rhodecode.lib import audit_logger
86 87
87 88 audit_logger.store(action='repo.edit', user=self._rhodecode_user)
88 89 audit_logger.store(action='repo.delete', user=audit_logger.UserWrap(username='itried-to-login', ip_addr='8.8.8.8'))
89 90
90 91 # without an user ?
91 92 audit_user = audit_logger.UserWrap(
92 93 username=self.request.params.get('username'),
93 94 ip_addr=self.request.remote_addr)
94 95 audit_logger.store(action='user.login.failure', user=audit_user)
95 96 """
96 97 from rhodecode.lib.utils2 import safe_unicode
97 98 from rhodecode.lib.auth import AuthUser
98 99
99 100 if action not in ACTIONS:
100 101 raise ValueError('Action `{}` not in valid actions'.format(action))
101 102
102 103 if not sa_session:
103 104 sa_session = meta.Session()
104 105
105 106 try:
106 107 username = getattr(user, 'username', None)
107 108 if not username:
108 109 pass
109 110
110 111 user_id = getattr(user, 'user_id', None)
111 112 if not user_id:
112 113 # maybe we have username ? Try to figure user_id from username
113 114 if username:
114 115 user_id = getattr(
115 116 User.get_by_username(username), 'user_id', None)
116 117
117 118 ip_addr = ip_addr or getattr(user, 'ip_addr', None)
118 119 if not ip_addr:
119 120 pass
120 121
121 122 if not user_data:
122 123 # try to get this from the auth user
123 124 if isinstance(user, AuthUser):
124 125 user_data = {
125 126 'username': user.username,
126 127 'email': user.email,
127 128 }
128 129
129 130 repository_id = getattr(repo, 'repo_id', None)
130 131 repository_name = getattr(repo, 'repo_name', None)
131 132
132 133 user_log = _store_log(
133 134 action_name=safe_unicode(action),
134 135 action_data=action_data or {},
135 136 user_id=user_id,
136 137 username=username,
137 138 user_data=user_data or {},
138 139 ip_address=safe_unicode(ip_addr),
139 140 repository_id=repository_id,
140 141 repository_name=repository_name
141 142 )
142 143 sa_session.add(user_log)
143 144 if commit:
144 145 sa_session.commit()
145 146
146 147 except Exception:
147 148 log.exception('AUDIT: failed to store audit log')
@@ -1,993 +1,999 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Repository model for rhodecode
23 23 """
24 24
25 25 import logging
26 26 import os
27 27 import re
28 28 import shutil
29 29 import time
30 30 import traceback
31 31 from datetime import datetime, timedelta
32 32
33 33 from zope.cachedescriptors.property import Lazy as LazyProperty
34 34
35 35 from rhodecode import events
36 36 from rhodecode.lib import helpers as h
37 37 from rhodecode.lib.auth import HasUserGroupPermissionAny
38 38 from rhodecode.lib.caching_query import FromCache
39 39 from rhodecode.lib.exceptions import AttachedForksError
40 40 from rhodecode.lib.hooks_base import log_delete_repository
41 41 from rhodecode.lib.utils import make_db_config
42 42 from rhodecode.lib.utils2 import (
43 43 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
44 44 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
45 45 from rhodecode.lib.vcs.backends import get_backend
46 46 from rhodecode.model import BaseModel
47 47 from rhodecode.model.db import (
48 48 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
49 49 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
50 50 RepoGroup, RepositoryField)
51 51
52 52 from rhodecode.model.settings import VcsSettingsModel
53 53
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 class RepoModel(BaseModel):
59 59
60 60 cls = Repository
61 61
62 62 def _get_user_group(self, users_group):
63 63 return self._get_instance(UserGroup, users_group,
64 64 callback=UserGroup.get_by_group_name)
65 65
66 66 def _get_repo_group(self, repo_group):
67 67 return self._get_instance(RepoGroup, repo_group,
68 68 callback=RepoGroup.get_by_group_name)
69 69
70 70 def _create_default_perms(self, repository, private):
71 71 # create default permission
72 72 default = 'repository.read'
73 73 def_user = User.get_default_user()
74 74 for p in def_user.user_perms:
75 75 if p.permission.permission_name.startswith('repository.'):
76 76 default = p.permission.permission_name
77 77 break
78 78
79 79 default_perm = 'repository.none' if private else default
80 80
81 81 repo_to_perm = UserRepoToPerm()
82 82 repo_to_perm.permission = Permission.get_by_key(default_perm)
83 83
84 84 repo_to_perm.repository = repository
85 85 repo_to_perm.user_id = def_user.user_id
86 86
87 87 return repo_to_perm
88 88
89 89 @LazyProperty
90 90 def repos_path(self):
91 91 """
92 92 Gets the repositories root path from database
93 93 """
94 94 settings_model = VcsSettingsModel(sa=self.sa)
95 95 return settings_model.get_repos_location()
96 96
97 97 def get(self, repo_id, cache=False):
98 98 repo = self.sa.query(Repository) \
99 99 .filter(Repository.repo_id == repo_id)
100 100
101 101 if cache:
102 102 repo = repo.options(FromCache("sql_cache_short",
103 103 "get_repo_%s" % repo_id))
104 104 return repo.scalar()
105 105
106 106 def get_repo(self, repository):
107 107 return self._get_repo(repository)
108 108
109 109 def get_by_repo_name(self, repo_name, cache=False):
110 110 repo = self.sa.query(Repository) \
111 111 .filter(Repository.repo_name == repo_name)
112 112
113 113 if cache:
114 114 repo = repo.options(FromCache("sql_cache_short",
115 115 "get_repo_%s" % repo_name))
116 116 return repo.scalar()
117 117
118 118 def _extract_id_from_repo_name(self, repo_name):
119 119 if repo_name.startswith('/'):
120 120 repo_name = repo_name.lstrip('/')
121 121 by_id_match = re.match(r'^_(\d{1,})', repo_name)
122 122 if by_id_match:
123 123 return by_id_match.groups()[0]
124 124
125 125 def get_repo_by_id(self, repo_name):
126 126 """
127 127 Extracts repo_name by id from special urls.
128 128 Example url is _11/repo_name
129 129
130 130 :param repo_name:
131 131 :return: repo object if matched else None
132 132 """
133 133 try:
134 134 _repo_id = self._extract_id_from_repo_name(repo_name)
135 135 if _repo_id:
136 136 return self.get(_repo_id)
137 137 except Exception:
138 138 log.exception('Failed to extract repo_name from URL')
139 139
140 140 return None
141 141
142 142 def get_repos_for_root(self, root, traverse=False):
143 143 if traverse:
144 144 like_expression = u'{}%'.format(safe_unicode(root))
145 145 repos = Repository.query().filter(
146 146 Repository.repo_name.like(like_expression)).all()
147 147 else:
148 148 if root and not isinstance(root, RepoGroup):
149 149 raise ValueError(
150 150 'Root must be an instance '
151 151 'of RepoGroup, got:{} instead'.format(type(root)))
152 152 repos = Repository.query().filter(Repository.group == root).all()
153 153 return repos
154 154
155 155 def get_url(self, repo):
156 156 return h.url('summary_home', repo_name=safe_str(repo.repo_name),
157 157 qualified=True)
158 158
159 159 @classmethod
160 160 def update_repoinfo(cls, repositories=None):
161 161 if not repositories:
162 162 repositories = Repository.getAll()
163 163 for repo in repositories:
164 164 repo.update_commit_cache()
165 165
166 166 def get_repos_as_dict(self, repo_list=None, admin=False,
167 167 super_user_actions=False):
168 168
169 169 from rhodecode.lib.utils import PartialRenderer
170 170 _render = PartialRenderer('data_table/_dt_elements.mako')
171 171 c = _render.c
172 172
173 173 def quick_menu(repo_name):
174 174 return _render('quick_menu', repo_name)
175 175
176 176 def repo_lnk(name, rtype, rstate, private, fork_of):
177 177 return _render('repo_name', name, rtype, rstate, private, fork_of,
178 178 short_name=not admin, admin=False)
179 179
180 180 def last_change(last_change):
181 181 if admin and isinstance(last_change, datetime) and not last_change.tzinfo:
182 182 last_change = last_change + timedelta(seconds=
183 183 (datetime.now() - datetime.utcnow()).seconds)
184 184 return _render("last_change", last_change)
185 185
186 186 def rss_lnk(repo_name):
187 187 return _render("rss", repo_name)
188 188
189 189 def atom_lnk(repo_name):
190 190 return _render("atom", repo_name)
191 191
192 192 def last_rev(repo_name, cs_cache):
193 193 return _render('revision', repo_name, cs_cache.get('revision'),
194 194 cs_cache.get('raw_id'), cs_cache.get('author'),
195 195 cs_cache.get('message'))
196 196
197 197 def desc(desc):
198 198 if c.visual.stylify_metatags:
199 199 desc = h.urlify_text(h.escaped_stylize(desc))
200 200 else:
201 201 desc = h.urlify_text(h.html_escape(desc))
202 202
203 203 return _render('repo_desc', desc)
204 204
205 205 def state(repo_state):
206 206 return _render("repo_state", repo_state)
207 207
208 208 def repo_actions(repo_name):
209 209 return _render('repo_actions', repo_name, super_user_actions)
210 210
211 211 def user_profile(username):
212 212 return _render('user_profile', username)
213 213
214 214 repos_data = []
215 215 for repo in repo_list:
216 216 cs_cache = repo.changeset_cache
217 217 row = {
218 218 "menu": quick_menu(repo.repo_name),
219 219
220 220 "name": repo_lnk(repo.repo_name, repo.repo_type,
221 221 repo.repo_state, repo.private, repo.fork),
222 222 "name_raw": repo.repo_name.lower(),
223 223
224 224 "last_change": last_change(repo.last_db_change),
225 225 "last_change_raw": datetime_to_time(repo.last_db_change),
226 226
227 227 "last_changeset": last_rev(repo.repo_name, cs_cache),
228 228 "last_changeset_raw": cs_cache.get('revision'),
229 229
230 230 "desc": desc(repo.description),
231 231 "owner": user_profile(repo.user.username),
232 232
233 233 "state": state(repo.repo_state),
234 234 "rss": rss_lnk(repo.repo_name),
235 235
236 236 "atom": atom_lnk(repo.repo_name),
237 237 }
238 238 if admin:
239 239 row.update({
240 240 "action": repo_actions(repo.repo_name),
241 241 })
242 242 repos_data.append(row)
243 243
244 244 return repos_data
245 245
246 246 def _get_defaults(self, repo_name):
247 247 """
248 248 Gets information about repository, and returns a dict for
249 249 usage in forms
250 250
251 251 :param repo_name:
252 252 """
253 253
254 254 repo_info = Repository.get_by_repo_name(repo_name)
255 255
256 256 if repo_info is None:
257 257 return None
258 258
259 259 defaults = repo_info.get_dict()
260 260 defaults['repo_name'] = repo_info.just_name
261 261
262 262 groups = repo_info.groups_with_parents
263 263 parent_group = groups[-1] if groups else None
264 264
265 265 # we use -1 as this is how in HTML, we mark an empty group
266 266 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
267 267
268 268 keys_to_process = (
269 269 {'k': 'repo_type', 'strip': False},
270 270 {'k': 'repo_enable_downloads', 'strip': True},
271 271 {'k': 'repo_description', 'strip': True},
272 272 {'k': 'repo_enable_locking', 'strip': True},
273 273 {'k': 'repo_landing_rev', 'strip': True},
274 274 {'k': 'clone_uri', 'strip': False},
275 275 {'k': 'repo_private', 'strip': True},
276 276 {'k': 'repo_enable_statistics', 'strip': True}
277 277 )
278 278
279 279 for item in keys_to_process:
280 280 attr = item['k']
281 281 if item['strip']:
282 282 attr = remove_prefix(item['k'], 'repo_')
283 283
284 284 val = defaults[attr]
285 285 if item['k'] == 'repo_landing_rev':
286 286 val = ':'.join(defaults[attr])
287 287 defaults[item['k']] = val
288 288 if item['k'] == 'clone_uri':
289 289 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
290 290
291 291 # fill owner
292 292 if repo_info.user:
293 293 defaults.update({'user': repo_info.user.username})
294 294 else:
295 295 replacement_user = User.get_first_super_admin().username
296 296 defaults.update({'user': replacement_user})
297 297
298 # fill repository users
299 for p in repo_info.repo_to_perm:
300 defaults.update({'u_perm_%s' % p.user.user_id:
301 p.permission.permission_name})
302
303 # fill repository groups
304 for p in repo_info.users_group_to_perm:
305 defaults.update({'g_perm_%s' % p.users_group.users_group_id:
306 p.permission.permission_name})
307
308 298 return defaults
309 299
310 300 def update(self, repo, **kwargs):
311 301 try:
312 302 cur_repo = self._get_repo(repo)
313 303 source_repo_name = cur_repo.repo_name
314 304 if 'user' in kwargs:
315 305 cur_repo.user = User.get_by_username(kwargs['user'])
316 306
317 307 if 'repo_group' in kwargs:
318 308 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
319 309 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
320 310
321 311 update_keys = [
322 312 (1, 'repo_description'),
323 313 (1, 'repo_landing_rev'),
324 314 (1, 'repo_private'),
325 315 (1, 'repo_enable_downloads'),
326 316 (1, 'repo_enable_locking'),
327 317 (1, 'repo_enable_statistics'),
328 318 (0, 'clone_uri'),
329 319 (0, 'fork_id')
330 320 ]
331 321 for strip, k in update_keys:
332 322 if k in kwargs:
333 323 val = kwargs[k]
334 324 if strip:
335 325 k = remove_prefix(k, 'repo_')
336 326
337 327 setattr(cur_repo, k, val)
338 328
339 329 new_name = cur_repo.get_new_name(kwargs['repo_name'])
340 330 cur_repo.repo_name = new_name
341 331
342 332 # if private flag is set, reset default permission to NONE
343 333 if kwargs.get('repo_private'):
344 334 EMPTY_PERM = 'repository.none'
345 335 RepoModel().grant_user_permission(
346 336 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
347 337 )
348 338
349 339 # handle extra fields
350 340 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
351 341 kwargs):
352 342 k = RepositoryField.un_prefix_key(field)
353 343 ex_field = RepositoryField.get_by_key_name(
354 344 key=k, repo=cur_repo)
355 345 if ex_field:
356 346 ex_field.field_value = kwargs[field]
357 347 self.sa.add(ex_field)
358 348 self.sa.add(cur_repo)
359 349
360 350 if source_repo_name != new_name:
361 351 # rename repository
362 352 self._rename_filesystem_repo(
363 353 old=source_repo_name, new=new_name)
364 354
365 355 return cur_repo
366 356 except Exception:
367 357 log.error(traceback.format_exc())
368 358 raise
369 359
370 360 def _create_repo(self, repo_name, repo_type, description, owner,
371 361 private=False, clone_uri=None, repo_group=None,
372 362 landing_rev='rev:tip', fork_of=None,
373 363 copy_fork_permissions=False, enable_statistics=False,
374 364 enable_locking=False, enable_downloads=False,
375 365 copy_group_permissions=False,
376 366 state=Repository.STATE_PENDING):
377 367 """
378 368 Create repository inside database with PENDING state, this should be
379 369 only executed by create() repo. With exception of importing existing
380 370 repos
381 371 """
382 372 from rhodecode.model.scm import ScmModel
383 373
384 374 owner = self._get_user(owner)
385 375 fork_of = self._get_repo(fork_of)
386 376 repo_group = self._get_repo_group(safe_int(repo_group))
387 377
388 378 try:
389 379 repo_name = safe_unicode(repo_name)
390 380 description = safe_unicode(description)
391 381 # repo name is just a name of repository
392 382 # while repo_name_full is a full qualified name that is combined
393 383 # with name and path of group
394 384 repo_name_full = repo_name
395 385 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
396 386
397 387 new_repo = Repository()
398 388 new_repo.repo_state = state
399 389 new_repo.enable_statistics = False
400 390 new_repo.repo_name = repo_name_full
401 391 new_repo.repo_type = repo_type
402 392 new_repo.user = owner
403 393 new_repo.group = repo_group
404 394 new_repo.description = description or repo_name
405 395 new_repo.private = private
406 396 new_repo.clone_uri = clone_uri
407 397 new_repo.landing_rev = landing_rev
408 398
409 399 new_repo.enable_statistics = enable_statistics
410 400 new_repo.enable_locking = enable_locking
411 401 new_repo.enable_downloads = enable_downloads
412 402
413 403 if repo_group:
414 404 new_repo.enable_locking = repo_group.enable_locking
415 405
416 406 if fork_of:
417 407 parent_repo = fork_of
418 408 new_repo.fork = parent_repo
419 409
420 410 events.trigger(events.RepoPreCreateEvent(new_repo))
421 411
422 412 self.sa.add(new_repo)
423 413
424 414 EMPTY_PERM = 'repository.none'
425 415 if fork_of and copy_fork_permissions:
426 416 repo = fork_of
427 417 user_perms = UserRepoToPerm.query() \
428 418 .filter(UserRepoToPerm.repository == repo).all()
429 419 group_perms = UserGroupRepoToPerm.query() \
430 420 .filter(UserGroupRepoToPerm.repository == repo).all()
431 421
432 422 for perm in user_perms:
433 423 UserRepoToPerm.create(
434 424 perm.user, new_repo, perm.permission)
435 425
436 426 for perm in group_perms:
437 427 UserGroupRepoToPerm.create(
438 428 perm.users_group, new_repo, perm.permission)
439 429 # in case we copy permissions and also set this repo to private
440 430 # override the default user permission to make it a private
441 431 # repo
442 432 if private:
443 433 RepoModel(self.sa).grant_user_permission(
444 434 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
445 435
446 436 elif repo_group and copy_group_permissions:
447 437 user_perms = UserRepoGroupToPerm.query() \
448 438 .filter(UserRepoGroupToPerm.group == repo_group).all()
449 439
450 440 group_perms = UserGroupRepoGroupToPerm.query() \
451 441 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
452 442
453 443 for perm in user_perms:
454 444 perm_name = perm.permission.permission_name.replace(
455 445 'group.', 'repository.')
456 446 perm_obj = Permission.get_by_key(perm_name)
457 447 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
458 448
459 449 for perm in group_perms:
460 450 perm_name = perm.permission.permission_name.replace(
461 451 'group.', 'repository.')
462 452 perm_obj = Permission.get_by_key(perm_name)
463 453 UserGroupRepoToPerm.create(
464 454 perm.users_group, new_repo, perm_obj)
465 455
466 456 if private:
467 457 RepoModel(self.sa).grant_user_permission(
468 458 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
469 459
470 460 else:
471 461 perm_obj = self._create_default_perms(new_repo, private)
472 462 self.sa.add(perm_obj)
473 463
474 464 # now automatically start following this repository as owner
475 465 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
476 466 owner.user_id)
477 467
478 468 # we need to flush here, in order to check if database won't
479 469 # throw any exceptions, create filesystem dirs at the very end
480 470 self.sa.flush()
481 471 events.trigger(events.RepoCreateEvent(new_repo))
482 472 return new_repo
483 473
484 474 except Exception:
485 475 log.error(traceback.format_exc())
486 476 raise
487 477
488 478 def create(self, form_data, cur_user):
489 479 """
490 480 Create repository using celery tasks
491 481
492 482 :param form_data:
493 483 :param cur_user:
494 484 """
495 485 from rhodecode.lib.celerylib import tasks, run_task
496 486 return run_task(tasks.create_repo, form_data, cur_user)
497 487
498 488 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
499 489 perm_deletions=None, check_perms=True,
500 490 cur_user=None):
501 491 if not perm_additions:
502 492 perm_additions = []
503 493 if not perm_updates:
504 494 perm_updates = []
505 495 if not perm_deletions:
506 496 perm_deletions = []
507 497
508 498 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
509 499
500 changes = {
501 'added': [],
502 'updated': [],
503 'deleted': []
504 }
510 505 # update permissions
511 506 for member_id, perm, member_type in perm_updates:
512 507 member_id = int(member_id)
513 508 if member_type == 'user':
509 member_name = User.get(member_id).username
514 510 # this updates also current one if found
515 511 self.grant_user_permission(
516 512 repo=repo, user=member_id, perm=perm)
517 513 else: # set for user group
518 514 # check if we have permissions to alter this usergroup
519 515 member_name = UserGroup.get(member_id).users_group_name
520 516 if not check_perms or HasUserGroupPermissionAny(
521 517 *req_perms)(member_name, user=cur_user):
522 518 self.grant_user_group_permission(
523 519 repo=repo, group_name=member_id, perm=perm)
524 520
521 changes['updated'].append({'type': member_type, 'id': member_id,
522 'name': member_name, 'new_perm': perm})
523
525 524 # set new permissions
526 525 for member_id, perm, member_type in perm_additions:
527 526 member_id = int(member_id)
528 527 if member_type == 'user':
528 member_name = User.get(member_id).username
529 529 self.grant_user_permission(
530 530 repo=repo, user=member_id, perm=perm)
531 531 else: # set for user group
532 532 # check if we have permissions to alter this usergroup
533 533 member_name = UserGroup.get(member_id).users_group_name
534 534 if not check_perms or HasUserGroupPermissionAny(
535 535 *req_perms)(member_name, user=cur_user):
536 536 self.grant_user_group_permission(
537 537 repo=repo, group_name=member_id, perm=perm)
538
538 changes['added'].append({'type': member_type, 'id': member_id,
539 'name': member_name, 'new_perm': perm})
539 540 # delete permissions
540 541 for member_id, perm, member_type in perm_deletions:
541 542 member_id = int(member_id)
542 543 if member_type == 'user':
544 member_name = User.get(member_id).username
543 545 self.revoke_user_permission(repo=repo, user=member_id)
544 546 else: # set for user group
545 547 # check if we have permissions to alter this usergroup
546 548 member_name = UserGroup.get(member_id).users_group_name
547 549 if not check_perms or HasUserGroupPermissionAny(
548 550 *req_perms)(member_name, user=cur_user):
549 551 self.revoke_user_group_permission(
550 552 repo=repo, group_name=member_id)
551 553
554 changes['deleted'].append({'type': member_type, 'id': member_id,
555 'name': member_name, 'new_perm': perm})
556 return changes
557
552 558 def create_fork(self, form_data, cur_user):
553 559 """
554 560 Simple wrapper into executing celery task for fork creation
555 561
556 562 :param form_data:
557 563 :param cur_user:
558 564 """
559 565 from rhodecode.lib.celerylib import tasks, run_task
560 566 return run_task(tasks.create_repo_fork, form_data, cur_user)
561 567
562 568 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
563 569 """
564 570 Delete given repository, forks parameter defines what do do with
565 571 attached forks. Throws AttachedForksError if deleted repo has attached
566 572 forks
567 573
568 574 :param repo:
569 575 :param forks: str 'delete' or 'detach'
570 576 :param fs_remove: remove(archive) repo from filesystem
571 577 """
572 578 if not cur_user:
573 579 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
574 580 repo = self._get_repo(repo)
575 581 if repo:
576 582 if forks == 'detach':
577 583 for r in repo.forks:
578 584 r.fork = None
579 585 self.sa.add(r)
580 586 elif forks == 'delete':
581 587 for r in repo.forks:
582 588 self.delete(r, forks='delete')
583 589 elif [f for f in repo.forks]:
584 590 raise AttachedForksError()
585 591
586 592 old_repo_dict = repo.get_dict()
587 593 events.trigger(events.RepoPreDeleteEvent(repo))
588 594 try:
589 595 self.sa.delete(repo)
590 596 if fs_remove:
591 597 self._delete_filesystem_repo(repo)
592 598 else:
593 599 log.debug('skipping removal from filesystem')
594 600 old_repo_dict.update({
595 601 'deleted_by': cur_user,
596 602 'deleted_on': time.time(),
597 603 })
598 604 log_delete_repository(**old_repo_dict)
599 605 events.trigger(events.RepoDeleteEvent(repo))
600 606 except Exception:
601 607 log.error(traceback.format_exc())
602 608 raise
603 609
604 610 def grant_user_permission(self, repo, user, perm):
605 611 """
606 612 Grant permission for user on given repository, or update existing one
607 613 if found
608 614
609 615 :param repo: Instance of Repository, repository_id, or repository name
610 616 :param user: Instance of User, user_id or username
611 617 :param perm: Instance of Permission, or permission_name
612 618 """
613 619 user = self._get_user(user)
614 620 repo = self._get_repo(repo)
615 621 permission = self._get_perm(perm)
616 622
617 623 # check if we have that permission already
618 624 obj = self.sa.query(UserRepoToPerm) \
619 625 .filter(UserRepoToPerm.user == user) \
620 626 .filter(UserRepoToPerm.repository == repo) \
621 627 .scalar()
622 628 if obj is None:
623 629 # create new !
624 630 obj = UserRepoToPerm()
625 631 obj.repository = repo
626 632 obj.user = user
627 633 obj.permission = permission
628 634 self.sa.add(obj)
629 635 log.debug('Granted perm %s to %s on %s', perm, user, repo)
630 636 action_logger_generic(
631 637 'granted permission: {} to user: {} on repo: {}'.format(
632 638 perm, user, repo), namespace='security.repo')
633 639 return obj
634 640
635 641 def revoke_user_permission(self, repo, user):
636 642 """
637 643 Revoke permission for user on given repository
638 644
639 645 :param repo: Instance of Repository, repository_id, or repository name
640 646 :param user: Instance of User, user_id or username
641 647 """
642 648
643 649 user = self._get_user(user)
644 650 repo = self._get_repo(repo)
645 651
646 652 obj = self.sa.query(UserRepoToPerm) \
647 653 .filter(UserRepoToPerm.repository == repo) \
648 654 .filter(UserRepoToPerm.user == user) \
649 655 .scalar()
650 656 if obj:
651 657 self.sa.delete(obj)
652 658 log.debug('Revoked perm on %s on %s', repo, user)
653 659 action_logger_generic(
654 660 'revoked permission from user: {} on repo: {}'.format(
655 661 user, repo), namespace='security.repo')
656 662
657 663 def grant_user_group_permission(self, repo, group_name, perm):
658 664 """
659 665 Grant permission for user group on given repository, or update
660 666 existing one if found
661 667
662 668 :param repo: Instance of Repository, repository_id, or repository name
663 669 :param group_name: Instance of UserGroup, users_group_id,
664 670 or user group name
665 671 :param perm: Instance of Permission, or permission_name
666 672 """
667 673 repo = self._get_repo(repo)
668 674 group_name = self._get_user_group(group_name)
669 675 permission = self._get_perm(perm)
670 676
671 677 # check if we have that permission already
672 678 obj = self.sa.query(UserGroupRepoToPerm) \
673 679 .filter(UserGroupRepoToPerm.users_group == group_name) \
674 680 .filter(UserGroupRepoToPerm.repository == repo) \
675 681 .scalar()
676 682
677 683 if obj is None:
678 684 # create new
679 685 obj = UserGroupRepoToPerm()
680 686
681 687 obj.repository = repo
682 688 obj.users_group = group_name
683 689 obj.permission = permission
684 690 self.sa.add(obj)
685 691 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
686 692 action_logger_generic(
687 693 'granted permission: {} to usergroup: {} on repo: {}'.format(
688 694 perm, group_name, repo), namespace='security.repo')
689 695
690 696 return obj
691 697
692 698 def revoke_user_group_permission(self, repo, group_name):
693 699 """
694 700 Revoke permission for user group on given repository
695 701
696 702 :param repo: Instance of Repository, repository_id, or repository name
697 703 :param group_name: Instance of UserGroup, users_group_id,
698 704 or user group name
699 705 """
700 706 repo = self._get_repo(repo)
701 707 group_name = self._get_user_group(group_name)
702 708
703 709 obj = self.sa.query(UserGroupRepoToPerm) \
704 710 .filter(UserGroupRepoToPerm.repository == repo) \
705 711 .filter(UserGroupRepoToPerm.users_group == group_name) \
706 712 .scalar()
707 713 if obj:
708 714 self.sa.delete(obj)
709 715 log.debug('Revoked perm to %s on %s', repo, group_name)
710 716 action_logger_generic(
711 717 'revoked permission from usergroup: {} on repo: {}'.format(
712 718 group_name, repo), namespace='security.repo')
713 719
714 720 def delete_stats(self, repo_name):
715 721 """
716 722 removes stats for given repo
717 723
718 724 :param repo_name:
719 725 """
720 726 repo = self._get_repo(repo_name)
721 727 try:
722 728 obj = self.sa.query(Statistics) \
723 729 .filter(Statistics.repository == repo).scalar()
724 730 if obj:
725 731 self.sa.delete(obj)
726 732 except Exception:
727 733 log.error(traceback.format_exc())
728 734 raise
729 735
730 736 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
731 737 field_type='str', field_desc=''):
732 738
733 739 repo = self._get_repo(repo_name)
734 740
735 741 new_field = RepositoryField()
736 742 new_field.repository = repo
737 743 new_field.field_key = field_key
738 744 new_field.field_type = field_type # python type
739 745 new_field.field_value = field_value
740 746 new_field.field_desc = field_desc
741 747 new_field.field_label = field_label
742 748 self.sa.add(new_field)
743 749 return new_field
744 750
745 751 def delete_repo_field(self, repo_name, field_key):
746 752 repo = self._get_repo(repo_name)
747 753 field = RepositoryField.get_by_key_name(field_key, repo)
748 754 if field:
749 755 self.sa.delete(field)
750 756
751 757 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
752 758 clone_uri=None, repo_store_location=None,
753 759 use_global_config=False):
754 760 """
755 761 makes repository on filesystem. It's group aware means it'll create
756 762 a repository within a group, and alter the paths accordingly of
757 763 group location
758 764
759 765 :param repo_name:
760 766 :param alias:
761 767 :param parent:
762 768 :param clone_uri:
763 769 :param repo_store_location:
764 770 """
765 771 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
766 772 from rhodecode.model.scm import ScmModel
767 773
768 774 if Repository.NAME_SEP in repo_name:
769 775 raise ValueError(
770 776 'repo_name must not contain groups got `%s`' % repo_name)
771 777
772 778 if isinstance(repo_group, RepoGroup):
773 779 new_parent_path = os.sep.join(repo_group.full_path_splitted)
774 780 else:
775 781 new_parent_path = repo_group or ''
776 782
777 783 if repo_store_location:
778 784 _paths = [repo_store_location]
779 785 else:
780 786 _paths = [self.repos_path, new_parent_path, repo_name]
781 787 # we need to make it str for mercurial
782 788 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
783 789
784 790 # check if this path is not a repository
785 791 if is_valid_repo(repo_path, self.repos_path):
786 792 raise Exception('This path %s is a valid repository' % repo_path)
787 793
788 794 # check if this path is a group
789 795 if is_valid_repo_group(repo_path, self.repos_path):
790 796 raise Exception('This path %s is a valid group' % repo_path)
791 797
792 798 log.info('creating repo %s in %s from url: `%s`',
793 799 repo_name, safe_unicode(repo_path),
794 800 obfuscate_url_pw(clone_uri))
795 801
796 802 backend = get_backend(repo_type)
797 803
798 804 config_repo = None if use_global_config else repo_name
799 805 if config_repo and new_parent_path:
800 806 config_repo = Repository.NAME_SEP.join(
801 807 (new_parent_path, config_repo))
802 808 config = make_db_config(clear_session=False, repo=config_repo)
803 809 config.set('extensions', 'largefiles', '')
804 810
805 811 # patch and reset hooks section of UI config to not run any
806 812 # hooks on creating remote repo
807 813 config.clear_section('hooks')
808 814
809 815 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
810 816 if repo_type == 'git':
811 817 repo = backend(
812 818 repo_path, config=config, create=True, src_url=clone_uri,
813 819 bare=True)
814 820 else:
815 821 repo = backend(
816 822 repo_path, config=config, create=True, src_url=clone_uri)
817 823
818 824 ScmModel().install_hooks(repo, repo_type=repo_type)
819 825
820 826 log.debug('Created repo %s with %s backend',
821 827 safe_unicode(repo_name), safe_unicode(repo_type))
822 828 return repo
823 829
824 830 def _rename_filesystem_repo(self, old, new):
825 831 """
826 832 renames repository on filesystem
827 833
828 834 :param old: old name
829 835 :param new: new name
830 836 """
831 837 log.info('renaming repo from %s to %s', old, new)
832 838
833 839 old_path = os.path.join(self.repos_path, old)
834 840 new_path = os.path.join(self.repos_path, new)
835 841 if os.path.isdir(new_path):
836 842 raise Exception(
837 843 'Was trying to rename to already existing dir %s' % new_path
838 844 )
839 845 shutil.move(old_path, new_path)
840 846
841 847 def _delete_filesystem_repo(self, repo):
842 848 """
843 849 removes repo from filesystem, the removal is acctually made by
844 850 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
845 851 repository is no longer valid for rhodecode, can be undeleted later on
846 852 by reverting the renames on this repository
847 853
848 854 :param repo: repo object
849 855 """
850 856 rm_path = os.path.join(self.repos_path, repo.repo_name)
851 857 repo_group = repo.group
852 858 log.info("Removing repository %s", rm_path)
853 859 # disable hg/git internal that it doesn't get detected as repo
854 860 alias = repo.repo_type
855 861
856 862 config = make_db_config(clear_session=False)
857 863 config.set('extensions', 'largefiles', '')
858 864 bare = getattr(repo.scm_instance(config=config), 'bare', False)
859 865
860 866 # skip this for bare git repos
861 867 if not bare:
862 868 # disable VCS repo
863 869 vcs_path = os.path.join(rm_path, '.%s' % alias)
864 870 if os.path.exists(vcs_path):
865 871 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
866 872
867 873 _now = datetime.now()
868 874 _ms = str(_now.microsecond).rjust(6, '0')
869 875 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
870 876 repo.just_name)
871 877 if repo_group:
872 878 # if repository is in group, prefix the removal path with the group
873 879 args = repo_group.full_path_splitted + [_d]
874 880 _d = os.path.join(*args)
875 881
876 882 if os.path.isdir(rm_path):
877 883 shutil.move(rm_path, os.path.join(self.repos_path, _d))
878 884
879 885
880 886 class ReadmeFinder:
881 887 """
882 888 Utility which knows how to find a readme for a specific commit.
883 889
884 890 The main idea is that this is a configurable algorithm. When creating an
885 891 instance you can define parameters, currently only the `default_renderer`.
886 892 Based on this configuration the method :meth:`search` behaves slightly
887 893 different.
888 894 """
889 895
890 896 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
891 897 path_re = re.compile(r'^docs?', re.IGNORECASE)
892 898
893 899 default_priorities = {
894 900 None: 0,
895 901 '.text': 2,
896 902 '.txt': 3,
897 903 '.rst': 1,
898 904 '.rest': 2,
899 905 '.md': 1,
900 906 '.mkdn': 2,
901 907 '.mdown': 3,
902 908 '.markdown': 4,
903 909 }
904 910
905 911 path_priority = {
906 912 'doc': 0,
907 913 'docs': 1,
908 914 }
909 915
910 916 FALLBACK_PRIORITY = 99
911 917
912 918 RENDERER_TO_EXTENSION = {
913 919 'rst': ['.rst', '.rest'],
914 920 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
915 921 }
916 922
917 923 def __init__(self, default_renderer=None):
918 924 self._default_renderer = default_renderer
919 925 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
920 926 default_renderer, [])
921 927
922 928 def search(self, commit, path='/'):
923 929 """
924 930 Find a readme in the given `commit`.
925 931 """
926 932 nodes = commit.get_nodes(path)
927 933 matches = self._match_readmes(nodes)
928 934 matches = self._sort_according_to_priority(matches)
929 935 if matches:
930 936 return matches[0].node
931 937
932 938 paths = self._match_paths(nodes)
933 939 paths = self._sort_paths_according_to_priority(paths)
934 940 for path in paths:
935 941 match = self.search(commit, path=path)
936 942 if match:
937 943 return match
938 944
939 945 return None
940 946
941 947 def _match_readmes(self, nodes):
942 948 for node in nodes:
943 949 if not node.is_file():
944 950 continue
945 951 path = node.path.rsplit('/', 1)[-1]
946 952 match = self.readme_re.match(path)
947 953 if match:
948 954 extension = match.group(1)
949 955 yield ReadmeMatch(node, match, self._priority(extension))
950 956
951 957 def _match_paths(self, nodes):
952 958 for node in nodes:
953 959 if not node.is_dir():
954 960 continue
955 961 match = self.path_re.match(node.path)
956 962 if match:
957 963 yield node.path
958 964
959 965 def _priority(self, extension):
960 966 renderer_priority = (
961 967 0 if extension in self._renderer_extensions else 1)
962 968 extension_priority = self.default_priorities.get(
963 969 extension, self.FALLBACK_PRIORITY)
964 970 return (renderer_priority, extension_priority)
965 971
966 972 def _sort_according_to_priority(self, matches):
967 973
968 974 def priority_and_path(match):
969 975 return (match.priority, match.path)
970 976
971 977 return sorted(matches, key=priority_and_path)
972 978
973 979 def _sort_paths_according_to_priority(self, paths):
974 980
975 981 def priority_and_path(path):
976 982 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
977 983
978 984 return sorted(paths, key=priority_and_path)
979 985
980 986
981 987 class ReadmeMatch:
982 988
983 989 def __init__(self, node, match, priority):
984 990 self.node = node
985 991 self._match = match
986 992 self.priority = priority
987 993
988 994 @property
989 995 def path(self):
990 996 return self.node.path
991 997
992 998 def __repr__(self):
993 999 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
@@ -1,1109 +1,1122 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Set of generic validators
23 23 """
24 24
25 25 import logging
26 26 import os
27 27 import re
28 28 from collections import defaultdict
29 29
30 30 import formencode
31 31 import ipaddress
32 32 from formencode.validators import (
33 33 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
34 34 NotEmpty, IPAddress, CIDR, String, FancyValidator
35 35 )
36 36 from pylons.i18n.translation import _
37 37 from sqlalchemy.sql.expression import true
38 38 from sqlalchemy.util import OrderedSet
39 39 from webhelpers.pylonslib.secure_form import authentication_token
40 40
41 41 from rhodecode.authentication import (
42 42 legacy_plugin_prefix, _import_legacy_plugin)
43 43 from rhodecode.authentication.base import loadplugin
44 44 from rhodecode.config.routing import ADMIN_PREFIX
45 45 from rhodecode.lib.auth import HasRepoGroupPermissionAny, HasPermissionAny
46 46 from rhodecode.lib.utils import repo_name_slug, make_db_config
47 47 from rhodecode.lib.utils2 import safe_int, str2bool, aslist, md5
48 48 from rhodecode.lib.vcs.backends.git.repository import GitRepository
49 49 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
50 50 from rhodecode.lib.vcs.backends.svn.repository import SubversionRepository
51 51 from rhodecode.model.db import (
52 52 RepoGroup, Repository, UserGroup, User, ChangesetStatus, Gist)
53 53 from rhodecode.model.settings import VcsSettingsModel
54 54
55 55 # silence warnings and pylint
56 56 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
57 57 NotEmpty, IPAddress, CIDR, String, FancyValidator
58 58
59 59 log = logging.getLogger(__name__)
60 60
61 61
62 62 class _Missing(object):
63 63 pass
64 64
65 65 Missing = _Missing()
66 66
67 67
68 68 class StateObj(object):
69 69 """
70 70 this is needed to translate the messages using _() in validators
71 71 """
72 72 _ = staticmethod(_)
73 73
74 74
75 75 def M(self, key, state=None, **kwargs):
76 76 """
77 77 returns string from self.message based on given key,
78 78 passed kw params are used to substitute %(named)s params inside
79 79 translated strings
80 80
81 81 :param msg:
82 82 :param state:
83 83 """
84 84 if state is None:
85 85 state = StateObj()
86 86 else:
87 87 state._ = staticmethod(_)
88 88 # inject validator into state object
89 89 return self.message(key, state, **kwargs)
90 90
91 91
92 92 def UniqueList(convert=None):
93 93 class _UniqueList(formencode.FancyValidator):
94 94 """
95 95 Unique List !
96 96 """
97 97 messages = {
98 98 'empty': _(u'Value cannot be an empty list'),
99 99 'missing_value': _(u'Value cannot be an empty list'),
100 100 }
101 101
102 102 def _to_python(self, value, state):
103 103 ret_val = []
104 104
105 105 def make_unique(value):
106 106 seen = []
107 107 return [c for c in value if not (c in seen or seen.append(c))]
108 108
109 109 if isinstance(value, list):
110 110 ret_val = make_unique(value)
111 111 elif isinstance(value, set):
112 112 ret_val = make_unique(list(value))
113 113 elif isinstance(value, tuple):
114 114 ret_val = make_unique(list(value))
115 115 elif value is None:
116 116 ret_val = []
117 117 else:
118 118 ret_val = [value]
119 119
120 120 if convert:
121 121 ret_val = map(convert, ret_val)
122 122 return ret_val
123 123
124 124 def empty_value(self, value):
125 125 return []
126 126
127 127 return _UniqueList
128 128
129 129
130 130 def UniqueListFromString():
131 131 class _UniqueListFromString(UniqueList()):
132 132 def _to_python(self, value, state):
133 133 if isinstance(value, basestring):
134 134 value = aslist(value, ',')
135 135 return super(_UniqueListFromString, self)._to_python(value, state)
136 136 return _UniqueListFromString
137 137
138 138
139 139 def ValidSvnPattern(section, repo_name=None):
140 140 class _validator(formencode.validators.FancyValidator):
141 141 messages = {
142 142 'pattern_exists': _(u'Pattern already exists'),
143 143 }
144 144
145 145 def validate_python(self, value, state):
146 146 if not value:
147 147 return
148 148 model = VcsSettingsModel(repo=repo_name)
149 149 ui_settings = model.get_svn_patterns(section=section)
150 150 for entry in ui_settings:
151 151 if value == entry.value:
152 152 msg = M(self, 'pattern_exists', state)
153 153 raise formencode.Invalid(msg, value, state)
154 154 return _validator
155 155
156 156
157 157 def ValidUsername(edit=False, old_data={}):
158 158 class _validator(formencode.validators.FancyValidator):
159 159 messages = {
160 160 'username_exists': _(u'Username "%(username)s" already exists'),
161 161 'system_invalid_username':
162 162 _(u'Username "%(username)s" is forbidden'),
163 163 'invalid_username':
164 164 _(u'Username may only contain alphanumeric characters '
165 165 u'underscores, periods or dashes and must begin with '
166 166 u'alphanumeric character or underscore')
167 167 }
168 168
169 169 def validate_python(self, value, state):
170 170 if value in ['default', 'new_user']:
171 171 msg = M(self, 'system_invalid_username', state, username=value)
172 172 raise formencode.Invalid(msg, value, state)
173 173 # check if user is unique
174 174 old_un = None
175 175 if edit:
176 176 old_un = User.get(old_data.get('user_id')).username
177 177
178 178 if old_un != value or not edit:
179 179 if User.get_by_username(value, case_insensitive=True):
180 180 msg = M(self, 'username_exists', state, username=value)
181 181 raise formencode.Invalid(msg, value, state)
182 182
183 183 if (re.match(r'^[\w]{1}[\w\-\.]{0,254}$', value)
184 184 is None):
185 185 msg = M(self, 'invalid_username', state)
186 186 raise formencode.Invalid(msg, value, state)
187 187 return _validator
188 188
189 189
190 190 def ValidRegex(msg=None):
191 191 class _validator(formencode.validators.Regex):
192 192 messages = {'invalid': msg or _(u'The input is not valid')}
193 193 return _validator
194 194
195 195
196 196 def ValidRepoUser(allow_disabled=False):
197 197 class _validator(formencode.validators.FancyValidator):
198 198 messages = {
199 199 'invalid_username': _(u'Username %(username)s is not valid'),
200 200 'disabled_username': _(u'Username %(username)s is disabled')
201 201 }
202 202
203 203 def validate_python(self, value, state):
204 204 try:
205 205 user = User.query().filter(User.username == value).one()
206 206 except Exception:
207 207 msg = M(self, 'invalid_username', state, username=value)
208 208 raise formencode.Invalid(
209 209 msg, value, state, error_dict={'username': msg}
210 210 )
211 211 if user and (not allow_disabled and not user.active):
212 212 msg = M(self, 'disabled_username', state, username=value)
213 213 raise formencode.Invalid(
214 214 msg, value, state, error_dict={'username': msg}
215 215 )
216 216
217 217 return _validator
218 218
219 219
220 220 def ValidUserGroup(edit=False, old_data={}):
221 221 class _validator(formencode.validators.FancyValidator):
222 222 messages = {
223 223 'invalid_group': _(u'Invalid user group name'),
224 224 'group_exist': _(u'User group "%(usergroup)s" already exists'),
225 225 'invalid_usergroup_name':
226 226 _(u'user group name may only contain alphanumeric '
227 227 u'characters underscores, periods or dashes and must begin '
228 228 u'with alphanumeric character')
229 229 }
230 230
231 231 def validate_python(self, value, state):
232 232 if value in ['default']:
233 233 msg = M(self, 'invalid_group', state)
234 234 raise formencode.Invalid(
235 235 msg, value, state, error_dict={'users_group_name': msg}
236 236 )
237 237 # check if group is unique
238 238 old_ugname = None
239 239 if edit:
240 240 old_id = old_data.get('users_group_id')
241 241 old_ugname = UserGroup.get(old_id).users_group_name
242 242
243 243 if old_ugname != value or not edit:
244 244 is_existing_group = UserGroup.get_by_group_name(
245 245 value, case_insensitive=True)
246 246 if is_existing_group:
247 247 msg = M(self, 'group_exist', state, usergroup=value)
248 248 raise formencode.Invalid(
249 249 msg, value, state, error_dict={'users_group_name': msg}
250 250 )
251 251
252 252 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
253 253 msg = M(self, 'invalid_usergroup_name', state)
254 254 raise formencode.Invalid(
255 255 msg, value, state, error_dict={'users_group_name': msg}
256 256 )
257 257
258 258 return _validator
259 259
260 260
261 261 def ValidRepoGroup(edit=False, old_data={}, can_create_in_root=False):
262 262 class _validator(formencode.validators.FancyValidator):
263 263 messages = {
264 264 'group_parent_id': _(u'Cannot assign this group as parent'),
265 265 'group_exists': _(u'Group "%(group_name)s" already exists'),
266 266 'repo_exists': _(u'Repository with name "%(group_name)s" '
267 267 u'already exists'),
268 268 'permission_denied': _(u"no permission to store repository group"
269 269 u"in this location"),
270 270 'permission_denied_root': _(
271 271 u"no permission to store repository group "
272 272 u"in root location")
273 273 }
274 274
275 275 def _to_python(self, value, state):
276 276 group_name = repo_name_slug(value.get('group_name', ''))
277 277 group_parent_id = safe_int(value.get('group_parent_id'))
278 278 gr = RepoGroup.get(group_parent_id)
279 279 if gr:
280 280 parent_group_path = gr.full_path
281 281 # value needs to be aware of group name in order to check
282 282 # db key This is an actual just the name to store in the
283 283 # database
284 284 group_name_full = (
285 285 parent_group_path + RepoGroup.url_sep() + group_name)
286 286 else:
287 287 group_name_full = group_name
288 288
289 289 value['group_name'] = group_name
290 290 value['group_name_full'] = group_name_full
291 291 value['group_parent_id'] = group_parent_id
292 292 return value
293 293
294 294 def validate_python(self, value, state):
295 295
296 296 old_group_name = None
297 297 group_name = value.get('group_name')
298 298 group_name_full = value.get('group_name_full')
299 299 group_parent_id = safe_int(value.get('group_parent_id'))
300 300 if group_parent_id == -1:
301 301 group_parent_id = None
302 302
303 303 group_obj = RepoGroup.get(old_data.get('group_id'))
304 304 parent_group_changed = False
305 305 if edit:
306 306 old_group_name = group_obj.group_name
307 307 old_group_parent_id = group_obj.group_parent_id
308 308
309 309 if group_parent_id != old_group_parent_id:
310 310 parent_group_changed = True
311 311
312 312 # TODO: mikhail: the following if statement is not reached
313 313 # since group_parent_id's OneOf validation fails before.
314 314 # Can be removed.
315 315
316 316 # check against setting a parent of self
317 317 parent_of_self = (
318 318 old_data['group_id'] == group_parent_id
319 319 if group_parent_id else False
320 320 )
321 321 if parent_of_self:
322 322 msg = M(self, 'group_parent_id', state)
323 323 raise formencode.Invalid(
324 324 msg, value, state, error_dict={'group_parent_id': msg}
325 325 )
326 326
327 327 # group we're moving current group inside
328 328 child_group = None
329 329 if group_parent_id:
330 330 child_group = RepoGroup.query().filter(
331 331 RepoGroup.group_id == group_parent_id).scalar()
332 332
333 333 # do a special check that we cannot move a group to one of
334 334 # it's children
335 335 if edit and child_group:
336 336 parents = [x.group_id for x in child_group.parents]
337 337 move_to_children = old_data['group_id'] in parents
338 338 if move_to_children:
339 339 msg = M(self, 'group_parent_id', state)
340 340 raise formencode.Invalid(
341 341 msg, value, state, error_dict={'group_parent_id': msg})
342 342
343 343 # Check if we have permission to store in the parent.
344 344 # Only check if the parent group changed.
345 345 if parent_group_changed:
346 346 if child_group is None:
347 347 if not can_create_in_root:
348 348 msg = M(self, 'permission_denied_root', state)
349 349 raise formencode.Invalid(
350 350 msg, value, state,
351 351 error_dict={'group_parent_id': msg})
352 352 else:
353 353 valid = HasRepoGroupPermissionAny('group.admin')
354 354 forbidden = not valid(
355 355 child_group.group_name, 'can create group validator')
356 356 if forbidden:
357 357 msg = M(self, 'permission_denied', state)
358 358 raise formencode.Invalid(
359 359 msg, value, state,
360 360 error_dict={'group_parent_id': msg})
361 361
362 362 # if we change the name or it's new group, check for existing names
363 363 # or repositories with the same name
364 364 if old_group_name != group_name_full or not edit:
365 365 # check group
366 366 gr = RepoGroup.get_by_group_name(group_name_full)
367 367 if gr:
368 368 msg = M(self, 'group_exists', state, group_name=group_name)
369 369 raise formencode.Invalid(
370 370 msg, value, state, error_dict={'group_name': msg})
371 371
372 372 # check for same repo
373 373 repo = Repository.get_by_repo_name(group_name_full)
374 374 if repo:
375 375 msg = M(self, 'repo_exists', state, group_name=group_name)
376 376 raise formencode.Invalid(
377 377 msg, value, state, error_dict={'group_name': msg})
378 378
379 379 return _validator
380 380
381 381
382 382 def ValidPassword():
383 383 class _validator(formencode.validators.FancyValidator):
384 384 messages = {
385 385 'invalid_password':
386 386 _(u'Invalid characters (non-ascii) in password')
387 387 }
388 388
389 389 def validate_python(self, value, state):
390 390 try:
391 391 (value or '').decode('ascii')
392 392 except UnicodeError:
393 393 msg = M(self, 'invalid_password', state)
394 394 raise formencode.Invalid(msg, value, state,)
395 395 return _validator
396 396
397 397
398 398 def ValidOldPassword(username):
399 399 class _validator(formencode.validators.FancyValidator):
400 400 messages = {
401 401 'invalid_password': _(u'Invalid old password')
402 402 }
403 403
404 404 def validate_python(self, value, state):
405 405 from rhodecode.authentication.base import authenticate, HTTP_TYPE
406 406 if not authenticate(username, value, '', HTTP_TYPE):
407 407 msg = M(self, 'invalid_password', state)
408 408 raise formencode.Invalid(
409 409 msg, value, state, error_dict={'current_password': msg}
410 410 )
411 411 return _validator
412 412
413 413
414 414 def ValidPasswordsMatch(
415 415 passwd='new_password', passwd_confirmation='password_confirmation'):
416 416 class _validator(formencode.validators.FancyValidator):
417 417 messages = {
418 418 'password_mismatch': _(u'Passwords do not match'),
419 419 }
420 420
421 421 def validate_python(self, value, state):
422 422
423 423 pass_val = value.get('password') or value.get(passwd)
424 424 if pass_val != value[passwd_confirmation]:
425 425 msg = M(self, 'password_mismatch', state)
426 426 raise formencode.Invalid(
427 427 msg, value, state,
428 428 error_dict={passwd: msg, passwd_confirmation: msg}
429 429 )
430 430 return _validator
431 431
432 432
433 433 def ValidAuth():
434 434 class _validator(formencode.validators.FancyValidator):
435 435 messages = {
436 436 'invalid_password': _(u'invalid password'),
437 437 'invalid_username': _(u'invalid user name'),
438 438 'disabled_account': _(u'Your account is disabled')
439 439 }
440 440
441 441 def validate_python(self, value, state):
442 442 from rhodecode.authentication.base import authenticate, HTTP_TYPE
443 443
444 444 password = value['password']
445 445 username = value['username']
446 446
447 447 if not authenticate(username, password, '', HTTP_TYPE,
448 448 skip_missing=True):
449 449 user = User.get_by_username(username)
450 450 if user and not user.active:
451 451 log.warning('user %s is disabled', username)
452 452 msg = M(self, 'disabled_account', state)
453 453 raise formencode.Invalid(
454 454 msg, value, state, error_dict={'username': msg}
455 455 )
456 456 else:
457 457 log.warning('user `%s` failed to authenticate', username)
458 458 msg = M(self, 'invalid_username', state)
459 459 msg2 = M(self, 'invalid_password', state)
460 460 raise formencode.Invalid(
461 461 msg, value, state,
462 462 error_dict={'username': msg, 'password': msg2}
463 463 )
464 464 return _validator
465 465
466 466
467 467 def ValidAuthToken():
468 468 class _validator(formencode.validators.FancyValidator):
469 469 messages = {
470 470 'invalid_token': _(u'Token mismatch')
471 471 }
472 472
473 473 def validate_python(self, value, state):
474 474 if value != authentication_token():
475 475 msg = M(self, 'invalid_token', state)
476 476 raise formencode.Invalid(msg, value, state)
477 477 return _validator
478 478
479 479
480 480 def ValidRepoName(edit=False, old_data={}):
481 481 class _validator(formencode.validators.FancyValidator):
482 482 messages = {
483 483 'invalid_repo_name':
484 484 _(u'Repository name %(repo)s is disallowed'),
485 485 # top level
486 486 'repository_exists': _(u'Repository with name %(repo)s '
487 487 u'already exists'),
488 488 'group_exists': _(u'Repository group with name "%(repo)s" '
489 489 u'already exists'),
490 490 # inside a group
491 491 'repository_in_group_exists': _(u'Repository with name %(repo)s '
492 492 u'exists in group "%(group)s"'),
493 493 'group_in_group_exists': _(
494 494 u'Repository group with name "%(repo)s" '
495 495 u'exists in group "%(group)s"'),
496 496 }
497 497
498 498 def _to_python(self, value, state):
499 499 repo_name = repo_name_slug(value.get('repo_name', ''))
500 500 repo_group = value.get('repo_group')
501 501 if repo_group:
502 502 gr = RepoGroup.get(repo_group)
503 503 group_path = gr.full_path
504 504 group_name = gr.group_name
505 505 # value needs to be aware of group name in order to check
506 506 # db key This is an actual just the name to store in the
507 507 # database
508 508 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
509 509 else:
510 510 group_name = group_path = ''
511 511 repo_name_full = repo_name
512 512
513 513 value['repo_name'] = repo_name
514 514 value['repo_name_full'] = repo_name_full
515 515 value['group_path'] = group_path
516 516 value['group_name'] = group_name
517 517 return value
518 518
519 519 def validate_python(self, value, state):
520 520
521 521 repo_name = value.get('repo_name')
522 522 repo_name_full = value.get('repo_name_full')
523 523 group_path = value.get('group_path')
524 524 group_name = value.get('group_name')
525 525
526 526 if repo_name in [ADMIN_PREFIX, '']:
527 527 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
528 528 raise formencode.Invalid(
529 529 msg, value, state, error_dict={'repo_name': msg})
530 530
531 531 rename = old_data.get('repo_name') != repo_name_full
532 532 create = not edit
533 533 if rename or create:
534 534
535 535 if group_path:
536 536 if Repository.get_by_repo_name(repo_name_full):
537 537 msg = M(self, 'repository_in_group_exists', state,
538 538 repo=repo_name, group=group_name)
539 539 raise formencode.Invalid(
540 540 msg, value, state, error_dict={'repo_name': msg})
541 541 if RepoGroup.get_by_group_name(repo_name_full):
542 542 msg = M(self, 'group_in_group_exists', state,
543 543 repo=repo_name, group=group_name)
544 544 raise formencode.Invalid(
545 545 msg, value, state, error_dict={'repo_name': msg})
546 546 else:
547 547 if RepoGroup.get_by_group_name(repo_name_full):
548 548 msg = M(self, 'group_exists', state, repo=repo_name)
549 549 raise formencode.Invalid(
550 550 msg, value, state, error_dict={'repo_name': msg})
551 551
552 552 if Repository.get_by_repo_name(repo_name_full):
553 553 msg = M(
554 554 self, 'repository_exists', state, repo=repo_name)
555 555 raise formencode.Invalid(
556 556 msg, value, state, error_dict={'repo_name': msg})
557 557 return value
558 558 return _validator
559 559
560 560
561 561 def ValidForkName(*args, **kwargs):
562 562 return ValidRepoName(*args, **kwargs)
563 563
564 564
565 565 def SlugifyName():
566 566 class _validator(formencode.validators.FancyValidator):
567 567
568 568 def _to_python(self, value, state):
569 569 return repo_name_slug(value)
570 570
571 571 def validate_python(self, value, state):
572 572 pass
573 573
574 574 return _validator
575 575
576 576
577 577 def CannotHaveGitSuffix():
578 578 class _validator(formencode.validators.FancyValidator):
579 579 messages = {
580 580 'has_git_suffix':
581 581 _(u'Repository name cannot end with .git'),
582 582 }
583 583
584 584 def _to_python(self, value, state):
585 585 return value
586 586
587 587 def validate_python(self, value, state):
588 588 if value and value.endswith('.git'):
589 589 msg = M(
590 590 self, 'has_git_suffix', state)
591 591 raise formencode.Invalid(
592 592 msg, value, state, error_dict={'repo_name': msg})
593 593
594 594 return _validator
595 595
596 596
597 597 def ValidCloneUri():
598 598 class InvalidCloneUrl(Exception):
599 599 allowed_prefixes = ()
600 600
601 601 def url_handler(repo_type, url):
602 602 config = make_db_config(clear_session=False)
603 603 if repo_type == 'hg':
604 604 allowed_prefixes = ('http', 'svn+http', 'git+http')
605 605
606 606 if 'http' in url[:4]:
607 607 # initially check if it's at least the proper URL
608 608 # or does it pass basic auth
609 609 MercurialRepository.check_url(url, config)
610 610 elif 'svn+http' in url[:8]: # svn->hg import
611 611 SubversionRepository.check_url(url, config)
612 612 elif 'git+http' in url[:8]: # git->hg import
613 613 raise NotImplementedError()
614 614 else:
615 615 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
616 616 'Allowed url must start with one of %s'
617 617 % (url, ','.join(allowed_prefixes)))
618 618 exc.allowed_prefixes = allowed_prefixes
619 619 raise exc
620 620
621 621 elif repo_type == 'git':
622 622 allowed_prefixes = ('http', 'svn+http', 'hg+http')
623 623 if 'http' in url[:4]:
624 624 # initially check if it's at least the proper URL
625 625 # or does it pass basic auth
626 626 GitRepository.check_url(url, config)
627 627 elif 'svn+http' in url[:8]: # svn->git import
628 628 raise NotImplementedError()
629 629 elif 'hg+http' in url[:8]: # hg->git import
630 630 raise NotImplementedError()
631 631 else:
632 632 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
633 633 'Allowed url must start with one of %s'
634 634 % (url, ','.join(allowed_prefixes)))
635 635 exc.allowed_prefixes = allowed_prefixes
636 636 raise exc
637 637
638 638 class _validator(formencode.validators.FancyValidator):
639 639 messages = {
640 640 'clone_uri': _(u'invalid clone url for %(rtype)s repository'),
641 641 'invalid_clone_uri': _(
642 642 u'Invalid clone url, provide a valid clone '
643 643 u'url starting with one of %(allowed_prefixes)s')
644 644 }
645 645
646 646 def validate_python(self, value, state):
647 647 repo_type = value.get('repo_type')
648 648 url = value.get('clone_uri')
649 649
650 650 if url:
651 651 try:
652 652 url_handler(repo_type, url)
653 653 except InvalidCloneUrl as e:
654 654 log.warning(e)
655 655 msg = M(self, 'invalid_clone_uri', rtype=repo_type,
656 656 allowed_prefixes=','.join(e.allowed_prefixes))
657 657 raise formencode.Invalid(msg, value, state,
658 658 error_dict={'clone_uri': msg})
659 659 except Exception:
660 660 log.exception('Url validation failed')
661 661 msg = M(self, 'clone_uri', rtype=repo_type)
662 662 raise formencode.Invalid(msg, value, state,
663 663 error_dict={'clone_uri': msg})
664 664 return _validator
665 665
666 666
667 667 def ValidForkType(old_data={}):
668 668 class _validator(formencode.validators.FancyValidator):
669 669 messages = {
670 670 'invalid_fork_type': _(u'Fork have to be the same type as parent')
671 671 }
672 672
673 673 def validate_python(self, value, state):
674 674 if old_data['repo_type'] != value:
675 675 msg = M(self, 'invalid_fork_type', state)
676 676 raise formencode.Invalid(
677 677 msg, value, state, error_dict={'repo_type': msg}
678 678 )
679 679 return _validator
680 680
681 681
682 682 def CanWriteGroup(old_data=None):
683 683 class _validator(formencode.validators.FancyValidator):
684 684 messages = {
685 685 'permission_denied': _(
686 686 u"You do not have the permission "
687 687 u"to create repositories in this group."),
688 688 'permission_denied_root': _(
689 689 u"You do not have the permission to store repositories in "
690 690 u"the root location.")
691 691 }
692 692
693 693 def _to_python(self, value, state):
694 694 # root location
695 695 if value in [-1, "-1"]:
696 696 return None
697 697 return value
698 698
699 699 def validate_python(self, value, state):
700 700 gr = RepoGroup.get(value)
701 701 gr_name = gr.group_name if gr else None # None means ROOT location
702 702 # create repositories with write permission on group is set to true
703 703 create_on_write = HasPermissionAny(
704 704 'hg.create.write_on_repogroup.true')()
705 705 group_admin = HasRepoGroupPermissionAny('group.admin')(
706 706 gr_name, 'can write into group validator')
707 707 group_write = HasRepoGroupPermissionAny('group.write')(
708 708 gr_name, 'can write into group validator')
709 709 forbidden = not (group_admin or (group_write and create_on_write))
710 710 can_create_repos = HasPermissionAny(
711 711 'hg.admin', 'hg.create.repository')
712 712 gid = (old_data['repo_group'].get('group_id')
713 713 if (old_data and 'repo_group' in old_data) else None)
714 714 value_changed = gid != safe_int(value)
715 715 new = not old_data
716 716 # do check if we changed the value, there's a case that someone got
717 717 # revoked write permissions to a repository, he still created, we
718 718 # don't need to check permission if he didn't change the value of
719 719 # groups in form box
720 720 if value_changed or new:
721 721 # parent group need to be existing
722 722 if gr and forbidden:
723 723 msg = M(self, 'permission_denied', state)
724 724 raise formencode.Invalid(
725 725 msg, value, state, error_dict={'repo_type': msg}
726 726 )
727 727 # check if we can write to root location !
728 728 elif gr is None and not can_create_repos():
729 729 msg = M(self, 'permission_denied_root', state)
730 730 raise formencode.Invalid(
731 731 msg, value, state, error_dict={'repo_type': msg}
732 732 )
733 733
734 734 return _validator
735 735
736 736
737 737 def ValidPerms(type_='repo'):
738 738 if type_ == 'repo_group':
739 739 EMPTY_PERM = 'group.none'
740 740 elif type_ == 'repo':
741 741 EMPTY_PERM = 'repository.none'
742 742 elif type_ == 'user_group':
743 743 EMPTY_PERM = 'usergroup.none'
744 744
745 745 class _validator(formencode.validators.FancyValidator):
746 746 messages = {
747 747 'perm_new_member_name':
748 748 _(u'This username or user group name is not valid')
749 749 }
750 750
751 751 def _to_python(self, value, state):
752 752 perm_updates = OrderedSet()
753 753 perm_additions = OrderedSet()
754 754 perm_deletions = OrderedSet()
755 755 # build a list of permission to update/delete and new permission
756 756
757 757 # Read the perm_new_member/perm_del_member attributes and group
758 758 # them by they IDs
759 759 new_perms_group = defaultdict(dict)
760 760 del_perms_group = defaultdict(dict)
761 761 for k, v in value.copy().iteritems():
762 762 if k.startswith('perm_del_member'):
763 763 # delete from org storage so we don't process that later
764 764 del value[k]
765 765 # part is `id`, `type`
766 766 _type, part = k.split('perm_del_member_')
767 767 args = part.split('_')
768 768 if len(args) == 2:
769 769 _key, pos = args
770 770 del_perms_group[pos][_key] = v
771 771 if k.startswith('perm_new_member'):
772 772 # delete from org storage so we don't process that later
773 773 del value[k]
774 774 # part is `id`, `type`, `perm`
775 775 _type, part = k.split('perm_new_member_')
776 776 args = part.split('_')
777 777 if len(args) == 2:
778 778 _key, pos = args
779 779 new_perms_group[pos][_key] = v
780 780
781 781 # store the deletes
782 782 for k in sorted(del_perms_group.keys()):
783 783 perm_dict = del_perms_group[k]
784 784 del_member = perm_dict.get('id')
785 785 del_type = perm_dict.get('type')
786 786 if del_member and del_type:
787 perm_deletions.add((del_member, None, del_type))
787 perm_deletions.add(
788 (del_member, None, del_type))
788 789
789 790 # store additions in order of how they were added in web form
790 791 for k in sorted(new_perms_group.keys()):
791 792 perm_dict = new_perms_group[k]
792 793 new_member = perm_dict.get('id')
793 794 new_type = perm_dict.get('type')
794 795 new_perm = perm_dict.get('perm')
795 796 if new_member and new_perm and new_type:
796 perm_additions.add((new_member, new_perm, new_type))
797 perm_additions.add(
798 (new_member, new_perm, new_type))
797 799
798 800 # get updates of permissions
799 801 # (read the existing radio button states)
802 default_user_id = User.get_default_user().user_id
800 803 for k, update_value in value.iteritems():
801 804 if k.startswith('u_perm_') or k.startswith('g_perm_'):
802 805 member = k[7:]
803 806 update_type = {'u': 'user',
804 807 'g': 'users_group'}[k[0]]
805 if member == User.DEFAULT_USER:
808
809 if safe_int(member) == default_user_id:
806 810 if str2bool(value.get('repo_private')):
807 # set none for default when updating to
808 # private repo protects agains form manipulation
811 # prevent from updating default user permissions
812 # when this repository is marked as private
809 813 update_value = EMPTY_PERM
810 perm_updates.add((member, update_value, update_type))
811 # check the deletes
812 814
813 value['perm_additions'] = list(perm_additions)
815 perm_updates.add(
816 (member, update_value, update_type))
817
818 value['perm_additions'] = [] # propagated later
814 819 value['perm_updates'] = list(perm_updates)
815 820 value['perm_deletions'] = list(perm_deletions)
816 821
817 # validate users they exist and they are active !
818 for member_id, _perm, member_type in perm_additions:
822 updates_map = dict(
823 (x[0], (x[1], x[2])) for x in value['perm_updates'])
824 # make sure Additions don't override updates.
825 for member_id, perm, member_type in list(perm_additions):
826 if member_id in updates_map:
827 perm = updates_map[member_id][0]
828 value['perm_additions'].append((member_id, perm, member_type))
829
830 # on new entries validate users they exist and they are active !
831 # this leaves feedback to the form
819 832 try:
820 833 if member_type == 'user':
821 self.user_db = User.query()\
834 User.query()\
822 835 .filter(User.active == true())\
823 836 .filter(User.user_id == member_id).one()
824 837 if member_type == 'users_group':
825 self.user_db = UserGroup.query()\
838 UserGroup.query()\
826 839 .filter(UserGroup.users_group_active == true())\
827 840 .filter(UserGroup.users_group_id == member_id)\
828 841 .one()
829 842
830 843 except Exception:
831 844 log.exception('Updated permission failed: org_exc:')
832 845 msg = M(self, 'perm_new_member_type', state)
833 846 raise formencode.Invalid(
834 847 msg, value, state, error_dict={
835 848 'perm_new_member_name': msg}
836 849 )
837 850 return value
838 851 return _validator
839 852
840 853
841 854 def ValidSettings():
842 855 class _validator(formencode.validators.FancyValidator):
843 856 def _to_python(self, value, state):
844 857 # settings form for users that are not admin
845 858 # can't edit certain parameters, it's extra backup if they mangle
846 859 # with forms
847 860
848 861 forbidden_params = [
849 862 'user', 'repo_type', 'repo_enable_locking',
850 863 'repo_enable_downloads', 'repo_enable_statistics'
851 864 ]
852 865
853 866 for param in forbidden_params:
854 867 if param in value:
855 868 del value[param]
856 869 return value
857 870
858 871 def validate_python(self, value, state):
859 872 pass
860 873 return _validator
861 874
862 875
863 876 def ValidPath():
864 877 class _validator(formencode.validators.FancyValidator):
865 878 messages = {
866 879 'invalid_path': _(u'This is not a valid path')
867 880 }
868 881
869 882 def validate_python(self, value, state):
870 883 if not os.path.isdir(value):
871 884 msg = M(self, 'invalid_path', state)
872 885 raise formencode.Invalid(
873 886 msg, value, state, error_dict={'paths_root_path': msg}
874 887 )
875 888 return _validator
876 889
877 890
878 891 def UniqSystemEmail(old_data={}):
879 892 class _validator(formencode.validators.FancyValidator):
880 893 messages = {
881 894 'email_taken': _(u'This e-mail address is already taken')
882 895 }
883 896
884 897 def _to_python(self, value, state):
885 898 return value.lower()
886 899
887 900 def validate_python(self, value, state):
888 901 if (old_data.get('email') or '').lower() != value:
889 902 user = User.get_by_email(value, case_insensitive=True)
890 903 if user:
891 904 msg = M(self, 'email_taken', state)
892 905 raise formencode.Invalid(
893 906 msg, value, state, error_dict={'email': msg}
894 907 )
895 908 return _validator
896 909
897 910
898 911 def ValidSystemEmail():
899 912 class _validator(formencode.validators.FancyValidator):
900 913 messages = {
901 914 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
902 915 }
903 916
904 917 def _to_python(self, value, state):
905 918 return value.lower()
906 919
907 920 def validate_python(self, value, state):
908 921 user = User.get_by_email(value, case_insensitive=True)
909 922 if user is None:
910 923 msg = M(self, 'non_existing_email', state, email=value)
911 924 raise formencode.Invalid(
912 925 msg, value, state, error_dict={'email': msg}
913 926 )
914 927
915 928 return _validator
916 929
917 930
918 931 def NotReviewedRevisions(repo_id):
919 932 class _validator(formencode.validators.FancyValidator):
920 933 messages = {
921 934 'rev_already_reviewed':
922 935 _(u'Revisions %(revs)s are already part of pull request '
923 936 u'or have set status'),
924 937 }
925 938
926 939 def validate_python(self, value, state):
927 940 # check revisions if they are not reviewed, or a part of another
928 941 # pull request
929 942 statuses = ChangesetStatus.query()\
930 943 .filter(ChangesetStatus.revision.in_(value))\
931 944 .filter(ChangesetStatus.repo_id == repo_id)\
932 945 .all()
933 946
934 947 errors = []
935 948 for status in statuses:
936 949 if status.pull_request_id:
937 950 errors.append(['pull_req', status.revision[:12]])
938 951 elif status.status:
939 952 errors.append(['status', status.revision[:12]])
940 953
941 954 if errors:
942 955 revs = ','.join([x[1] for x in errors])
943 956 msg = M(self, 'rev_already_reviewed', state, revs=revs)
944 957 raise formencode.Invalid(
945 958 msg, value, state, error_dict={'revisions': revs})
946 959
947 960 return _validator
948 961
949 962
950 963 def ValidIp():
951 964 class _validator(CIDR):
952 965 messages = {
953 966 'badFormat': _(u'Please enter a valid IPv4 or IpV6 address'),
954 967 'illegalBits': _(
955 968 u'The network size (bits) must be within the range '
956 969 u'of 0-32 (not %(bits)r)'),
957 970 }
958 971
959 972 # we ovveride the default to_python() call
960 973 def to_python(self, value, state):
961 974 v = super(_validator, self).to_python(value, state)
962 975 v = v.strip()
963 976 net = ipaddress.ip_network(address=v, strict=False)
964 977 return str(net)
965 978
966 979 def validate_python(self, value, state):
967 980 try:
968 981 addr = value.strip()
969 982 # this raises an ValueError if address is not IpV4 or IpV6
970 983 ipaddress.ip_network(addr, strict=False)
971 984 except ValueError:
972 985 raise formencode.Invalid(self.message('badFormat', state),
973 986 value, state)
974 987
975 988 return _validator
976 989
977 990
978 991 def FieldKey():
979 992 class _validator(formencode.validators.FancyValidator):
980 993 messages = {
981 994 'badFormat': _(
982 995 u'Key name can only consist of letters, '
983 996 u'underscore, dash or numbers'),
984 997 }
985 998
986 999 def validate_python(self, value, state):
987 1000 if not re.match('[a-zA-Z0-9_-]+$', value):
988 1001 raise formencode.Invalid(self.message('badFormat', state),
989 1002 value, state)
990 1003 return _validator
991 1004
992 1005
993 1006 def ValidAuthPlugins():
994 1007 class _validator(formencode.validators.FancyValidator):
995 1008 messages = {
996 1009 'import_duplicate': _(
997 1010 u'Plugins %(loaded)s and %(next_to_load)s '
998 1011 u'both export the same name'),
999 1012 'missing_includeme': _(
1000 1013 u'The plugin "%(plugin_id)s" is missing an includeme '
1001 1014 u'function.'),
1002 1015 'import_error': _(
1003 1016 u'Can not load plugin "%(plugin_id)s"'),
1004 1017 'no_plugin': _(
1005 1018 u'No plugin available with ID "%(plugin_id)s"'),
1006 1019 }
1007 1020
1008 1021 def _to_python(self, value, state):
1009 1022 # filter empty values
1010 1023 return filter(lambda s: s not in [None, ''], value)
1011 1024
1012 1025 def _validate_legacy_plugin_id(self, plugin_id, value, state):
1013 1026 """
1014 1027 Validates that the plugin import works. It also checks that the
1015 1028 plugin has an includeme attribute.
1016 1029 """
1017 1030 try:
1018 1031 plugin = _import_legacy_plugin(plugin_id)
1019 1032 except Exception as e:
1020 1033 log.exception(
1021 1034 'Exception during import of auth legacy plugin "{}"'
1022 1035 .format(plugin_id))
1023 1036 msg = M(self, 'import_error', plugin_id=plugin_id)
1024 1037 raise formencode.Invalid(msg, value, state)
1025 1038
1026 1039 if not hasattr(plugin, 'includeme'):
1027 1040 msg = M(self, 'missing_includeme', plugin_id=plugin_id)
1028 1041 raise formencode.Invalid(msg, value, state)
1029 1042
1030 1043 return plugin
1031 1044
1032 1045 def _validate_plugin_id(self, plugin_id, value, state):
1033 1046 """
1034 1047 Plugins are already imported during app start up. Therefore this
1035 1048 validation only retrieves the plugin from the plugin registry and
1036 1049 if it returns something not None everything is OK.
1037 1050 """
1038 1051 plugin = loadplugin(plugin_id)
1039 1052
1040 1053 if plugin is None:
1041 1054 msg = M(self, 'no_plugin', plugin_id=plugin_id)
1042 1055 raise formencode.Invalid(msg, value, state)
1043 1056
1044 1057 return plugin
1045 1058
1046 1059 def validate_python(self, value, state):
1047 1060 unique_names = {}
1048 1061 for plugin_id in value:
1049 1062
1050 1063 # Validate legacy or normal plugin.
1051 1064 if plugin_id.startswith(legacy_plugin_prefix):
1052 1065 plugin = self._validate_legacy_plugin_id(
1053 1066 plugin_id, value, state)
1054 1067 else:
1055 1068 plugin = self._validate_plugin_id(plugin_id, value, state)
1056 1069
1057 1070 # Only allow unique plugin names.
1058 1071 if plugin.name in unique_names:
1059 1072 msg = M(self, 'import_duplicate', state,
1060 1073 loaded=unique_names[plugin.name],
1061 1074 next_to_load=plugin)
1062 1075 raise formencode.Invalid(msg, value, state)
1063 1076 unique_names[plugin.name] = plugin
1064 1077
1065 1078 return _validator
1066 1079
1067 1080
1068 1081 def ValidPattern():
1069 1082
1070 1083 class _Validator(formencode.validators.FancyValidator):
1071 1084
1072 1085 def _to_python(self, value, state):
1073 1086 patterns = []
1074 1087
1075 1088 prefix = 'new_pattern'
1076 1089 for name, v in value.iteritems():
1077 1090 pattern_name = '_'.join((prefix, 'pattern'))
1078 1091 if name.startswith(pattern_name):
1079 1092 new_item_id = name[len(pattern_name)+1:]
1080 1093
1081 1094 def _field(name):
1082 1095 return '%s_%s_%s' % (prefix, name, new_item_id)
1083 1096
1084 1097 values = {
1085 1098 'issuetracker_pat': value.get(_field('pattern')),
1086 1099 'issuetracker_pat': value.get(_field('pattern')),
1087 1100 'issuetracker_url': value.get(_field('url')),
1088 1101 'issuetracker_pref': value.get(_field('prefix')),
1089 1102 'issuetracker_desc': value.get(_field('description'))
1090 1103 }
1091 1104 new_uid = md5(values['issuetracker_pat'])
1092 1105
1093 1106 has_required_fields = (
1094 1107 values['issuetracker_pat']
1095 1108 and values['issuetracker_url'])
1096 1109
1097 1110 if has_required_fields:
1098 1111 settings = [
1099 1112 ('_'.join((key, new_uid)), values[key], 'unicode')
1100 1113 for key in values]
1101 1114 patterns.append(settings)
1102 1115
1103 1116 value['patterns'] = patterns
1104 1117 delete_patterns = value.get('uid') or []
1105 1118 if not isinstance(delete_patterns, (list, tuple)):
1106 1119 delete_patterns = [delete_patterns]
1107 1120 value['delete_patterns'] = delete_patterns
1108 1121 return value
1109 1122 return _Validator
@@ -1,118 +1,118 b''
1 1
2 2 /******************************************************************************
3 3 * *
4 4 * DO NOT CHANGE THIS FILE MANUALLY *
5 5 * *
6 6 * *
7 7 * This file is automatically generated when the app starts up with *
8 8 * generate_js_files = true *
9 9 * *
10 10 * To add a route here pass jsroute=True to the route definition in the app *
11 11 * *
12 12 ******************************************************************************/
13 13 function registerRCRoutes() {
14 14 // routes registration
15 15 pyroutes.register('home', '/', []);
16 16 pyroutes.register('new_repo', '/_admin/create_repository', []);
17 17 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
18 18 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
19 19 pyroutes.register('gists', '/_admin/gists', []);
20 20 pyroutes.register('new_gist', '/_admin/gists/new', []);
21 21 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
22 22 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
23 23 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
24 24 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
25 25 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/default-reviewers', ['repo_name']);
26 26 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
27 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
28 27 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
29 28 pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']);
30 29 pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
31 30 pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']);
32 31 pyroutes.register('compare_url', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
33 32 pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']);
34 33 pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']);
35 34 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
36 35 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
37 36 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
38 37 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
39 38 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
40 39 pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
41 40 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
42 41 pyroutes.register('changelog_home', '/%(repo_name)s/changelog', ['repo_name']);
43 42 pyroutes.register('changelog_file_home', '/%(repo_name)s/changelog/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
44 43 pyroutes.register('changelog_elements', '/%(repo_name)s/changelog_details', ['repo_name']);
45 44 pyroutes.register('files_home', '/%(repo_name)s/files/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
46 45 pyroutes.register('files_history_home', '/%(repo_name)s/history/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
47 46 pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
48 47 pyroutes.register('files_annotate_home', '/%(repo_name)s/annotate/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
49 48 pyroutes.register('files_annotate_previous', '/%(repo_name)s/annotate-previous/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
50 49 pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
51 50 pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
52 51 pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
53 52 pyroutes.register('summary_home_slash', '/%(repo_name)s/', ['repo_name']);
54 53 pyroutes.register('summary_home', '/%(repo_name)s', ['repo_name']);
55 54 pyroutes.register('favicon', '/favicon.ico', []);
56 55 pyroutes.register('robots', '/robots.txt', []);
57 56 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
58 57 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
59 58 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
60 59 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
61 60 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
62 61 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
63 62 pyroutes.register('repo_group_integrations_home', '%(repo_group_name)s/settings/integrations', ['repo_group_name']);
64 63 pyroutes.register('repo_group_integrations_list', '%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
65 64 pyroutes.register('repo_group_integrations_new', '%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
66 65 pyroutes.register('repo_group_integrations_create', '%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
67 66 pyroutes.register('repo_group_integrations_edit', '%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
68 67 pyroutes.register('repo_integrations_home', '%(repo_name)s/settings/integrations', ['repo_name']);
69 68 pyroutes.register('repo_integrations_list', '%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
70 69 pyroutes.register('repo_integrations_new', '%(repo_name)s/settings/integrations/new', ['repo_name']);
71 70 pyroutes.register('repo_integrations_create', '%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
72 71 pyroutes.register('repo_integrations_edit', '%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
73 72 pyroutes.register('ops_ping', '_admin/ops/ping', []);
74 73 pyroutes.register('admin_settings_open_source', '_admin/settings/open_source', []);
75 74 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '_admin/settings/vcs/svn_generate_cfg', []);
76 75 pyroutes.register('admin_settings_system', '_admin/settings/system', []);
77 76 pyroutes.register('admin_settings_system_update', '_admin/settings/system/updates', []);
78 77 pyroutes.register('admin_settings_sessions', '_admin/settings/sessions', []);
79 78 pyroutes.register('admin_settings_sessions_cleanup', '_admin/settings/sessions/cleanup', []);
80 79 pyroutes.register('users', '_admin/users', []);
81 80 pyroutes.register('users_data', '_admin/users_data', []);
82 81 pyroutes.register('edit_user_auth_tokens', '_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
83 82 pyroutes.register('edit_user_auth_tokens_add', '_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
84 83 pyroutes.register('edit_user_auth_tokens_delete', '_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
85 84 pyroutes.register('edit_user_groups_management', '_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
86 85 pyroutes.register('edit_user_groups_management_updates', '_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
87 86 pyroutes.register('edit_user_audit_logs', '_admin/users/%(user_id)s/edit/audit', ['user_id']);
88 87 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
89 88 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
90 89 pyroutes.register('channelstream_proxy', '/_channelstream', []);
91 90 pyroutes.register('login', '/_admin/login', []);
92 91 pyroutes.register('logout', '/_admin/logout', []);
93 92 pyroutes.register('register', '/_admin/register', []);
94 93 pyroutes.register('reset_password', '/_admin/password_reset', []);
95 94 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
96 95 pyroutes.register('user_autocomplete_data', '/_users', []);
97 96 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
98 97 pyroutes.register('repo_list_data', '/_repos', []);
99 98 pyroutes.register('goto_switcher_data', '/_goto_data', []);
100 99 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
101 100 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
101 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
102 102 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
103 103 pyroutes.register('repo_maintenance', '/%(repo_name)s/maintenance', ['repo_name']);
104 104 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/maintenance/execute', ['repo_name']);
105 105 pyroutes.register('strip', '/%(repo_name)s/strip', ['repo_name']);
106 106 pyroutes.register('strip_check', '/%(repo_name)s/strip_check', ['repo_name']);
107 107 pyroutes.register('strip_execute', '/%(repo_name)s/strip_execute', ['repo_name']);
108 108 pyroutes.register('search', '/_admin/search', []);
109 109 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
110 110 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
111 111 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
112 112 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
113 113 pyroutes.register('my_account_password_update', '/_admin/my_account/password', []);
114 114 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
115 115 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
116 116 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
117 117 pyroutes.register('apiv2', '/_admin/api', []);
118 118 }
@@ -1,95 +1,95 b''
1 1 ## -*- coding: utf-8 -*-
2 2 ##
3 3 ## See also repo_settings.html
4 4 ##
5 5 <%inherit file="/base/base.mako"/>
6 6
7 7 <%def name="title()">
8 8 ${_('%s repository settings') % c.repo_info.repo_name}
9 9 %if c.rhodecode_name:
10 10 &middot; ${h.branding(c.rhodecode_name)}
11 11 %endif
12 12 </%def>
13 13
14 14 <%def name="breadcrumbs_links()">
15 15 ${_('Settings')}
16 16 </%def>
17 17
18 18 <%def name="menu_bar_nav()">
19 19 ${self.menu_items(active='repositories')}
20 20 </%def>
21 21
22 22 <%def name="menu_bar_subnav()">
23 23 ${self.repo_menu(active='options')}
24 24 </%def>
25 25
26 26 <%def name="main_content()">
27 27 <%include file="/admin/repos/repo_edit_${c.active}.mako"/>
28 28 </%def>
29 29
30 30
31 31 <%def name="main()">
32 32 <div class="box">
33 33 <div class="title">
34 34 ${self.repo_page_title(c.rhodecode_db_repo)}
35 35 ${self.breadcrumbs()}
36 36 </div>
37 37
38 38 <div class="sidebar-col-wrapper scw-small">
39 39 <div class="sidebar">
40 40 <ul class="nav nav-pills nav-stacked">
41 41 <li class="${'active' if c.active=='settings' else ''}">
42 42 <a href="${h.route_path('edit_repo', repo_name=c.repo_name)}">${_('Settings')}</a>
43 43 </li>
44 44 <li class="${'active' if c.active=='permissions' else ''}">
45 <a href="${h.url('edit_repo_perms', repo_name=c.repo_name)}">${_('Permissions')}</a>
45 <a href="${h.route_path('edit_repo_perms', repo_name=c.repo_name)}">${_('Permissions')}</a>
46 46 </li>
47 47 <li class="${'active' if c.active=='advanced' else ''}">
48 48 <a href="${h.url('edit_repo_advanced', repo_name=c.repo_name)}">${_('Advanced')}</a>
49 49 </li>
50 50 <li class="${'active' if c.active=='vcs' else ''}">
51 51 <a href="${h.url('repo_vcs_settings', repo_name=c.repo_name)}">${_('VCS')}</a>
52 52 </li>
53 53 <li class="${'active' if c.active=='fields' else ''}">
54 54 <a href="${h.url('edit_repo_fields', repo_name=c.repo_name)}">${_('Extra Fields')}</a>
55 55 </li>
56 56 <li class="${'active' if c.active=='issuetracker' else ''}">
57 57 <a href="${h.url('repo_settings_issuetracker', repo_name=c.repo_name)}">${_('Issue Tracker')}</a>
58 58 </li>
59 59 <li class="${'active' if c.active=='caches' else ''}">
60 60 <a href="${h.route_path('edit_repo_caches', repo_name=c.repo_name)}">${_('Caches')}</a>
61 61 </li>
62 62 %if c.repo_info.repo_type != 'svn':
63 63 <li class="${'active' if c.active=='remote' else ''}">
64 64 <a href="${h.url('edit_repo_remote', repo_name=c.repo_name)}">${_('Remote')}</a>
65 65 </li>
66 66 %endif
67 67 <li class="${'active' if c.active=='statistics' else ''}">
68 68 <a href="${h.url('edit_repo_statistics', repo_name=c.repo_name)}">${_('Statistics')}</a>
69 69 </li>
70 70 <li class="${'active' if c.active=='integrations' else ''}">
71 71 <a href="${h.route_path('repo_integrations_home', repo_name=c.repo_name)}">${_('Integrations')}</a>
72 72 </li>
73 73 %if c.repo_info.repo_type != 'svn':
74 74 <li class="${'active' if c.active=='reviewers' else ''}">
75 75 <a href="${h.route_path('repo_reviewers', repo_name=c.repo_name)}">${_('Reviewer Rules')}</a>
76 76 </li>
77 77 %endif
78 78 <li class="${'active' if c.active=='maintenance' else ''}">
79 79 <a href="${h.route_path('repo_maintenance', repo_name=c.repo_name)}">${_('Maintenance')}</a>
80 80 </li>
81 81 <li class="${'active' if c.active=='strip' else ''}">
82 82 <a href="${h.route_path('strip', repo_name=c.repo_name)}">${_('Strip')}</a>
83 83 </li>
84 84
85 85 </ul>
86 86 </div>
87 87
88 88 <div class="main-content-full-width">
89 89 ${self.main_content()}
90 90 </div>
91 91
92 92 </div>
93 93 </div>
94 94
95 95 </%def> No newline at end of file
@@ -1,124 +1,123 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <div class="panel panel-default">
4 4 <div class="panel-heading">
5 5 <h3 class="panel-title">${_('Repository Permissions')}</h3>
6 6 </div>
7 7 <div class="panel-body">
8 ${h.secure_form(url('edit_repo_perms_update', repo_name=c.repo_name), method='put')}
9 ${h.hidden('repo_private')}
8 ${h.secure_form(h.route_path('edit_repo_perms', repo_name=c.repo_name), method='POST')}
10 9 <table id="permissions_manage" class="rctable permissions">
11 10 <tr>
12 11 <th class="td-radio">${_('None')}</th>
13 12 <th class="td-radio">${_('Read')}</th>
14 13 <th class="td-radio">${_('Write')}</th>
15 14 <th class="td-radio">${_('Admin')}</th>
16 15 <th class="td-owner">${_('User/User Group')}</th>
17 16 <th></th>
18 17 </tr>
19 18 ## USERS
20 19 %for _user in c.repo_info.permissions():
21 20 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
22 21 <tr class="perm_admin_row">
23 22 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
24 23 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
25 24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
26 25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
27 26 <td class="td-user">
28 27 ${base.gravatar(_user.email, 16)}
29 28 ${h.link_to_user(_user.username)}
30 29 %if getattr(_user, 'admin_row', None):
31 30 (${_('super admin')})
32 31 %endif
33 32 %if getattr(_user, 'owner_row', None):
34 33 (${_('owner')})
35 34 %endif
36 35 </td>
37 36 <td></td>
38 37 </tr>
39 38 %elif _user.username == h.DEFAULT_USER and c.repo_info.private:
40 39 <tr>
41 40 <td colspan="4">
42 41 <span class="private_repo_msg">
43 <strong>${_('private repository')}</strong>
42 <strong title="${_user.permission}">${_('private repository')}</strong>
44 43 </span>
45 44 </td>
46 45 <td class="private_repo_msg">
47 46 ${base.gravatar(h.DEFAULT_USER_EMAIL, 16)}
48 47 ${h.DEFAULT_USER} - ${_('only users/user groups explicitly added here will have access')}</td>
49 48 <td></td>
50 49 </tr>
51 50 %else:
52 51 <tr>
53 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.none')}</td>
54 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.read')}</td>
55 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.write')}</td>
56 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.admin')}</td>
52 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.none', checked=_user.permission=='repository.none')}</td>
53 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.read', checked=_user.permission=='repository.read')}</td>
54 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.write', checked=_user.permission=='repository.write')}</td>
55 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.admin', checked=_user.permission=='repository.admin')}</td>
57 56 <td class="td-user">
58 57 ${base.gravatar(_user.email, 16)}
59 58 <span class="user">
60 59 % if _user.username == h.DEFAULT_USER:
61 60 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
62 61 % else:
63 62 ${h.link_to_user(_user.username)}
64 63 % endif
65 64 </span>
66 65 </td>
67 66 <td class="td-action">
68 67 %if _user.username != h.DEFAULT_USER:
69 68 <span class="btn btn-link btn-danger revoke_perm"
70 69 member="${_user.user_id}" member_type="user">
71 70 <i class="icon-remove"></i> ${_('Revoke')}
72 71 </span>
73 72 %endif
74 73 </td>
75 74 </tr>
76 75 %endif
77 76 %endfor
78 77
79 78 ## USER GROUPS
80 79 %for _user_group in c.repo_info.permission_user_groups():
81 80 <tr>
82 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.none')}</td>
83 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.read')}</td>
84 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.write')}</td>
85 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.admin')}</td>
81 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.none', checked=_user_group.permission=='repository.none')}</td>
82 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.read', checked=_user_group.permission=='repository.read')}</td>
83 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.write', checked=_user_group.permission=='repository.write')}</td>
84 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.admin', checked=_user_group.permission=='repository.admin')}</td>
86 85 <td class="td-componentname">
87 86 <i class="icon-group" ></i>
88 87 %if h.HasPermissionAny('hg.admin')():
89 88 <a href="${h.url('edit_users_group',user_group_id=_user_group.users_group_id)}">
90 89 ${_user_group.users_group_name}
91 90 </a>
92 91 %else:
93 92 ${_user_group.users_group_name}
94 93 %endif
95 94 </td>
96 95 <td class="td-action">
97 96 <span class="btn btn-link btn-danger revoke_perm"
98 97 member="${_user_group.users_group_id}" member_type="user_group">
99 98 <i class="icon-remove"></i> ${_('Revoke')}
100 99 </span>
101 100 </td>
102 101 </tr>
103 102 %endfor
104 103 <tr class="new_members" id="add_perm_input"></tr>
105 104 </table>
106 105 <div id="add_perm" class="link">
107 106 ${_('Add new')}
108 107 </div>
109 108 <div class="buttons">
110 109 ${h.submit('save',_('Save'),class_="btn btn-primary")}
111 110 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
112 111 </div>
113 112 ${h.end_form()}
114 113 </div>
115 114 </div>
116 115
117 116 <script type="text/javascript">
118 117 $('#add_perm').on('click', function(e){
119 118 addNewPermInput($(this), 'repository');
120 119 });
121 120 $('.revoke_perm').on('click', function(e){
122 121 markRevokePermInput($(this), 'repository');
123 122 });
124 123 </script>
@@ -1,207 +1,207 b''
1 1 ## snippet for displaying permissions overview for users
2 2 ## usage:
3 3 ## <%namespace name="p" file="/base/perms_summary.mako"/>
4 4 ## ${p.perms_summary(c.perm_user.permissions)}
5 5
6 6 <%def name="perms_summary(permissions, show_all=False, actions=True)">
7 7 <div id="perms" class="table fields">
8 8 %for section in sorted(permissions.keys()):
9 9 <div class="panel panel-default">
10 10 <div class="panel-heading">
11 11 <h3 class="panel-title">${section.replace("_"," ").capitalize()}</h3>
12 12 </div>
13 13 <div class="panel-body">
14 14 <div class="perms_section_head field">
15 15 <div class="radios">
16 16 %if section != 'global':
17 17 <span class="permissions_boxes">
18 18 <span class="desc">${_('show')}: </span>
19 19 ${h.checkbox('perms_filter_none_%s' % section, 'none', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='none')} <label for="${'perms_filter_none_%s' % section}"><span class="perm_tag none">${_('none')}</span></label>
20 20 ${h.checkbox('perms_filter_read_%s' % section, 'read', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='read')} <label for="${'perms_filter_read_%s' % section}"><span class="perm_tag read">${_('read')}</span></label>
21 21 ${h.checkbox('perms_filter_write_%s' % section, 'write', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='write')} <label for="${'perms_filter_write_%s' % section}"> <span class="perm_tag write">${_('write')}</span></label>
22 22 ${h.checkbox('perms_filter_admin_%s' % section, 'admin', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='admin')} <label for="${'perms_filter_admin_%s' % section}"><span class="perm_tag admin">${_('admin')}</span></label>
23 23 </span>
24 24 %endif
25 25 </div>
26 26 </div>
27 27 <div class="field">
28 28 %if not permissions[section]:
29 29 <p class="empty_data help-block">${_('No permissions defined')}</p>
30 30 %else:
31 31 <div id='tbl_list_wrap_${section}'>
32 32 <table id="tbl_list_${section}" class="rctable">
33 33 ## global permission box
34 34 %if section == 'global':
35 35 <thead>
36 36 <tr>
37 37 <th colspan="2" class="left">${_('Permission')}</th>
38 38 %if actions:
39 39 <th>${_('Edit Permission')}</th>
40 40 %endif
41 41 </thead>
42 42 <tbody>
43 43
44 44 <%
45 45 def get_section_perms(prefix, opts):
46 46 _selected = []
47 47 for op in opts:
48 48 if op.startswith(prefix) and not op.startswith('hg.create.write_on_repogroup'):
49 49 _selected.append(op)
50 50 admin = 'hg.admin' in opts
51 51 _selected_vals = [x.partition(prefix)[-1] for x in _selected]
52 52 return admin, _selected_vals, _selected
53 53 %>
54 54 <%def name="glob(lbl, val, val_lbl=None, custom_url=None)">
55 55 <tr>
56 56 <td class="td-tags">
57 57 ${lbl}
58 58 </td>
59 59 <td class="td-tags">
60 60 %if val[0]:
61 61 %if not val_lbl:
62 62 ${h.bool2icon(True)}
63 63 %else:
64 64 <span class="perm_tag admin">${val_lbl}.admin</span>
65 65 %endif
66 66 %else:
67 67 %if not val_lbl:
68 68 ${h.bool2icon({'false': False,
69 69 'true': True,
70 70 'none': False,
71 71 'repository': True}.get(val[1][0] if 0 < len(val[1]) else 'false'))}
72 72 %else:
73 73 <span class="perm_tag ${val[1][0]}">${val_lbl}.${val[1][0]}</span>
74 74 %endif
75 75 %endif
76 76 </td>
77 77 %if actions:
78 78 <td class="td-action">
79 79 <a href="${custom_url or h.url('admin_permissions_global')}">${_('edit')}</a>
80 80 </td>
81 81 %endif
82 82 </tr>
83 83 </%def>
84 84
85 85 ${glob(_('Super admin'), get_section_perms('hg.admin', permissions[section]))}
86 86
87 87 ${glob(_('Repository default permission'), get_section_perms('repository.', permissions[section]), 'repository', h.url('admin_permissions_object'))}
88 88 ${glob(_('Repository group default permission'), get_section_perms('group.', permissions[section]), 'group', h.url('admin_permissions_object'))}
89 89 ${glob(_('User group default permission'), get_section_perms('usergroup.', permissions[section]), 'usergroup', h.url('admin_permissions_object'))}
90 90
91 91 ${glob(_('Create repositories'), get_section_perms('hg.create.', permissions[section]), custom_url=h.url('admin_permissions_global'))}
92 92 ${glob(_('Fork repositories'), get_section_perms('hg.fork.', permissions[section]), custom_url=h.url('admin_permissions_global'))}
93 93 ${glob(_('Create repository groups'), get_section_perms('hg.repogroup.create.', permissions[section]), custom_url=h.url('admin_permissions_global'))}
94 94 ${glob(_('Create user groups'), get_section_perms('hg.usergroup.create.', permissions[section]), custom_url=h.url('admin_permissions_global'))}
95 95
96 96
97 97 </tbody>
98 98 %else:
99 99 ## none/read/write/admin permissions on groups/repos etc
100 100 <thead>
101 101 <tr>
102 102 <th>${_('Name')}</th>
103 103 <th>${_('Permission')}</th>
104 104 %if actions:
105 105 <th>${_('Edit Permission')}</th>
106 106 %endif
107 107 </thead>
108 108 <tbody class="section_${section}">
109 109 <%
110 110 def sorter(permissions):
111 111 def custom_sorter(item):
112 112 ## read/write/admin
113 113 section = item[1].split('.')[-1]
114 114 section_importance = {'none': u'0',
115 115 'read': u'1',
116 116 'write':u'2',
117 117 'admin':u'3'}.get(section)
118 118 ## sort by group importance+name
119 119 return section_importance+item[0]
120 120 return sorted(permissions, key=custom_sorter)
121 121 %>
122 122 %for k, section_perm in sorter(permissions[section].items()):
123 123 %if section_perm.split('.')[-1] != 'none' or show_all:
124 124 <tr class="perm_row ${'%s_%s' % (section, section_perm.split('.')[-1])}">
125 125 <td class="td-componentname">
126 126 %if section == 'repositories':
127 127 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
128 128 %elif section == 'repositories_groups':
129 129 <a href="${h.url('repo_group_home',group_name=k)}">${k}</a>
130 130 %elif section == 'user_groups':
131 131 ##<a href="${h.url('edit_users_group',user_group_id=k)}">${k}</a>
132 132 ${k}
133 133 %endif
134 134 </td>
135 135 <td class="td-tags">
136 136 %if hasattr(permissions[section], 'perm_origin_stack'):
137 137 %for i, (perm, origin) in enumerate(reversed(permissions[section].perm_origin_stack[k])):
138 138 <span class="${i > 0 and 'perm_overriden' or ''} perm_tag ${perm.split('.')[-1]}">
139 139 ${perm} (${origin})
140 140 </span>
141 141 %endfor
142 142 %else:
143 143 <span class="perm_tag ${section_perm.split('.')[-1]}">${section_perm}</span>
144 144 %endif
145 145 </td>
146 146 %if actions:
147 147 <td class="td-action">
148 148 %if section == 'repositories':
149 <a href="${h.url('edit_repo_perms',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a>
149 <a href="${h.route_path('edit_repo_perms',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a>
150 150 %elif section == 'repositories_groups':
151 151 <a href="${h.url('edit_repo_group_perms',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
152 152 %elif section == 'user_groups':
153 153 ##<a href="${h.url('edit_users_group',user_group_id=k)}">${_('edit')}</a>
154 154 %endif
155 155 </td>
156 156 %endif
157 157 </tr>
158 158 %endif
159 159 %endfor
160 160
161 161 <tr id="empty_${section}" class="noborder" style="display:none;">
162 162 <td colspan="6">${_('No permission defined')}</td>
163 163 </tr>
164 164
165 165 </tbody>
166 166 %endif
167 167 </table>
168 168 </div>
169 169 %endif
170 170 </div>
171 171 </div>
172 172 </div>
173 173 %endfor
174 174 </div>
175 175
176 176 <script>
177 177 $(document).ready(function(){
178 178 var show_empty = function(section){
179 179 var visible = $('.section_{0} tr.perm_row:visible'.format(section)).length;
180 180 if(visible == 0){
181 181 $('#empty_{0}'.format(section)).show();
182 182 }
183 183 else{
184 184 $('#empty_{0}'.format(section)).hide();
185 185 }
186 186 };
187 187 $('.perm_filter').on('change', function(e){
188 188 var self = this;
189 189 var section = $(this).attr('section');
190 190
191 191 var opts = {};
192 192 var elems = $('.filter_' + section).each(function(el){
193 193 var perm_type = $(this).attr('perm_type');
194 194 var checked = this.checked;
195 195 opts[perm_type] = checked;
196 196 if(checked){
197 197 $('.'+section+'_'+perm_type).show();
198 198 }
199 199 else{
200 200 $('.'+section+'_'+perm_type).hide();
201 201 }
202 202 });
203 203 show_empty(section);
204 204 })
205 205 })
206 206 </script>
207 207 </%def>
General Comments 0
You need to be logged in to leave comments. Login now