##// END OF EJS Templates
repositories: added option to archive repositories instead of deleting them....
marcink -
r3090:bdd9dc16 default
parent child Browse files
Show More

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

@@ -0,0 +1,36 b''
1 import logging
2
3 from sqlalchemy import *
4
5 from rhodecode.model import meta
6 from rhodecode.lib.dbmigrate.versions import _reset_base, notify
7
8 log = logging.getLogger(__name__)
9
10
11 def upgrade(migrate_engine):
12 """
13 Upgrade operations go here.
14 Don't create your own engine; bind migrate_engine to your metadata
15 """
16 _reset_base(migrate_engine)
17 from rhodecode.lib.dbmigrate.schema import db_4_13_0_0 as db
18
19 repository_table = db.Repository.__table__
20
21 archived = Column('archived', Boolean(), nullable=True)
22 archived.create(table=repository_table)
23
24 # issue fixups
25 fixups(db, meta.Session)
26
27
28 def downgrade(migrate_engine):
29 meta = MetaData()
30 meta.bind = migrate_engine
31
32
33 def fixups(models, _SESSION):
34 pass
35
36
@@ -1,63 +1,63 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22
22
23 RhodeCode, a web based repository management software
23 RhodeCode, a web based repository management software
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
25 """
25 """
26
26
27 import os
27 import os
28 import sys
28 import sys
29 import platform
29 import platform
30
30
31 VERSION = tuple(open(os.path.join(
31 VERSION = tuple(open(os.path.join(
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
33
33
34 BACKENDS = {
34 BACKENDS = {
35 'hg': 'Mercurial repository',
35 'hg': 'Mercurial repository',
36 'git': 'Git repository',
36 'git': 'Git repository',
37 'svn': 'Subversion repository',
37 'svn': 'Subversion repository',
38 }
38 }
39
39
40 CELERY_ENABLED = False
40 CELERY_ENABLED = False
41 CELERY_EAGER = False
41 CELERY_EAGER = False
42
42
43 # link to config for pyramid
43 # link to config for pyramid
44 CONFIG = {}
44 CONFIG = {}
45
45
46 # Populated with the settings dictionary from application init in
46 # Populated with the settings dictionary from application init in
47 # rhodecode.conf.environment.load_pyramid_environment
47 # rhodecode.conf.environment.load_pyramid_environment
48 PYRAMID_SETTINGS = {}
48 PYRAMID_SETTINGS = {}
49
49
50 # Linked module for extensions
50 # Linked module for extensions
51 EXTENSIONS = {}
51 EXTENSIONS = {}
52
52
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
54 __dbversion__ = 90 # defines current db version for migrations
54 __dbversion__ = 91 # defines current db version for migrations
55 __platform__ = platform.system()
55 __platform__ = platform.system()
56 __license__ = 'AGPLv3, and Commercial License'
56 __license__ = 'AGPLv3, and Commercial License'
57 __author__ = 'RhodeCode GmbH'
57 __author__ = 'RhodeCode GmbH'
58 __url__ = 'https://code.rhodecode.com'
58 __url__ = 'https://code.rhodecode.com'
59
59
60 is_windows = __platform__ in ['Windows']
60 is_windows = __platform__ in ['Windows']
61 is_unix = not is_windows
61 is_unix = not is_windows
62 is_test = False
62 is_test = False
63 disable_error_handler = False
63 disable_error_handler = False
@@ -1,652 +1,677 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import time
21 import time
22 import logging
22 import logging
23 import operator
23 import operator
24
24
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
26
26
27 from rhodecode.lib import helpers as h, diffs
27 from rhodecode.lib import helpers as h, diffs
28 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
28 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
30 from rhodecode.model import repo
30 from rhodecode.model import repo
31 from rhodecode.model import repo_group
31 from rhodecode.model import repo_group
32 from rhodecode.model import user_group
32 from rhodecode.model import user_group
33 from rhodecode.model import user
33 from rhodecode.model import user
34 from rhodecode.model.db import User
34 from rhodecode.model.db import User
35 from rhodecode.model.scm import ScmModel
35 from rhodecode.model.scm import ScmModel
36 from rhodecode.model.settings import VcsSettingsModel
36 from rhodecode.model.settings import VcsSettingsModel
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 ADMIN_PREFIX = '/_admin'
41 ADMIN_PREFIX = '/_admin'
42 STATIC_FILE_PREFIX = '/_static'
42 STATIC_FILE_PREFIX = '/_static'
43
43
44 URL_NAME_REQUIREMENTS = {
44 URL_NAME_REQUIREMENTS = {
45 # group name can have a slash in them, but they must not end with a slash
45 # group name can have a slash in them, but they must not end with a slash
46 'group_name': r'.*?[^/]',
46 'group_name': r'.*?[^/]',
47 'repo_group_name': r'.*?[^/]',
47 'repo_group_name': r'.*?[^/]',
48 # repo names can have a slash in them, but they must not end with a slash
48 # repo names can have a slash in them, but they must not end with a slash
49 'repo_name': r'.*?[^/]',
49 'repo_name': r'.*?[^/]',
50 # file path eats up everything at the end
50 # file path eats up everything at the end
51 'f_path': r'.*',
51 'f_path': r'.*',
52 # reference types
52 # reference types
53 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
53 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
54 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
54 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
55 }
55 }
56
56
57
57
58 def add_route_with_slash(config,name, pattern, **kw):
58 def add_route_with_slash(config,name, pattern, **kw):
59 config.add_route(name, pattern, **kw)
59 config.add_route(name, pattern, **kw)
60 if not pattern.endswith('/'):
60 if not pattern.endswith('/'):
61 config.add_route(name + '_slash', pattern + '/', **kw)
61 config.add_route(name + '_slash', pattern + '/', **kw)
62
62
63
63
64 def add_route_requirements(route_path, requirements=URL_NAME_REQUIREMENTS):
64 def add_route_requirements(route_path, requirements=URL_NAME_REQUIREMENTS):
65 """
65 """
66 Adds regex requirements to pyramid routes using a mapping dict
66 Adds regex requirements to pyramid routes using a mapping dict
67 e.g::
67 e.g::
68 add_route_requirements('{repo_name}/settings')
68 add_route_requirements('{repo_name}/settings')
69 """
69 """
70 for key, regex in requirements.items():
70 for key, regex in requirements.items():
71 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
71 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
72 return route_path
72 return route_path
73
73
74
74
75 def get_format_ref_id(repo):
75 def get_format_ref_id(repo):
76 """Returns a `repo` specific reference formatter function"""
76 """Returns a `repo` specific reference formatter function"""
77 if h.is_svn(repo):
77 if h.is_svn(repo):
78 return _format_ref_id_svn
78 return _format_ref_id_svn
79 else:
79 else:
80 return _format_ref_id
80 return _format_ref_id
81
81
82
82
83 def _format_ref_id(name, raw_id):
83 def _format_ref_id(name, raw_id):
84 """Default formatting of a given reference `name`"""
84 """Default formatting of a given reference `name`"""
85 return name
85 return name
86
86
87
87
88 def _format_ref_id_svn(name, raw_id):
88 def _format_ref_id_svn(name, raw_id):
89 """Special way of formatting a reference for Subversion including path"""
89 """Special way of formatting a reference for Subversion including path"""
90 return '%s@%s' % (name, raw_id)
90 return '%s@%s' % (name, raw_id)
91
91
92
92
93 class TemplateArgs(StrictAttributeDict):
93 class TemplateArgs(StrictAttributeDict):
94 pass
94 pass
95
95
96
96
97 class BaseAppView(object):
97 class BaseAppView(object):
98
98
99 def __init__(self, context, request):
99 def __init__(self, context, request):
100 self.request = request
100 self.request = request
101 self.context = context
101 self.context = context
102 self.session = request.session
102 self.session = request.session
103 if not hasattr(request, 'user'):
103 if not hasattr(request, 'user'):
104 # NOTE(marcink): edge case, we ended up in matched route
104 # NOTE(marcink): edge case, we ended up in matched route
105 # but probably of web-app context, e.g API CALL/VCS CALL
105 # but probably of web-app context, e.g API CALL/VCS CALL
106 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
106 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
107 log.warning('Unable to process request `%s` in this scope', request)
107 log.warning('Unable to process request `%s` in this scope', request)
108 raise HTTPBadRequest()
108 raise HTTPBadRequest()
109
109
110 self._rhodecode_user = request.user # auth user
110 self._rhodecode_user = request.user # auth user
111 self._rhodecode_db_user = self._rhodecode_user.get_instance()
111 self._rhodecode_db_user = self._rhodecode_user.get_instance()
112 self._maybe_needs_password_change(
112 self._maybe_needs_password_change(
113 request.matched_route.name, self._rhodecode_db_user)
113 request.matched_route.name, self._rhodecode_db_user)
114
114
115 def _maybe_needs_password_change(self, view_name, user_obj):
115 def _maybe_needs_password_change(self, view_name, user_obj):
116 log.debug('Checking if user %s needs password change on view %s',
116 log.debug('Checking if user %s needs password change on view %s',
117 user_obj, view_name)
117 user_obj, view_name)
118 skip_user_views = [
118 skip_user_views = [
119 'logout', 'login',
119 'logout', 'login',
120 'my_account_password', 'my_account_password_update'
120 'my_account_password', 'my_account_password_update'
121 ]
121 ]
122
122
123 if not user_obj:
123 if not user_obj:
124 return
124 return
125
125
126 if user_obj.username == User.DEFAULT_USER:
126 if user_obj.username == User.DEFAULT_USER:
127 return
127 return
128
128
129 now = time.time()
129 now = time.time()
130 should_change = user_obj.user_data.get('force_password_change')
130 should_change = user_obj.user_data.get('force_password_change')
131 change_after = safe_int(should_change) or 0
131 change_after = safe_int(should_change) or 0
132 if should_change and now > change_after:
132 if should_change and now > change_after:
133 log.debug('User %s requires password change', user_obj)
133 log.debug('User %s requires password change', user_obj)
134 h.flash('You are required to change your password', 'warning',
134 h.flash('You are required to change your password', 'warning',
135 ignore_duplicate=True)
135 ignore_duplicate=True)
136
136
137 if view_name not in skip_user_views:
137 if view_name not in skip_user_views:
138 raise HTTPFound(
138 raise HTTPFound(
139 self.request.route_path('my_account_password'))
139 self.request.route_path('my_account_password'))
140
140
141 def _log_creation_exception(self, e, repo_name):
141 def _log_creation_exception(self, e, repo_name):
142 _ = self.request.translate
142 _ = self.request.translate
143 reason = None
143 reason = None
144 if len(e.args) == 2:
144 if len(e.args) == 2:
145 reason = e.args[1]
145 reason = e.args[1]
146
146
147 if reason == 'INVALID_CERTIFICATE':
147 if reason == 'INVALID_CERTIFICATE':
148 log.exception(
148 log.exception(
149 'Exception creating a repository: invalid certificate')
149 'Exception creating a repository: invalid certificate')
150 msg = (_('Error creating repository %s: invalid certificate')
150 msg = (_('Error creating repository %s: invalid certificate')
151 % repo_name)
151 % repo_name)
152 else:
152 else:
153 log.exception("Exception creating a repository")
153 log.exception("Exception creating a repository")
154 msg = (_('Error creating repository %s')
154 msg = (_('Error creating repository %s')
155 % repo_name)
155 % repo_name)
156 return msg
156 return msg
157
157
158 def _get_local_tmpl_context(self, include_app_defaults=True):
158 def _get_local_tmpl_context(self, include_app_defaults=True):
159 c = TemplateArgs()
159 c = TemplateArgs()
160 c.auth_user = self.request.user
160 c.auth_user = self.request.user
161 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
161 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
162 c.rhodecode_user = self.request.user
162 c.rhodecode_user = self.request.user
163
163
164 if include_app_defaults:
164 if include_app_defaults:
165 from rhodecode.lib.base import attach_context_attributes
165 from rhodecode.lib.base import attach_context_attributes
166 attach_context_attributes(c, self.request, self.request.user.user_id)
166 attach_context_attributes(c, self.request, self.request.user.user_id)
167
167
168 return c
168 return c
169
169
170 def _get_template_context(self, tmpl_args, **kwargs):
170 def _get_template_context(self, tmpl_args, **kwargs):
171
171
172 local_tmpl_args = {
172 local_tmpl_args = {
173 'defaults': {},
173 'defaults': {},
174 'errors': {},
174 'errors': {},
175 'c': tmpl_args
175 'c': tmpl_args
176 }
176 }
177 local_tmpl_args.update(kwargs)
177 local_tmpl_args.update(kwargs)
178 return local_tmpl_args
178 return local_tmpl_args
179
179
180 def load_default_context(self):
180 def load_default_context(self):
181 """
181 """
182 example:
182 example:
183
183
184 def load_default_context(self):
184 def load_default_context(self):
185 c = self._get_local_tmpl_context()
185 c = self._get_local_tmpl_context()
186 c.custom_var = 'foobar'
186 c.custom_var = 'foobar'
187
187
188 return c
188 return c
189 """
189 """
190 raise NotImplementedError('Needs implementation in view class')
190 raise NotImplementedError('Needs implementation in view class')
191
191
192
192
193 class RepoAppView(BaseAppView):
193 class RepoAppView(BaseAppView):
194
194
195 def __init__(self, context, request):
195 def __init__(self, context, request):
196 super(RepoAppView, self).__init__(context, request)
196 super(RepoAppView, self).__init__(context, request)
197 self.db_repo = request.db_repo
197 self.db_repo = request.db_repo
198 self.db_repo_name = self.db_repo.repo_name
198 self.db_repo_name = self.db_repo.repo_name
199 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
199 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
200
200
201 def _handle_missing_requirements(self, error):
201 def _handle_missing_requirements(self, error):
202 log.error(
202 log.error(
203 'Requirements are missing for repository %s: %s',
203 'Requirements are missing for repository %s: %s',
204 self.db_repo_name, error.message)
204 self.db_repo_name, error.message)
205
205
206 def _get_local_tmpl_context(self, include_app_defaults=True):
206 def _get_local_tmpl_context(self, include_app_defaults=True):
207 _ = self.request.translate
207 _ = self.request.translate
208 c = super(RepoAppView, self)._get_local_tmpl_context(
208 c = super(RepoAppView, self)._get_local_tmpl_context(
209 include_app_defaults=include_app_defaults)
209 include_app_defaults=include_app_defaults)
210
210
211 # register common vars for this type of view
211 # register common vars for this type of view
212 c.rhodecode_db_repo = self.db_repo
212 c.rhodecode_db_repo = self.db_repo
213 c.repo_name = self.db_repo_name
213 c.repo_name = self.db_repo_name
214 c.repository_pull_requests = self.db_repo_pull_requests
214 c.repository_pull_requests = self.db_repo_pull_requests
215 self.path_filter = PathFilter(None)
215 self.path_filter = PathFilter(None)
216
216
217 c.repository_requirements_missing = {}
217 c.repository_requirements_missing = {}
218 try:
218 try:
219 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
219 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
220 if self.rhodecode_vcs_repo:
220 if self.rhodecode_vcs_repo:
221 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
221 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
222 c.auth_user.username)
222 c.auth_user.username)
223 self.path_filter = PathFilter(path_perms)
223 self.path_filter = PathFilter(path_perms)
224 except RepositoryRequirementError as e:
224 except RepositoryRequirementError as e:
225 c.repository_requirements_missing = {'error': str(e)}
225 c.repository_requirements_missing = {'error': str(e)}
226 self._handle_missing_requirements(e)
226 self._handle_missing_requirements(e)
227 self.rhodecode_vcs_repo = None
227 self.rhodecode_vcs_repo = None
228
228
229 c.path_filter = self.path_filter # used by atom_feed_entry.mako
229 c.path_filter = self.path_filter # used by atom_feed_entry.mako
230
230
231 if self.rhodecode_vcs_repo is None:
231 if self.rhodecode_vcs_repo is None:
232 # unable to fetch this repo as vcs instance, report back to user
232 # unable to fetch this repo as vcs instance, report back to user
233 h.flash(_(
233 h.flash(_(
234 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
234 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
235 "Please check if it exist, or is not damaged.") %
235 "Please check if it exist, or is not damaged.") %
236 {'repo_name': c.repo_name},
236 {'repo_name': c.repo_name},
237 category='error', ignore_duplicate=True)
237 category='error', ignore_duplicate=True)
238 if c.repository_requirements_missing:
238 if c.repository_requirements_missing:
239 route = self.request.matched_route.name
239 route = self.request.matched_route.name
240 if route.startswith(('edit_repo', 'repo_summary')):
240 if route.startswith(('edit_repo', 'repo_summary')):
241 # allow summary and edit repo on missing requirements
241 # allow summary and edit repo on missing requirements
242 return c
242 return c
243
243
244 raise HTTPFound(
244 raise HTTPFound(
245 h.route_path('repo_summary', repo_name=self.db_repo_name))
245 h.route_path('repo_summary', repo_name=self.db_repo_name))
246
246
247 else: # redirect if we don't show missing requirements
247 else: # redirect if we don't show missing requirements
248 raise HTTPFound(h.route_path('home'))
248 raise HTTPFound(h.route_path('home'))
249
249
250 return c
250 return c
251
251
252 def _get_f_path_unchecked(self, matchdict, default=None):
252 def _get_f_path_unchecked(self, matchdict, default=None):
253 """
253 """
254 Should only be used by redirects, everything else should call _get_f_path
254 Should only be used by redirects, everything else should call _get_f_path
255 """
255 """
256 f_path = matchdict.get('f_path')
256 f_path = matchdict.get('f_path')
257 if f_path:
257 if f_path:
258 # fix for multiple initial slashes that causes errors for GIT
258 # fix for multiple initial slashes that causes errors for GIT
259 return f_path.lstrip('/')
259 return f_path.lstrip('/')
260
260
261 return default
261 return default
262
262
263 def _get_f_path(self, matchdict, default=None):
263 def _get_f_path(self, matchdict, default=None):
264 f_path_match = self._get_f_path_unchecked(matchdict, default)
264 f_path_match = self._get_f_path_unchecked(matchdict, default)
265 return self.path_filter.assert_path_permissions(f_path_match)
265 return self.path_filter.assert_path_permissions(f_path_match)
266
266
267 def _get_general_setting(self, target_repo, settings_key, default=False):
267 def _get_general_setting(self, target_repo, settings_key, default=False):
268 settings_model = VcsSettingsModel(repo=target_repo)
268 settings_model = VcsSettingsModel(repo=target_repo)
269 settings = settings_model.get_general_settings()
269 settings = settings_model.get_general_settings()
270 return settings.get(settings_key, default)
270 return settings.get(settings_key, default)
271
271
272
272
273 class PathFilter(object):
273 class PathFilter(object):
274
274
275 # Expects and instance of BasePathPermissionChecker or None
275 # Expects and instance of BasePathPermissionChecker or None
276 def __init__(self, permission_checker):
276 def __init__(self, permission_checker):
277 self.permission_checker = permission_checker
277 self.permission_checker = permission_checker
278
278
279 def assert_path_permissions(self, path):
279 def assert_path_permissions(self, path):
280 if path and self.permission_checker and not self.permission_checker.has_access(path):
280 if path and self.permission_checker and not self.permission_checker.has_access(path):
281 raise HTTPForbidden()
281 raise HTTPForbidden()
282 return path
282 return path
283
283
284 def filter_patchset(self, patchset):
284 def filter_patchset(self, patchset):
285 if not self.permission_checker or not patchset:
285 if not self.permission_checker or not patchset:
286 return patchset, False
286 return patchset, False
287 had_filtered = False
287 had_filtered = False
288 filtered_patchset = []
288 filtered_patchset = []
289 for patch in patchset:
289 for patch in patchset:
290 filename = patch.get('filename', None)
290 filename = patch.get('filename', None)
291 if not filename or self.permission_checker.has_access(filename):
291 if not filename or self.permission_checker.has_access(filename):
292 filtered_patchset.append(patch)
292 filtered_patchset.append(patch)
293 else:
293 else:
294 had_filtered = True
294 had_filtered = True
295 if had_filtered:
295 if had_filtered:
296 if isinstance(patchset, diffs.LimitedDiffContainer):
296 if isinstance(patchset, diffs.LimitedDiffContainer):
297 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
297 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
298 return filtered_patchset, True
298 return filtered_patchset, True
299 else:
299 else:
300 return patchset, False
300 return patchset, False
301
301
302 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
302 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
303 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
303 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
304 result = diffset.render_patchset(filtered_patchset, source_ref=source_ref, target_ref=target_ref)
304 result = diffset.render_patchset(filtered_patchset, source_ref=source_ref, target_ref=target_ref)
305 result.has_hidden_changes = has_hidden_changes
305 result.has_hidden_changes = has_hidden_changes
306 return result
306 return result
307
307
308 def get_raw_patch(self, diff_processor):
308 def get_raw_patch(self, diff_processor):
309 if self.permission_checker is None:
309 if self.permission_checker is None:
310 return diff_processor.as_raw()
310 return diff_processor.as_raw()
311 elif self.permission_checker.has_full_access:
311 elif self.permission_checker.has_full_access:
312 return diff_processor.as_raw()
312 return diff_processor.as_raw()
313 else:
313 else:
314 return '# Repository has user-specific filters, raw patch generation is disabled.'
314 return '# Repository has user-specific filters, raw patch generation is disabled.'
315
315
316 @property
316 @property
317 def is_enabled(self):
317 def is_enabled(self):
318 return self.permission_checker is not None
318 return self.permission_checker is not None
319
319
320
320
321 class RepoGroupAppView(BaseAppView):
321 class RepoGroupAppView(BaseAppView):
322 def __init__(self, context, request):
322 def __init__(self, context, request):
323 super(RepoGroupAppView, self).__init__(context, request)
323 super(RepoGroupAppView, self).__init__(context, request)
324 self.db_repo_group = request.db_repo_group
324 self.db_repo_group = request.db_repo_group
325 self.db_repo_group_name = self.db_repo_group.group_name
325 self.db_repo_group_name = self.db_repo_group.group_name
326
326
327 def _revoke_perms_on_yourself(self, form_result):
327 def _revoke_perms_on_yourself(self, form_result):
328 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
328 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
329 form_result['perm_updates'])
329 form_result['perm_updates'])
330 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
330 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
331 form_result['perm_additions'])
331 form_result['perm_additions'])
332 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
332 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
333 form_result['perm_deletions'])
333 form_result['perm_deletions'])
334 admin_perm = 'group.admin'
334 admin_perm = 'group.admin'
335 if _updates and _updates[0][1] != admin_perm or \
335 if _updates and _updates[0][1] != admin_perm or \
336 _additions and _additions[0][1] != admin_perm or \
336 _additions and _additions[0][1] != admin_perm or \
337 _deletions and _deletions[0][1] != admin_perm:
337 _deletions and _deletions[0][1] != admin_perm:
338 return True
338 return True
339 return False
339 return False
340
340
341
341
342 class UserGroupAppView(BaseAppView):
342 class UserGroupAppView(BaseAppView):
343 def __init__(self, context, request):
343 def __init__(self, context, request):
344 super(UserGroupAppView, self).__init__(context, request)
344 super(UserGroupAppView, self).__init__(context, request)
345 self.db_user_group = request.db_user_group
345 self.db_user_group = request.db_user_group
346 self.db_user_group_name = self.db_user_group.users_group_name
346 self.db_user_group_name = self.db_user_group.users_group_name
347
347
348
348
349 class UserAppView(BaseAppView):
349 class UserAppView(BaseAppView):
350 def __init__(self, context, request):
350 def __init__(self, context, request):
351 super(UserAppView, self).__init__(context, request)
351 super(UserAppView, self).__init__(context, request)
352 self.db_user = request.db_user
352 self.db_user = request.db_user
353 self.db_user_id = self.db_user.user_id
353 self.db_user_id = self.db_user.user_id
354
354
355 _ = self.request.translate
355 _ = self.request.translate
356 if not request.db_user_supports_default:
356 if not request.db_user_supports_default:
357 if self.db_user.username == User.DEFAULT_USER:
357 if self.db_user.username == User.DEFAULT_USER:
358 h.flash(_("Editing user `{}` is disabled.".format(
358 h.flash(_("Editing user `{}` is disabled.".format(
359 User.DEFAULT_USER)), category='warning')
359 User.DEFAULT_USER)), category='warning')
360 raise HTTPFound(h.route_path('users'))
360 raise HTTPFound(h.route_path('users'))
361
361
362
362
363 class DataGridAppView(object):
363 class DataGridAppView(object):
364 """
364 """
365 Common class to have re-usable grid rendering components
365 Common class to have re-usable grid rendering components
366 """
366 """
367
367
368 def _extract_ordering(self, request, column_map=None):
368 def _extract_ordering(self, request, column_map=None):
369 column_map = column_map or {}
369 column_map = column_map or {}
370 column_index = safe_int(request.GET.get('order[0][column]'))
370 column_index = safe_int(request.GET.get('order[0][column]'))
371 order_dir = request.GET.get(
371 order_dir = request.GET.get(
372 'order[0][dir]', 'desc')
372 'order[0][dir]', 'desc')
373 order_by = request.GET.get(
373 order_by = request.GET.get(
374 'columns[%s][data][sort]' % column_index, 'name_raw')
374 'columns[%s][data][sort]' % column_index, 'name_raw')
375
375
376 # translate datatable to DB columns
376 # translate datatable to DB columns
377 order_by = column_map.get(order_by) or order_by
377 order_by = column_map.get(order_by) or order_by
378
378
379 search_q = request.GET.get('search[value]')
379 search_q = request.GET.get('search[value]')
380 return search_q, order_by, order_dir
380 return search_q, order_by, order_dir
381
381
382 def _extract_chunk(self, request):
382 def _extract_chunk(self, request):
383 start = safe_int(request.GET.get('start'), 0)
383 start = safe_int(request.GET.get('start'), 0)
384 length = safe_int(request.GET.get('length'), 25)
384 length = safe_int(request.GET.get('length'), 25)
385 draw = safe_int(request.GET.get('draw'))
385 draw = safe_int(request.GET.get('draw'))
386 return draw, start, length
386 return draw, start, length
387
387
388 def _get_order_col(self, order_by, model):
388 def _get_order_col(self, order_by, model):
389 if isinstance(order_by, basestring):
389 if isinstance(order_by, basestring):
390 try:
390 try:
391 return operator.attrgetter(order_by)(model)
391 return operator.attrgetter(order_by)(model)
392 except AttributeError:
392 except AttributeError:
393 return None
393 return None
394 else:
394 else:
395 return order_by
395 return order_by
396
396
397
397
398 class BaseReferencesView(RepoAppView):
398 class BaseReferencesView(RepoAppView):
399 """
399 """
400 Base for reference view for branches, tags and bookmarks.
400 Base for reference view for branches, tags and bookmarks.
401 """
401 """
402 def load_default_context(self):
402 def load_default_context(self):
403 c = self._get_local_tmpl_context()
403 c = self._get_local_tmpl_context()
404
404
405
405
406 return c
406 return c
407
407
408 def load_refs_context(self, ref_items, partials_template):
408 def load_refs_context(self, ref_items, partials_template):
409 _render = self.request.get_partial_renderer(partials_template)
409 _render = self.request.get_partial_renderer(partials_template)
410 pre_load = ["author", "date", "message"]
410 pre_load = ["author", "date", "message"]
411
411
412 is_svn = h.is_svn(self.rhodecode_vcs_repo)
412 is_svn = h.is_svn(self.rhodecode_vcs_repo)
413 is_hg = h.is_hg(self.rhodecode_vcs_repo)
413 is_hg = h.is_hg(self.rhodecode_vcs_repo)
414
414
415 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
415 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
416
416
417 closed_refs = {}
417 closed_refs = {}
418 if is_hg:
418 if is_hg:
419 closed_refs = self.rhodecode_vcs_repo.branches_closed
419 closed_refs = self.rhodecode_vcs_repo.branches_closed
420
420
421 data = []
421 data = []
422 for ref_name, commit_id in ref_items:
422 for ref_name, commit_id in ref_items:
423 commit = self.rhodecode_vcs_repo.get_commit(
423 commit = self.rhodecode_vcs_repo.get_commit(
424 commit_id=commit_id, pre_load=pre_load)
424 commit_id=commit_id, pre_load=pre_load)
425 closed = ref_name in closed_refs
425 closed = ref_name in closed_refs
426
426
427 # TODO: johbo: Unify generation of reference links
427 # TODO: johbo: Unify generation of reference links
428 use_commit_id = '/' in ref_name or is_svn
428 use_commit_id = '/' in ref_name or is_svn
429
429
430 if use_commit_id:
430 if use_commit_id:
431 files_url = h.route_path(
431 files_url = h.route_path(
432 'repo_files',
432 'repo_files',
433 repo_name=self.db_repo_name,
433 repo_name=self.db_repo_name,
434 f_path=ref_name if is_svn else '',
434 f_path=ref_name if is_svn else '',
435 commit_id=commit_id)
435 commit_id=commit_id)
436
436
437 else:
437 else:
438 files_url = h.route_path(
438 files_url = h.route_path(
439 'repo_files',
439 'repo_files',
440 repo_name=self.db_repo_name,
440 repo_name=self.db_repo_name,
441 f_path=ref_name if is_svn else '',
441 f_path=ref_name if is_svn else '',
442 commit_id=ref_name,
442 commit_id=ref_name,
443 _query=dict(at=ref_name))
443 _query=dict(at=ref_name))
444
444
445 data.append({
445 data.append({
446 "name": _render('name', ref_name, files_url, closed),
446 "name": _render('name', ref_name, files_url, closed),
447 "name_raw": ref_name,
447 "name_raw": ref_name,
448 "date": _render('date', commit.date),
448 "date": _render('date', commit.date),
449 "date_raw": datetime_to_time(commit.date),
449 "date_raw": datetime_to_time(commit.date),
450 "author": _render('author', commit.author),
450 "author": _render('author', commit.author),
451 "commit": _render(
451 "commit": _render(
452 'commit', commit.message, commit.raw_id, commit.idx),
452 'commit', commit.message, commit.raw_id, commit.idx),
453 "commit_raw": commit.idx,
453 "commit_raw": commit.idx,
454 "compare": _render(
454 "compare": _render(
455 'compare', format_ref_id(ref_name, commit.raw_id)),
455 'compare', format_ref_id(ref_name, commit.raw_id)),
456 })
456 })
457
457
458 return data
458 return data
459
459
460
460
461 class RepoRoutePredicate(object):
461 class RepoRoutePredicate(object):
462 def __init__(self, val, config):
462 def __init__(self, val, config):
463 self.val = val
463 self.val = val
464
464
465 def text(self):
465 def text(self):
466 return 'repo_route = %s' % self.val
466 return 'repo_route = %s' % self.val
467
467
468 phash = text
468 phash = text
469
469
470 def __call__(self, info, request):
470 def __call__(self, info, request):
471 if hasattr(request, 'vcs_call'):
471 if hasattr(request, 'vcs_call'):
472 # skip vcs calls
472 # skip vcs calls
473 return
473 return
474
474
475 repo_name = info['match']['repo_name']
475 repo_name = info['match']['repo_name']
476 repo_model = repo.RepoModel()
476 repo_model = repo.RepoModel()
477
477
478 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
478 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
479
479
480 def redirect_if_creating(route_info, db_repo):
480 def redirect_if_creating(route_info, db_repo):
481 skip_views = ['edit_repo_advanced_delete']
481 skip_views = ['edit_repo_advanced_delete']
482 route = route_info['route']
482 route = route_info['route']
483 # we should skip delete view so we can actually "remove" repositories
483 # we should skip delete view so we can actually "remove" repositories
484 # if they get stuck in creating state.
484 # if they get stuck in creating state.
485 if route.name in skip_views:
485 if route.name in skip_views:
486 return
486 return
487
487
488 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
488 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
489 repo_creating_url = request.route_path(
489 repo_creating_url = request.route_path(
490 'repo_creating', repo_name=db_repo.repo_name)
490 'repo_creating', repo_name=db_repo.repo_name)
491 raise HTTPFound(repo_creating_url)
491 raise HTTPFound(repo_creating_url)
492
492
493 if by_name_match:
493 if by_name_match:
494 # register this as request object we can re-use later
494 # register this as request object we can re-use later
495 request.db_repo = by_name_match
495 request.db_repo = by_name_match
496 redirect_if_creating(info, by_name_match)
496 redirect_if_creating(info, by_name_match)
497 return True
497 return True
498
498
499 by_id_match = repo_model.get_repo_by_id(repo_name)
499 by_id_match = repo_model.get_repo_by_id(repo_name)
500 if by_id_match:
500 if by_id_match:
501 request.db_repo = by_id_match
501 request.db_repo = by_id_match
502 redirect_if_creating(info, by_id_match)
502 redirect_if_creating(info, by_id_match)
503 return True
503 return True
504
504
505 return False
505 return False
506
506
507
507
508 class RepoForbidArchivedRoutePredicate(object):
509 def __init__(self, val, config):
510 self.val = val
511
512 def text(self):
513 return 'repo_forbid_archived = %s' % self.val
514
515 phash = text
516
517 def __call__(self, info, request):
518 _ = request.translate
519 rhodecode_db_repo = request.db_repo
520
521 log.debug(
522 '%s checking if archived flag for repo for %s',
523 self.__class__.__name__, rhodecode_db_repo.repo_name)
524
525 if rhodecode_db_repo.archived:
526 log.warning('Current view is not supported for archived repo:%s',
527 rhodecode_db_repo.repo_name)
528
529 h.flash(
530 h.literal(_('Action not supported for archived repository.')),
531 category='warning')
532 summary_url = request.route_path(
533 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
534 raise HTTPFound(summary_url)
535 return True
536
537
508 class RepoTypeRoutePredicate(object):
538 class RepoTypeRoutePredicate(object):
509 def __init__(self, val, config):
539 def __init__(self, val, config):
510 self.val = val or ['hg', 'git', 'svn']
540 self.val = val or ['hg', 'git', 'svn']
511
541
512 def text(self):
542 def text(self):
513 return 'repo_accepted_type = %s' % self.val
543 return 'repo_accepted_type = %s' % self.val
514
544
515 phash = text
545 phash = text
516
546
517 def __call__(self, info, request):
547 def __call__(self, info, request):
518 if hasattr(request, 'vcs_call'):
548 if hasattr(request, 'vcs_call'):
519 # skip vcs calls
549 # skip vcs calls
520 return
550 return
521
551
522 rhodecode_db_repo = request.db_repo
552 rhodecode_db_repo = request.db_repo
523
553
524 log.debug(
554 log.debug(
525 '%s checking repo type for %s in %s',
555 '%s checking repo type for %s in %s',
526 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
556 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
527
557
528 if rhodecode_db_repo.repo_type in self.val:
558 if rhodecode_db_repo.repo_type in self.val:
529 return True
559 return True
530 else:
560 else:
531 log.warning('Current view is not supported for repo type:%s',
561 log.warning('Current view is not supported for repo type:%s',
532 rhodecode_db_repo.repo_type)
562 rhodecode_db_repo.repo_type)
533
534 # h.flash(h.literal(
535 # _('Action not supported for %s.' % rhodecode_repo.alias)),
536 # category='warning')
537 # return redirect(
538 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
539
540 return False
563 return False
541
564
542
565
543 class RepoGroupRoutePredicate(object):
566 class RepoGroupRoutePredicate(object):
544 def __init__(self, val, config):
567 def __init__(self, val, config):
545 self.val = val
568 self.val = val
546
569
547 def text(self):
570 def text(self):
548 return 'repo_group_route = %s' % self.val
571 return 'repo_group_route = %s' % self.val
549
572
550 phash = text
573 phash = text
551
574
552 def __call__(self, info, request):
575 def __call__(self, info, request):
553 if hasattr(request, 'vcs_call'):
576 if hasattr(request, 'vcs_call'):
554 # skip vcs calls
577 # skip vcs calls
555 return
578 return
556
579
557 repo_group_name = info['match']['repo_group_name']
580 repo_group_name = info['match']['repo_group_name']
558 repo_group_model = repo_group.RepoGroupModel()
581 repo_group_model = repo_group.RepoGroupModel()
559 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
582 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
560
583
561 if by_name_match:
584 if by_name_match:
562 # register this as request object we can re-use later
585 # register this as request object we can re-use later
563 request.db_repo_group = by_name_match
586 request.db_repo_group = by_name_match
564 return True
587 return True
565
588
566 return False
589 return False
567
590
568
591
569 class UserGroupRoutePredicate(object):
592 class UserGroupRoutePredicate(object):
570 def __init__(self, val, config):
593 def __init__(self, val, config):
571 self.val = val
594 self.val = val
572
595
573 def text(self):
596 def text(self):
574 return 'user_group_route = %s' % self.val
597 return 'user_group_route = %s' % self.val
575
598
576 phash = text
599 phash = text
577
600
578 def __call__(self, info, request):
601 def __call__(self, info, request):
579 if hasattr(request, 'vcs_call'):
602 if hasattr(request, 'vcs_call'):
580 # skip vcs calls
603 # skip vcs calls
581 return
604 return
582
605
583 user_group_id = info['match']['user_group_id']
606 user_group_id = info['match']['user_group_id']
584 user_group_model = user_group.UserGroup()
607 user_group_model = user_group.UserGroup()
585 by_id_match = user_group_model.get(user_group_id, cache=False)
608 by_id_match = user_group_model.get(user_group_id, cache=False)
586
609
587 if by_id_match:
610 if by_id_match:
588 # register this as request object we can re-use later
611 # register this as request object we can re-use later
589 request.db_user_group = by_id_match
612 request.db_user_group = by_id_match
590 return True
613 return True
591
614
592 return False
615 return False
593
616
594
617
595 class UserRoutePredicateBase(object):
618 class UserRoutePredicateBase(object):
596 supports_default = None
619 supports_default = None
597
620
598 def __init__(self, val, config):
621 def __init__(self, val, config):
599 self.val = val
622 self.val = val
600
623
601 def text(self):
624 def text(self):
602 raise NotImplementedError()
625 raise NotImplementedError()
603
626
604 def __call__(self, info, request):
627 def __call__(self, info, request):
605 if hasattr(request, 'vcs_call'):
628 if hasattr(request, 'vcs_call'):
606 # skip vcs calls
629 # skip vcs calls
607 return
630 return
608
631
609 user_id = info['match']['user_id']
632 user_id = info['match']['user_id']
610 user_model = user.User()
633 user_model = user.User()
611 by_id_match = user_model.get(user_id, cache=False)
634 by_id_match = user_model.get(user_id, cache=False)
612
635
613 if by_id_match:
636 if by_id_match:
614 # register this as request object we can re-use later
637 # register this as request object we can re-use later
615 request.db_user = by_id_match
638 request.db_user = by_id_match
616 request.db_user_supports_default = self.supports_default
639 request.db_user_supports_default = self.supports_default
617 return True
640 return True
618
641
619 return False
642 return False
620
643
621
644
622 class UserRoutePredicate(UserRoutePredicateBase):
645 class UserRoutePredicate(UserRoutePredicateBase):
623 supports_default = False
646 supports_default = False
624
647
625 def text(self):
648 def text(self):
626 return 'user_route = %s' % self.val
649 return 'user_route = %s' % self.val
627
650
628 phash = text
651 phash = text
629
652
630
653
631 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
654 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
632 supports_default = True
655 supports_default = True
633
656
634 def text(self):
657 def text(self):
635 return 'user_with_default_route = %s' % self.val
658 return 'user_with_default_route = %s' % self.val
636
659
637 phash = text
660 phash = text
638
661
639
662
640 def includeme(config):
663 def includeme(config):
641 config.add_route_predicate(
664 config.add_route_predicate(
642 'repo_route', RepoRoutePredicate)
665 'repo_route', RepoRoutePredicate)
643 config.add_route_predicate(
666 config.add_route_predicate(
644 'repo_accepted_types', RepoTypeRoutePredicate)
667 'repo_accepted_types', RepoTypeRoutePredicate)
645 config.add_route_predicate(
668 config.add_route_predicate(
669 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
670 config.add_route_predicate(
646 'repo_group_route', RepoGroupRoutePredicate)
671 'repo_group_route', RepoGroupRoutePredicate)
647 config.add_route_predicate(
672 config.add_route_predicate(
648 'user_group_route', UserGroupRoutePredicate)
673 'user_group_route', UserGroupRoutePredicate)
649 config.add_route_predicate(
674 config.add_route_predicate(
650 'user_route_with_default', UserRouteWithDefaultPredicate)
675 'user_route_with_default', UserRouteWithDefaultPredicate)
651 config.add_route_predicate(
676 config.add_route_predicate(
652 'user_route', UserRoutePredicate) No newline at end of file
677 'user_route', UserRoutePredicate)
@@ -1,461 +1,462 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import re
21 import re
22 import logging
22 import logging
23 import collections
23 import collections
24
24
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26
26
27 from rhodecode.apps._base import BaseAppView
27 from rhodecode.apps._base import BaseAppView
28 from rhodecode.lib import helpers as h
28 from rhodecode.lib import helpers as h
29 from rhodecode.lib.auth import (
29 from rhodecode.lib.auth import (
30 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator,
30 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator,
31 CSRFRequired)
31 CSRFRequired)
32 from rhodecode.lib.index import searcher_from_config
32 from rhodecode.lib.index import searcher_from_config
33 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
33 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
34 from rhodecode.lib.ext_json import json
34 from rhodecode.lib.ext_json import json
35 from rhodecode.model.db import (
35 from rhodecode.model.db import (
36 func, or_, in_filter_generator, Repository, RepoGroup, User, UserGroup)
36 func, true, or_, in_filter_generator, Repository, RepoGroup, User, UserGroup)
37 from rhodecode.model.repo import RepoModel
37 from rhodecode.model.repo import RepoModel
38 from rhodecode.model.repo_group import RepoGroupModel
38 from rhodecode.model.repo_group import RepoGroupModel
39 from rhodecode.model.scm import RepoGroupList, RepoList
39 from rhodecode.model.scm import RepoGroupList, RepoList
40 from rhodecode.model.user import UserModel
40 from rhodecode.model.user import UserModel
41 from rhodecode.model.user_group import UserGroupModel
41 from rhodecode.model.user_group import UserGroupModel
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class HomeView(BaseAppView):
46 class HomeView(BaseAppView):
47
47
48 def load_default_context(self):
48 def load_default_context(self):
49 c = self._get_local_tmpl_context()
49 c = self._get_local_tmpl_context()
50 c.user = c.auth_user.get_instance()
50 c.user = c.auth_user.get_instance()
51
51
52 return c
52 return c
53
53
54 @LoginRequired()
54 @LoginRequired()
55 @view_config(
55 @view_config(
56 route_name='user_autocomplete_data', request_method='GET',
56 route_name='user_autocomplete_data', request_method='GET',
57 renderer='json_ext', xhr=True)
57 renderer='json_ext', xhr=True)
58 def user_autocomplete_data(self):
58 def user_autocomplete_data(self):
59 self.load_default_context()
59 self.load_default_context()
60 query = self.request.GET.get('query')
60 query = self.request.GET.get('query')
61 active = str2bool(self.request.GET.get('active') or True)
61 active = str2bool(self.request.GET.get('active') or True)
62 include_groups = str2bool(self.request.GET.get('user_groups'))
62 include_groups = str2bool(self.request.GET.get('user_groups'))
63 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
63 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
64 skip_default_user = str2bool(self.request.GET.get('skip_default_user'))
64 skip_default_user = str2bool(self.request.GET.get('skip_default_user'))
65
65
66 log.debug('generating user list, query:%s, active:%s, with_groups:%s',
66 log.debug('generating user list, query:%s, active:%s, with_groups:%s',
67 query, active, include_groups)
67 query, active, include_groups)
68
68
69 _users = UserModel().get_users(
69 _users = UserModel().get_users(
70 name_contains=query, only_active=active)
70 name_contains=query, only_active=active)
71
71
72 def maybe_skip_default_user(usr):
72 def maybe_skip_default_user(usr):
73 if skip_default_user and usr['username'] == UserModel.cls.DEFAULT_USER:
73 if skip_default_user and usr['username'] == UserModel.cls.DEFAULT_USER:
74 return False
74 return False
75 return True
75 return True
76 _users = filter(maybe_skip_default_user, _users)
76 _users = filter(maybe_skip_default_user, _users)
77
77
78 if include_groups:
78 if include_groups:
79 # extend with user groups
79 # extend with user groups
80 _user_groups = UserGroupModel().get_user_groups(
80 _user_groups = UserGroupModel().get_user_groups(
81 name_contains=query, only_active=active,
81 name_contains=query, only_active=active,
82 expand_groups=expand_groups)
82 expand_groups=expand_groups)
83 _users = _users + _user_groups
83 _users = _users + _user_groups
84
84
85 return {'suggestions': _users}
85 return {'suggestions': _users}
86
86
87 @LoginRequired()
87 @LoginRequired()
88 @NotAnonymous()
88 @NotAnonymous()
89 @view_config(
89 @view_config(
90 route_name='user_group_autocomplete_data', request_method='GET',
90 route_name='user_group_autocomplete_data', request_method='GET',
91 renderer='json_ext', xhr=True)
91 renderer='json_ext', xhr=True)
92 def user_group_autocomplete_data(self):
92 def user_group_autocomplete_data(self):
93 self.load_default_context()
93 self.load_default_context()
94 query = self.request.GET.get('query')
94 query = self.request.GET.get('query')
95 active = str2bool(self.request.GET.get('active') or True)
95 active = str2bool(self.request.GET.get('active') or True)
96 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
96 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
97
97
98 log.debug('generating user group list, query:%s, active:%s',
98 log.debug('generating user group list, query:%s, active:%s',
99 query, active)
99 query, active)
100
100
101 _user_groups = UserGroupModel().get_user_groups(
101 _user_groups = UserGroupModel().get_user_groups(
102 name_contains=query, only_active=active,
102 name_contains=query, only_active=active,
103 expand_groups=expand_groups)
103 expand_groups=expand_groups)
104 _user_groups = _user_groups
104 _user_groups = _user_groups
105
105
106 return {'suggestions': _user_groups}
106 return {'suggestions': _user_groups}
107
107
108 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
108 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
109 org_query = name_contains
109 org_query = name_contains
110 allowed_ids = self._rhodecode_user.repo_acl_ids(
110 allowed_ids = self._rhodecode_user.repo_acl_ids(
111 ['repository.read', 'repository.write', 'repository.admin'],
111 ['repository.read', 'repository.write', 'repository.admin'],
112 cache=False, name_filter=name_contains) or [-1]
112 cache=False, name_filter=name_contains) or [-1]
113
113
114 query = Repository.query()\
114 query = Repository.query()\
115 .order_by(func.length(Repository.repo_name))\
115 .order_by(func.length(Repository.repo_name))\
116 .order_by(Repository.repo_name)\
116 .order_by(Repository.repo_name)\
117 .filter(Repository.archived.isnot(true()))\
117 .filter(or_(
118 .filter(or_(
118 # generate multiple IN to fix limitation problems
119 # generate multiple IN to fix limitation problems
119 *in_filter_generator(Repository.repo_id, allowed_ids)
120 *in_filter_generator(Repository.repo_id, allowed_ids)
120 ))
121 ))
121
122
122 if repo_type:
123 if repo_type:
123 query = query.filter(Repository.repo_type == repo_type)
124 query = query.filter(Repository.repo_type == repo_type)
124
125
125 if name_contains:
126 if name_contains:
126 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
127 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
127 query = query.filter(
128 query = query.filter(
128 Repository.repo_name.ilike(ilike_expression))
129 Repository.repo_name.ilike(ilike_expression))
129 query = query.limit(limit)
130 query = query.limit(limit)
130
131
131 acl_iter = query
132 acl_iter = query
132
133
133 return [
134 return [
134 {
135 {
135 'id': obj.repo_name,
136 'id': obj.repo_name,
136 'value': org_query,
137 'value': org_query,
137 'value_display': obj.repo_name,
138 'value_display': obj.repo_name,
138 'text': obj.repo_name,
139 'text': obj.repo_name,
139 'type': 'repo',
140 'type': 'repo',
140 'repo_id': obj.repo_id,
141 'repo_id': obj.repo_id,
141 'repo_type': obj.repo_type,
142 'repo_type': obj.repo_type,
142 'private': obj.private,
143 'private': obj.private,
143 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
144 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
144 }
145 }
145 for obj in acl_iter]
146 for obj in acl_iter]
146
147
147 def _get_repo_group_list(self, name_contains=None, limit=20):
148 def _get_repo_group_list(self, name_contains=None, limit=20):
148 org_query = name_contains
149 org_query = name_contains
149 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
150 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
150 ['group.read', 'group.write', 'group.admin'],
151 ['group.read', 'group.write', 'group.admin'],
151 cache=False, name_filter=name_contains) or [-1]
152 cache=False, name_filter=name_contains) or [-1]
152
153
153 query = RepoGroup.query()\
154 query = RepoGroup.query()\
154 .order_by(func.length(RepoGroup.group_name))\
155 .order_by(func.length(RepoGroup.group_name))\
155 .order_by(RepoGroup.group_name) \
156 .order_by(RepoGroup.group_name) \
156 .filter(or_(
157 .filter(or_(
157 # generate multiple IN to fix limitation problems
158 # generate multiple IN to fix limitation problems
158 *in_filter_generator(RepoGroup.group_id, allowed_ids)
159 *in_filter_generator(RepoGroup.group_id, allowed_ids)
159 ))
160 ))
160
161
161 if name_contains:
162 if name_contains:
162 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
163 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
163 query = query.filter(
164 query = query.filter(
164 RepoGroup.group_name.ilike(ilike_expression))
165 RepoGroup.group_name.ilike(ilike_expression))
165 query = query.limit(limit)
166 query = query.limit(limit)
166
167
167 acl_iter = query
168 acl_iter = query
168
169
169 return [
170 return [
170 {
171 {
171 'id': obj.group_name,
172 'id': obj.group_name,
172 'value': org_query,
173 'value': org_query,
173 'value_display': obj.group_name,
174 'value_display': obj.group_name,
174 'type': 'repo_group',
175 'type': 'repo_group',
175 'url': h.route_path(
176 'url': h.route_path(
176 'repo_group_home', repo_group_name=obj.group_name)
177 'repo_group_home', repo_group_name=obj.group_name)
177 }
178 }
178 for obj in acl_iter]
179 for obj in acl_iter]
179
180
180 def _get_user_list(self, name_contains=None, limit=20):
181 def _get_user_list(self, name_contains=None, limit=20):
181 org_query = name_contains
182 org_query = name_contains
182 if not name_contains:
183 if not name_contains:
183 return []
184 return []
184
185
185 name_contains = re.compile('(?:user:)(.+)').findall(name_contains)
186 name_contains = re.compile('(?:user:)(.+)').findall(name_contains)
186 if len(name_contains) != 1:
187 if len(name_contains) != 1:
187 return []
188 return []
188 name_contains = name_contains[0]
189 name_contains = name_contains[0]
189
190
190 query = User.query()\
191 query = User.query()\
191 .order_by(func.length(User.username))\
192 .order_by(func.length(User.username))\
192 .order_by(User.username) \
193 .order_by(User.username) \
193 .filter(User.username != User.DEFAULT_USER)
194 .filter(User.username != User.DEFAULT_USER)
194
195
195 if name_contains:
196 if name_contains:
196 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
197 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
197 query = query.filter(
198 query = query.filter(
198 User.username.ilike(ilike_expression))
199 User.username.ilike(ilike_expression))
199 query = query.limit(limit)
200 query = query.limit(limit)
200
201
201 acl_iter = query
202 acl_iter = query
202
203
203 return [
204 return [
204 {
205 {
205 'id': obj.user_id,
206 'id': obj.user_id,
206 'value': org_query,
207 'value': org_query,
207 'value_display': obj.username,
208 'value_display': obj.username,
208 'type': 'user',
209 'type': 'user',
209 'icon_link': h.gravatar_url(obj.email, 30),
210 'icon_link': h.gravatar_url(obj.email, 30),
210 'url': h.route_path(
211 'url': h.route_path(
211 'user_profile', username=obj.username)
212 'user_profile', username=obj.username)
212 }
213 }
213 for obj in acl_iter]
214 for obj in acl_iter]
214
215
215 def _get_user_groups_list(self, name_contains=None, limit=20):
216 def _get_user_groups_list(self, name_contains=None, limit=20):
216 org_query = name_contains
217 org_query = name_contains
217 if not name_contains:
218 if not name_contains:
218 return []
219 return []
219
220
220 name_contains = re.compile('(?:user_group:)(.+)').findall(name_contains)
221 name_contains = re.compile('(?:user_group:)(.+)').findall(name_contains)
221 if len(name_contains) != 1:
222 if len(name_contains) != 1:
222 return []
223 return []
223 name_contains = name_contains[0]
224 name_contains = name_contains[0]
224
225
225 query = UserGroup.query()\
226 query = UserGroup.query()\
226 .order_by(func.length(UserGroup.users_group_name))\
227 .order_by(func.length(UserGroup.users_group_name))\
227 .order_by(UserGroup.users_group_name)
228 .order_by(UserGroup.users_group_name)
228
229
229 if name_contains:
230 if name_contains:
230 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
231 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
231 query = query.filter(
232 query = query.filter(
232 UserGroup.users_group_name.ilike(ilike_expression))
233 UserGroup.users_group_name.ilike(ilike_expression))
233 query = query.limit(limit)
234 query = query.limit(limit)
234
235
235 acl_iter = query
236 acl_iter = query
236
237
237 return [
238 return [
238 {
239 {
239 'id': obj.users_group_id,
240 'id': obj.users_group_id,
240 'value': org_query,
241 'value': org_query,
241 'value_display': obj.users_group_name,
242 'value_display': obj.users_group_name,
242 'type': 'user_group',
243 'type': 'user_group',
243 'url': h.route_path(
244 'url': h.route_path(
244 'user_group_profile', user_group_name=obj.users_group_name)
245 'user_group_profile', user_group_name=obj.users_group_name)
245 }
246 }
246 for obj in acl_iter]
247 for obj in acl_iter]
247
248
248 def _get_hash_commit_list(self, auth_user, query):
249 def _get_hash_commit_list(self, auth_user, query):
249 org_query = query
250 org_query = query
250 if not query or len(query) < 3:
251 if not query or len(query) < 3:
251 return []
252 return []
252
253
253 commit_hashes = re.compile('(?:commit:)([0-9a-f]{2,40})').findall(query)
254 commit_hashes = re.compile('(?:commit:)([0-9a-f]{2,40})').findall(query)
254
255
255 if len(commit_hashes) != 1:
256 if len(commit_hashes) != 1:
256 return []
257 return []
257 commit_hash = commit_hashes[0]
258 commit_hash = commit_hashes[0]
258
259
259 searcher = searcher_from_config(self.request.registry.settings)
260 searcher = searcher_from_config(self.request.registry.settings)
260 result = searcher.search(
261 result = searcher.search(
261 'commit_id:%s*' % commit_hash, 'commit', auth_user,
262 'commit_id:%s*' % commit_hash, 'commit', auth_user,
262 raise_on_exc=False)
263 raise_on_exc=False)
263
264
264 return [
265 return [
265 {
266 {
266 'id': entry['commit_id'],
267 'id': entry['commit_id'],
267 'value': org_query,
268 'value': org_query,
268 'value_display': 'repo `{}` commit: {}'.format(
269 'value_display': 'repo `{}` commit: {}'.format(
269 entry['repository'], entry['commit_id']),
270 entry['repository'], entry['commit_id']),
270 'type': 'commit',
271 'type': 'commit',
271 'repo': entry['repository'],
272 'repo': entry['repository'],
272 'url': h.route_path(
273 'url': h.route_path(
273 'repo_commit',
274 'repo_commit',
274 repo_name=entry['repository'], commit_id=entry['commit_id'])
275 repo_name=entry['repository'], commit_id=entry['commit_id'])
275 }
276 }
276 for entry in result['results']]
277 for entry in result['results']]
277
278
278 @LoginRequired()
279 @LoginRequired()
279 @view_config(
280 @view_config(
280 route_name='repo_list_data', request_method='GET',
281 route_name='repo_list_data', request_method='GET',
281 renderer='json_ext', xhr=True)
282 renderer='json_ext', xhr=True)
282 def repo_list_data(self):
283 def repo_list_data(self):
283 _ = self.request.translate
284 _ = self.request.translate
284 self.load_default_context()
285 self.load_default_context()
285
286
286 query = self.request.GET.get('query')
287 query = self.request.GET.get('query')
287 repo_type = self.request.GET.get('repo_type')
288 repo_type = self.request.GET.get('repo_type')
288 log.debug('generating repo list, query:%s, repo_type:%s',
289 log.debug('generating repo list, query:%s, repo_type:%s',
289 query, repo_type)
290 query, repo_type)
290
291
291 res = []
292 res = []
292 repos = self._get_repo_list(query, repo_type=repo_type)
293 repos = self._get_repo_list(query, repo_type=repo_type)
293 if repos:
294 if repos:
294 res.append({
295 res.append({
295 'text': _('Repositories'),
296 'text': _('Repositories'),
296 'children': repos
297 'children': repos
297 })
298 })
298
299
299 data = {
300 data = {
300 'more': False,
301 'more': False,
301 'results': res
302 'results': res
302 }
303 }
303 return data
304 return data
304
305
305 @LoginRequired()
306 @LoginRequired()
306 @view_config(
307 @view_config(
307 route_name='goto_switcher_data', request_method='GET',
308 route_name='goto_switcher_data', request_method='GET',
308 renderer='json_ext', xhr=True)
309 renderer='json_ext', xhr=True)
309 def goto_switcher_data(self):
310 def goto_switcher_data(self):
310 c = self.load_default_context()
311 c = self.load_default_context()
311
312
312 _ = self.request.translate
313 _ = self.request.translate
313
314
314 query = self.request.GET.get('query')
315 query = self.request.GET.get('query')
315 log.debug('generating main filter data, query %s', query)
316 log.debug('generating main filter data, query %s', query)
316
317
317 default_search_val = u'Full text search for: `{}`'.format(query)
318 default_search_val = u'Full text search for: `{}`'.format(query)
318 res = []
319 res = []
319 if not query:
320 if not query:
320 return {'suggestions': res}
321 return {'suggestions': res}
321
322
322 res.append({
323 res.append({
323 'id': -1,
324 'id': -1,
324 'value': query,
325 'value': query,
325 'value_display': default_search_val,
326 'value_display': default_search_val,
326 'type': 'search',
327 'type': 'search',
327 'url': h.route_path(
328 'url': h.route_path(
328 'search', _query={'q': query})
329 'search', _query={'q': query})
329 })
330 })
330 repo_group_id = safe_int(self.request.GET.get('repo_group_id'))
331 repo_group_id = safe_int(self.request.GET.get('repo_group_id'))
331 if repo_group_id:
332 if repo_group_id:
332 repo_group = RepoGroup.get(repo_group_id)
333 repo_group = RepoGroup.get(repo_group_id)
333 composed_hint = '{}/{}'.format(repo_group.group_name, query)
334 composed_hint = '{}/{}'.format(repo_group.group_name, query)
334 show_hint = not query.startswith(repo_group.group_name)
335 show_hint = not query.startswith(repo_group.group_name)
335 if repo_group and show_hint:
336 if repo_group and show_hint:
336 hint = u'Group search: `{}`'.format(composed_hint)
337 hint = u'Group search: `{}`'.format(composed_hint)
337 res.append({
338 res.append({
338 'id': -1,
339 'id': -1,
339 'value': composed_hint,
340 'value': composed_hint,
340 'value_display': hint,
341 'value_display': hint,
341 'type': 'hint',
342 'type': 'hint',
342 'url': ""
343 'url': ""
343 })
344 })
344
345
345 repo_groups = self._get_repo_group_list(query)
346 repo_groups = self._get_repo_group_list(query)
346 for serialized_repo_group in repo_groups:
347 for serialized_repo_group in repo_groups:
347 res.append(serialized_repo_group)
348 res.append(serialized_repo_group)
348
349
349 repos = self._get_repo_list(query)
350 repos = self._get_repo_list(query)
350 for serialized_repo in repos:
351 for serialized_repo in repos:
351 res.append(serialized_repo)
352 res.append(serialized_repo)
352
353
353 # TODO(marcink): permissions for that ?
354 # TODO(marcink): permissions for that ?
354 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
355 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
355 if allowed_user_search:
356 if allowed_user_search:
356 users = self._get_user_list(query)
357 users = self._get_user_list(query)
357 for serialized_user in users:
358 for serialized_user in users:
358 res.append(serialized_user)
359 res.append(serialized_user)
359
360
360 user_groups = self._get_user_groups_list(query)
361 user_groups = self._get_user_groups_list(query)
361 for serialized_user_group in user_groups:
362 for serialized_user_group in user_groups:
362 res.append(serialized_user_group)
363 res.append(serialized_user_group)
363
364
364 commits = self._get_hash_commit_list(c.auth_user, query)
365 commits = self._get_hash_commit_list(c.auth_user, query)
365 if commits:
366 if commits:
366 unique_repos = collections.OrderedDict()
367 unique_repos = collections.OrderedDict()
367 for commit in commits:
368 for commit in commits:
368 repo_name = commit['repo']
369 repo_name = commit['repo']
369 unique_repos.setdefault(repo_name, []).append(commit)
370 unique_repos.setdefault(repo_name, []).append(commit)
370
371
371 for repo, commits in unique_repos.items():
372 for repo, commits in unique_repos.items():
372 for commit in commits:
373 for commit in commits:
373 res.append(commit)
374 res.append(commit)
374
375
375 return {'suggestions': res}
376 return {'suggestions': res}
376
377
377 def _get_groups_and_repos(self, repo_group_id=None):
378 def _get_groups_and_repos(self, repo_group_id=None):
378 # repo groups groups
379 # repo groups groups
379 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
380 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
380 _perms = ['group.read', 'group.write', 'group.admin']
381 _perms = ['group.read', 'group.write', 'group.admin']
381 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
382 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
382 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
383 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
383 repo_group_list=repo_group_list_acl, admin=False)
384 repo_group_list=repo_group_list_acl, admin=False)
384
385
385 # repositories
386 # repositories
386 repo_list = Repository.get_all_repos(group_id=repo_group_id)
387 repo_list = Repository.get_all_repos(group_id=repo_group_id)
387 _perms = ['repository.read', 'repository.write', 'repository.admin']
388 _perms = ['repository.read', 'repository.write', 'repository.admin']
388 repo_list_acl = RepoList(repo_list, perm_set=_perms)
389 repo_list_acl = RepoList(repo_list, perm_set=_perms)
389 repo_data = RepoModel().get_repos_as_dict(
390 repo_data = RepoModel().get_repos_as_dict(
390 repo_list=repo_list_acl, admin=False)
391 repo_list=repo_list_acl, admin=False)
391
392
392 return repo_data, repo_group_data
393 return repo_data, repo_group_data
393
394
394 @LoginRequired()
395 @LoginRequired()
395 @view_config(
396 @view_config(
396 route_name='home', request_method='GET',
397 route_name='home', request_method='GET',
397 renderer='rhodecode:templates/index.mako')
398 renderer='rhodecode:templates/index.mako')
398 def main_page(self):
399 def main_page(self):
399 c = self.load_default_context()
400 c = self.load_default_context()
400 c.repo_group = None
401 c.repo_group = None
401
402
402 repo_data, repo_group_data = self._get_groups_and_repos()
403 repo_data, repo_group_data = self._get_groups_and_repos()
403 # json used to render the grids
404 # json used to render the grids
404 c.repos_data = json.dumps(repo_data)
405 c.repos_data = json.dumps(repo_data)
405 c.repo_groups_data = json.dumps(repo_group_data)
406 c.repo_groups_data = json.dumps(repo_group_data)
406
407
407 return self._get_template_context(c)
408 return self._get_template_context(c)
408
409
409 @LoginRequired()
410 @LoginRequired()
410 @HasRepoGroupPermissionAnyDecorator(
411 @HasRepoGroupPermissionAnyDecorator(
411 'group.read', 'group.write', 'group.admin')
412 'group.read', 'group.write', 'group.admin')
412 @view_config(
413 @view_config(
413 route_name='repo_group_home', request_method='GET',
414 route_name='repo_group_home', request_method='GET',
414 renderer='rhodecode:templates/index_repo_group.mako')
415 renderer='rhodecode:templates/index_repo_group.mako')
415 @view_config(
416 @view_config(
416 route_name='repo_group_home_slash', request_method='GET',
417 route_name='repo_group_home_slash', request_method='GET',
417 renderer='rhodecode:templates/index_repo_group.mako')
418 renderer='rhodecode:templates/index_repo_group.mako')
418 def repo_group_main_page(self):
419 def repo_group_main_page(self):
419 c = self.load_default_context()
420 c = self.load_default_context()
420 c.repo_group = self.request.db_repo_group
421 c.repo_group = self.request.db_repo_group
421 repo_data, repo_group_data = self._get_groups_and_repos(
422 repo_data, repo_group_data = self._get_groups_and_repos(
422 c.repo_group.group_id)
423 c.repo_group.group_id)
423
424
424 # json used to render the grids
425 # json used to render the grids
425 c.repos_data = json.dumps(repo_data)
426 c.repos_data = json.dumps(repo_data)
426 c.repo_groups_data = json.dumps(repo_group_data)
427 c.repo_groups_data = json.dumps(repo_group_data)
427
428
428 return self._get_template_context(c)
429 return self._get_template_context(c)
429
430
430 @LoginRequired()
431 @LoginRequired()
431 @CSRFRequired()
432 @CSRFRequired()
432 @view_config(
433 @view_config(
433 route_name='markup_preview', request_method='POST',
434 route_name='markup_preview', request_method='POST',
434 renderer='string', xhr=True)
435 renderer='string', xhr=True)
435 def markup_preview(self):
436 def markup_preview(self):
436 # Technically a CSRF token is not needed as no state changes with this
437 # Technically a CSRF token is not needed as no state changes with this
437 # call. However, as this is a POST is better to have it, so automated
438 # call. However, as this is a POST is better to have it, so automated
438 # tools don't flag it as potential CSRF.
439 # tools don't flag it as potential CSRF.
439 # Post is required because the payload could be bigger than the maximum
440 # Post is required because the payload could be bigger than the maximum
440 # allowed by GET.
441 # allowed by GET.
441
442
442 text = self.request.POST.get('text')
443 text = self.request.POST.get('text')
443 renderer = self.request.POST.get('renderer') or 'rst'
444 renderer = self.request.POST.get('renderer') or 'rst'
444 if text:
445 if text:
445 return h.render(text, renderer=renderer, mentions=True)
446 return h.render(text, renderer=renderer, mentions=True)
446 return ''
447 return ''
447
448
448 @LoginRequired()
449 @LoginRequired()
449 @CSRFRequired()
450 @CSRFRequired()
450 @view_config(
451 @view_config(
451 route_name='store_user_session_value', request_method='POST',
452 route_name='store_user_session_value', request_method='POST',
452 renderer='string', xhr=True)
453 renderer='string', xhr=True)
453 def store_user_session_attr(self):
454 def store_user_session_attr(self):
454 key = self.request.POST.get('key')
455 key = self.request.POST.get('key')
455 val = self.request.POST.get('val')
456 val = self.request.POST.get('val')
456
457
457 existing_value = self.request.session.get(key)
458 existing_value = self.request.session.get(key)
458 if existing_value != val:
459 if existing_value != val:
459 self.request.session[key] = val
460 self.request.session[key] = val
460
461
461 return 'stored:{}'.format(key)
462 return 'stored:{}'.format(key)
@@ -1,476 +1,483 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 from rhodecode.apps._base import add_route_with_slash
20 from rhodecode.apps._base import add_route_with_slash
21
21
22
22
23 def includeme(config):
23 def includeme(config):
24
24
25 # repo creating checks, special cases that aren't repo routes
25 # repo creating checks, special cases that aren't repo routes
26 config.add_route(
26 config.add_route(
27 name='repo_creating',
27 name='repo_creating',
28 pattern='/{repo_name:.*?[^/]}/repo_creating')
28 pattern='/{repo_name:.*?[^/]}/repo_creating')
29
29
30 config.add_route(
30 config.add_route(
31 name='repo_creating_check',
31 name='repo_creating_check',
32 pattern='/{repo_name:.*?[^/]}/repo_creating_check')
32 pattern='/{repo_name:.*?[^/]}/repo_creating_check')
33
33
34 # Summary
34 # Summary
35 # NOTE(marcink): one additional route is defined in very bottom, catch
35 # NOTE(marcink): one additional route is defined in very bottom, catch
36 # all pattern
36 # all pattern
37 config.add_route(
37 config.add_route(
38 name='repo_summary_explicit',
38 name='repo_summary_explicit',
39 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
39 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
40 config.add_route(
40 config.add_route(
41 name='repo_summary_commits',
41 name='repo_summary_commits',
42 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
42 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
43
43
44 # Commits
44 # Commits
45 config.add_route(
45 config.add_route(
46 name='repo_commit',
46 name='repo_commit',
47 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
47 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
48
48
49 config.add_route(
49 config.add_route(
50 name='repo_commit_children',
50 name='repo_commit_children',
51 pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True)
51 pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True)
52
52
53 config.add_route(
53 config.add_route(
54 name='repo_commit_parents',
54 name='repo_commit_parents',
55 pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True)
55 pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True)
56
56
57 config.add_route(
57 config.add_route(
58 name='repo_commit_raw',
58 name='repo_commit_raw',
59 pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True)
59 pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True)
60
60
61 config.add_route(
61 config.add_route(
62 name='repo_commit_patch',
62 name='repo_commit_patch',
63 pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True)
63 pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True)
64
64
65 config.add_route(
65 config.add_route(
66 name='repo_commit_download',
66 name='repo_commit_download',
67 pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True)
67 pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True)
68
68
69 config.add_route(
69 config.add_route(
70 name='repo_commit_data',
70 name='repo_commit_data',
71 pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True)
71 pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True)
72
72
73 config.add_route(
73 config.add_route(
74 name='repo_commit_comment_create',
74 name='repo_commit_comment_create',
75 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True)
75 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True)
76
76
77 config.add_route(
77 config.add_route(
78 name='repo_commit_comment_preview',
78 name='repo_commit_comment_preview',
79 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True)
79 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True)
80
80
81 config.add_route(
81 config.add_route(
82 name='repo_commit_comment_delete',
82 name='repo_commit_comment_delete',
83 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True)
83 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True)
84
84
85 # still working url for backward compat.
85 # still working url for backward compat.
86 config.add_route(
86 config.add_route(
87 name='repo_commit_raw_deprecated',
87 name='repo_commit_raw_deprecated',
88 pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True)
88 pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True)
89
89
90 # Files
90 # Files
91 config.add_route(
91 config.add_route(
92 name='repo_archivefile',
92 name='repo_archivefile',
93 pattern='/{repo_name:.*?[^/]}/archive/{fname}', repo_route=True)
93 pattern='/{repo_name:.*?[^/]}/archive/{fname}', repo_route=True)
94
94
95 config.add_route(
95 config.add_route(
96 name='repo_files_diff',
96 name='repo_files_diff',
97 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
97 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
98 config.add_route( # legacy route to make old links work
98 config.add_route( # legacy route to make old links work
99 name='repo_files_diff_2way_redirect',
99 name='repo_files_diff_2way_redirect',
100 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
100 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
101
101
102 config.add_route(
102 config.add_route(
103 name='repo_files',
103 name='repo_files',
104 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
104 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
105 config.add_route(
105 config.add_route(
106 name='repo_files:default_path',
106 name='repo_files:default_path',
107 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
107 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
108 config.add_route(
108 config.add_route(
109 name='repo_files:default_commit',
109 name='repo_files:default_commit',
110 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
110 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
111
111
112 config.add_route(
112 config.add_route(
113 name='repo_files:rendered',
113 name='repo_files:rendered',
114 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
114 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
115
115
116 config.add_route(
116 config.add_route(
117 name='repo_files:annotated',
117 name='repo_files:annotated',
118 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
118 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
119 config.add_route(
119 config.add_route(
120 name='repo_files:annotated_previous',
120 name='repo_files:annotated_previous',
121 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
121 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
122
122
123 config.add_route(
123 config.add_route(
124 name='repo_nodetree_full',
124 name='repo_nodetree_full',
125 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
125 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
126 config.add_route(
126 config.add_route(
127 name='repo_nodetree_full:default_path',
127 name='repo_nodetree_full:default_path',
128 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
128 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
129
129
130 config.add_route(
130 config.add_route(
131 name='repo_files_nodelist',
131 name='repo_files_nodelist',
132 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
132 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
133
133
134 config.add_route(
134 config.add_route(
135 name='repo_file_raw',
135 name='repo_file_raw',
136 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
136 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
137
137
138 config.add_route(
138 config.add_route(
139 name='repo_file_download',
139 name='repo_file_download',
140 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
140 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
141 config.add_route( # backward compat to keep old links working
141 config.add_route( # backward compat to keep old links working
142 name='repo_file_download:legacy',
142 name='repo_file_download:legacy',
143 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
143 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
144 repo_route=True)
144 repo_route=True)
145
145
146 config.add_route(
146 config.add_route(
147 name='repo_file_history',
147 name='repo_file_history',
148 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
148 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
149
149
150 config.add_route(
150 config.add_route(
151 name='repo_file_authors',
151 name='repo_file_authors',
152 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
152 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
153
153
154 config.add_route(
154 config.add_route(
155 name='repo_files_remove_file',
155 name='repo_files_remove_file',
156 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
156 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
157 repo_route=True)
157 repo_route=True)
158 config.add_route(
158 config.add_route(
159 name='repo_files_delete_file',
159 name='repo_files_delete_file',
160 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
160 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
161 repo_route=True)
161 repo_route=True)
162 config.add_route(
162 config.add_route(
163 name='repo_files_edit_file',
163 name='repo_files_edit_file',
164 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
164 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
165 repo_route=True)
165 repo_route=True)
166 config.add_route(
166 config.add_route(
167 name='repo_files_update_file',
167 name='repo_files_update_file',
168 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
168 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
169 repo_route=True)
169 repo_route=True)
170 config.add_route(
170 config.add_route(
171 name='repo_files_add_file',
171 name='repo_files_add_file',
172 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
172 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
173 repo_route=True)
173 repo_route=True)
174 config.add_route(
174 config.add_route(
175 name='repo_files_create_file',
175 name='repo_files_create_file',
176 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
176 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
177 repo_route=True)
177 repo_route=True)
178
178
179 # Refs data
179 # Refs data
180 config.add_route(
180 config.add_route(
181 name='repo_refs_data',
181 name='repo_refs_data',
182 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
182 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
183
183
184 config.add_route(
184 config.add_route(
185 name='repo_refs_changelog_data',
185 name='repo_refs_changelog_data',
186 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
186 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
187
187
188 config.add_route(
188 config.add_route(
189 name='repo_stats',
189 name='repo_stats',
190 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
190 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
191
191
192 # Changelog
192 # Changelog
193 config.add_route(
193 config.add_route(
194 name='repo_changelog',
194 name='repo_changelog',
195 pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True)
195 pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True)
196 config.add_route(
196 config.add_route(
197 name='repo_changelog_file',
197 name='repo_changelog_file',
198 pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True)
198 pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True)
199 config.add_route(
199 config.add_route(
200 name='repo_changelog_elements',
200 name='repo_changelog_elements',
201 pattern='/{repo_name:.*?[^/]}/changelog_elements', repo_route=True)
201 pattern='/{repo_name:.*?[^/]}/changelog_elements', repo_route=True)
202 config.add_route(
202 config.add_route(
203 name='repo_changelog_elements_file',
203 name='repo_changelog_elements_file',
204 pattern='/{repo_name:.*?[^/]}/changelog_elements/{commit_id}/{f_path:.*}', repo_route=True)
204 pattern='/{repo_name:.*?[^/]}/changelog_elements/{commit_id}/{f_path:.*}', repo_route=True)
205
205
206 # Compare
206 # Compare
207 config.add_route(
207 config.add_route(
208 name='repo_compare_select',
208 name='repo_compare_select',
209 pattern='/{repo_name:.*?[^/]}/compare', repo_route=True)
209 pattern='/{repo_name:.*?[^/]}/compare', repo_route=True)
210
210
211 config.add_route(
211 config.add_route(
212 name='repo_compare',
212 name='repo_compare',
213 pattern='/{repo_name:.*?[^/]}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', repo_route=True)
213 pattern='/{repo_name:.*?[^/]}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', repo_route=True)
214
214
215 # Tags
215 # Tags
216 config.add_route(
216 config.add_route(
217 name='tags_home',
217 name='tags_home',
218 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
218 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
219
219
220 # Branches
220 # Branches
221 config.add_route(
221 config.add_route(
222 name='branches_home',
222 name='branches_home',
223 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
223 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
224
224
225 # Bookmarks
225 # Bookmarks
226 config.add_route(
226 config.add_route(
227 name='bookmarks_home',
227 name='bookmarks_home',
228 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
228 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
229
229
230 # Forks
230 # Forks
231 config.add_route(
231 config.add_route(
232 name='repo_fork_new',
232 name='repo_fork_new',
233 pattern='/{repo_name:.*?[^/]}/fork', repo_route=True,
233 pattern='/{repo_name:.*?[^/]}/fork', repo_route=True,
234 repo_forbid_when_archived=True,
234 repo_accepted_types=['hg', 'git'])
235 repo_accepted_types=['hg', 'git'])
235
236
236 config.add_route(
237 config.add_route(
237 name='repo_fork_create',
238 name='repo_fork_create',
238 pattern='/{repo_name:.*?[^/]}/fork/create', repo_route=True,
239 pattern='/{repo_name:.*?[^/]}/fork/create', repo_route=True,
240 repo_forbid_when_archived=True,
239 repo_accepted_types=['hg', 'git'])
241 repo_accepted_types=['hg', 'git'])
240
242
241 config.add_route(
243 config.add_route(
242 name='repo_forks_show_all',
244 name='repo_forks_show_all',
243 pattern='/{repo_name:.*?[^/]}/forks', repo_route=True,
245 pattern='/{repo_name:.*?[^/]}/forks', repo_route=True,
244 repo_accepted_types=['hg', 'git'])
246 repo_accepted_types=['hg', 'git'])
245 config.add_route(
247 config.add_route(
246 name='repo_forks_data',
248 name='repo_forks_data',
247 pattern='/{repo_name:.*?[^/]}/forks/data', repo_route=True,
249 pattern='/{repo_name:.*?[^/]}/forks/data', repo_route=True,
248 repo_accepted_types=['hg', 'git'])
250 repo_accepted_types=['hg', 'git'])
249
251
250 # Pull Requests
252 # Pull Requests
251 config.add_route(
253 config.add_route(
252 name='pullrequest_show',
254 name='pullrequest_show',
253 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}',
255 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}',
254 repo_route=True)
256 repo_route=True)
255
257
256 config.add_route(
258 config.add_route(
257 name='pullrequest_show_all',
259 name='pullrequest_show_all',
258 pattern='/{repo_name:.*?[^/]}/pull-request',
260 pattern='/{repo_name:.*?[^/]}/pull-request',
259 repo_route=True, repo_accepted_types=['hg', 'git'])
261 repo_route=True, repo_accepted_types=['hg', 'git'])
260
262
261 config.add_route(
263 config.add_route(
262 name='pullrequest_show_all_data',
264 name='pullrequest_show_all_data',
263 pattern='/{repo_name:.*?[^/]}/pull-request-data',
265 pattern='/{repo_name:.*?[^/]}/pull-request-data',
264 repo_route=True, repo_accepted_types=['hg', 'git'])
266 repo_route=True, repo_accepted_types=['hg', 'git'])
265
267
266 config.add_route(
268 config.add_route(
267 name='pullrequest_repo_refs',
269 name='pullrequest_repo_refs',
268 pattern='/{repo_name:.*?[^/]}/pull-request/refs/{target_repo_name:.*?[^/]}',
270 pattern='/{repo_name:.*?[^/]}/pull-request/refs/{target_repo_name:.*?[^/]}',
269 repo_route=True)
271 repo_route=True)
270
272
271 config.add_route(
273 config.add_route(
272 name='pullrequest_repo_destinations',
274 name='pullrequest_repo_destinations',
273 pattern='/{repo_name:.*?[^/]}/pull-request/repo-destinations',
275 pattern='/{repo_name:.*?[^/]}/pull-request/repo-destinations',
274 repo_route=True)
276 repo_route=True)
275
277
276 config.add_route(
278 config.add_route(
277 name='pullrequest_new',
279 name='pullrequest_new',
278 pattern='/{repo_name:.*?[^/]}/pull-request/new',
280 pattern='/{repo_name:.*?[^/]}/pull-request/new',
279 repo_route=True, repo_accepted_types=['hg', 'git'])
281 repo_route=True, repo_accepted_types=['hg', 'git'],
282 repo_forbid_when_archived=True)
280
283
281 config.add_route(
284 config.add_route(
282 name='pullrequest_create',
285 name='pullrequest_create',
283 pattern='/{repo_name:.*?[^/]}/pull-request/create',
286 pattern='/{repo_name:.*?[^/]}/pull-request/create',
284 repo_route=True, repo_accepted_types=['hg', 'git'])
287 repo_route=True, repo_accepted_types=['hg', 'git'],
288 repo_forbid_when_archived=True)
285
289
286 config.add_route(
290 config.add_route(
287 name='pullrequest_update',
291 name='pullrequest_update',
288 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update',
292 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update',
289 repo_route=True)
293 repo_route=True, repo_forbid_when_archived=True)
290
294
291 config.add_route(
295 config.add_route(
292 name='pullrequest_merge',
296 name='pullrequest_merge',
293 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge',
297 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge',
294 repo_route=True)
298 repo_route=True, repo_forbid_when_archived=True)
295
299
296 config.add_route(
300 config.add_route(
297 name='pullrequest_delete',
301 name='pullrequest_delete',
298 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete',
302 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete',
299 repo_route=True)
303 repo_route=True, repo_forbid_when_archived=True)
300
304
301 config.add_route(
305 config.add_route(
302 name='pullrequest_comment_create',
306 name='pullrequest_comment_create',
303 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment',
307 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment',
304 repo_route=True)
308 repo_route=True)
305
309
306 config.add_route(
310 config.add_route(
307 name='pullrequest_comment_delete',
311 name='pullrequest_comment_delete',
308 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
312 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
309 repo_route=True, repo_accepted_types=['hg', 'git'])
313 repo_route=True, repo_accepted_types=['hg', 'git'])
310
314
311 # Settings
315 # Settings
312 config.add_route(
316 config.add_route(
313 name='edit_repo',
317 name='edit_repo',
314 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
318 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
315 # update is POST on edit_repo
319 # update is POST on edit_repo
316
320
317 # Settings advanced
321 # Settings advanced
318 config.add_route(
322 config.add_route(
319 name='edit_repo_advanced',
323 name='edit_repo_advanced',
320 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
324 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
321 config.add_route(
325 config.add_route(
326 name='edit_repo_advanced_archive',
327 pattern='/{repo_name:.*?[^/]}/settings/advanced/archive', repo_route=True)
328 config.add_route(
322 name='edit_repo_advanced_delete',
329 name='edit_repo_advanced_delete',
323 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
330 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
324 config.add_route(
331 config.add_route(
325 name='edit_repo_advanced_locking',
332 name='edit_repo_advanced_locking',
326 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
333 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
327 config.add_route(
334 config.add_route(
328 name='edit_repo_advanced_journal',
335 name='edit_repo_advanced_journal',
329 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
336 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
330 config.add_route(
337 config.add_route(
331 name='edit_repo_advanced_fork',
338 name='edit_repo_advanced_fork',
332 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
339 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
333
340
334 config.add_route(
341 config.add_route(
335 name='edit_repo_advanced_hooks',
342 name='edit_repo_advanced_hooks',
336 pattern='/{repo_name:.*?[^/]}/settings/advanced/hooks', repo_route=True)
343 pattern='/{repo_name:.*?[^/]}/settings/advanced/hooks', repo_route=True)
337
344
338 # Caches
345 # Caches
339 config.add_route(
346 config.add_route(
340 name='edit_repo_caches',
347 name='edit_repo_caches',
341 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
348 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
342
349
343 # Permissions
350 # Permissions
344 config.add_route(
351 config.add_route(
345 name='edit_repo_perms',
352 name='edit_repo_perms',
346 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
353 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
347
354
348 # Permissions Branch (EE feature)
355 # Permissions Branch (EE feature)
349 config.add_route(
356 config.add_route(
350 name='edit_repo_perms_branch',
357 name='edit_repo_perms_branch',
351 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions', repo_route=True)
358 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions', repo_route=True)
352 config.add_route(
359 config.add_route(
353 name='edit_repo_perms_branch_delete',
360 name='edit_repo_perms_branch_delete',
354 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions/{rule_id}/delete',
361 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions/{rule_id}/delete',
355 repo_route=True)
362 repo_route=True)
356
363
357 # Maintenance
364 # Maintenance
358 config.add_route(
365 config.add_route(
359 name='edit_repo_maintenance',
366 name='edit_repo_maintenance',
360 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
367 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
361
368
362 config.add_route(
369 config.add_route(
363 name='edit_repo_maintenance_execute',
370 name='edit_repo_maintenance_execute',
364 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
371 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
365
372
366 # Fields
373 # Fields
367 config.add_route(
374 config.add_route(
368 name='edit_repo_fields',
375 name='edit_repo_fields',
369 pattern='/{repo_name:.*?[^/]}/settings/fields', repo_route=True)
376 pattern='/{repo_name:.*?[^/]}/settings/fields', repo_route=True)
370 config.add_route(
377 config.add_route(
371 name='edit_repo_fields_create',
378 name='edit_repo_fields_create',
372 pattern='/{repo_name:.*?[^/]}/settings/fields/create', repo_route=True)
379 pattern='/{repo_name:.*?[^/]}/settings/fields/create', repo_route=True)
373 config.add_route(
380 config.add_route(
374 name='edit_repo_fields_delete',
381 name='edit_repo_fields_delete',
375 pattern='/{repo_name:.*?[^/]}/settings/fields/{field_id}/delete', repo_route=True)
382 pattern='/{repo_name:.*?[^/]}/settings/fields/{field_id}/delete', repo_route=True)
376
383
377 # Locking
384 # Locking
378 config.add_route(
385 config.add_route(
379 name='repo_edit_toggle_locking',
386 name='repo_edit_toggle_locking',
380 pattern='/{repo_name:.*?[^/]}/settings/toggle_locking', repo_route=True)
387 pattern='/{repo_name:.*?[^/]}/settings/toggle_locking', repo_route=True)
381
388
382 # Remote
389 # Remote
383 config.add_route(
390 config.add_route(
384 name='edit_repo_remote',
391 name='edit_repo_remote',
385 pattern='/{repo_name:.*?[^/]}/settings/remote', repo_route=True)
392 pattern='/{repo_name:.*?[^/]}/settings/remote', repo_route=True)
386 config.add_route(
393 config.add_route(
387 name='edit_repo_remote_pull',
394 name='edit_repo_remote_pull',
388 pattern='/{repo_name:.*?[^/]}/settings/remote/pull', repo_route=True)
395 pattern='/{repo_name:.*?[^/]}/settings/remote/pull', repo_route=True)
389 config.add_route(
396 config.add_route(
390 name='edit_repo_remote_push',
397 name='edit_repo_remote_push',
391 pattern='/{repo_name:.*?[^/]}/settings/remote/push', repo_route=True)
398 pattern='/{repo_name:.*?[^/]}/settings/remote/push', repo_route=True)
392
399
393 # Statistics
400 # Statistics
394 config.add_route(
401 config.add_route(
395 name='edit_repo_statistics',
402 name='edit_repo_statistics',
396 pattern='/{repo_name:.*?[^/]}/settings/statistics', repo_route=True)
403 pattern='/{repo_name:.*?[^/]}/settings/statistics', repo_route=True)
397 config.add_route(
404 config.add_route(
398 name='edit_repo_statistics_reset',
405 name='edit_repo_statistics_reset',
399 pattern='/{repo_name:.*?[^/]}/settings/statistics/update', repo_route=True)
406 pattern='/{repo_name:.*?[^/]}/settings/statistics/update', repo_route=True)
400
407
401 # Issue trackers
408 # Issue trackers
402 config.add_route(
409 config.add_route(
403 name='edit_repo_issuetracker',
410 name='edit_repo_issuetracker',
404 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers', repo_route=True)
411 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers', repo_route=True)
405 config.add_route(
412 config.add_route(
406 name='edit_repo_issuetracker_test',
413 name='edit_repo_issuetracker_test',
407 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/test', repo_route=True)
414 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/test', repo_route=True)
408 config.add_route(
415 config.add_route(
409 name='edit_repo_issuetracker_delete',
416 name='edit_repo_issuetracker_delete',
410 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/delete', repo_route=True)
417 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/delete', repo_route=True)
411 config.add_route(
418 config.add_route(
412 name='edit_repo_issuetracker_update',
419 name='edit_repo_issuetracker_update',
413 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/update', repo_route=True)
420 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/update', repo_route=True)
414
421
415 # VCS Settings
422 # VCS Settings
416 config.add_route(
423 config.add_route(
417 name='edit_repo_vcs',
424 name='edit_repo_vcs',
418 pattern='/{repo_name:.*?[^/]}/settings/vcs', repo_route=True)
425 pattern='/{repo_name:.*?[^/]}/settings/vcs', repo_route=True)
419 config.add_route(
426 config.add_route(
420 name='edit_repo_vcs_update',
427 name='edit_repo_vcs_update',
421 pattern='/{repo_name:.*?[^/]}/settings/vcs/update', repo_route=True)
428 pattern='/{repo_name:.*?[^/]}/settings/vcs/update', repo_route=True)
422
429
423 # svn pattern
430 # svn pattern
424 config.add_route(
431 config.add_route(
425 name='edit_repo_vcs_svn_pattern_delete',
432 name='edit_repo_vcs_svn_pattern_delete',
426 pattern='/{repo_name:.*?[^/]}/settings/vcs/svn_pattern/delete', repo_route=True)
433 pattern='/{repo_name:.*?[^/]}/settings/vcs/svn_pattern/delete', repo_route=True)
427
434
428 # Repo Review Rules (EE feature)
435 # Repo Review Rules (EE feature)
429 config.add_route(
436 config.add_route(
430 name='repo_reviewers',
437 name='repo_reviewers',
431 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
438 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
432
439
433 config.add_route(
440 config.add_route(
434 name='repo_default_reviewers_data',
441 name='repo_default_reviewers_data',
435 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
442 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
436
443
437 # Repo Automation (EE feature)
444 # Repo Automation (EE feature)
438 config.add_route(
445 config.add_route(
439 name='repo_automation',
446 name='repo_automation',
440 pattern='/{repo_name:.*?[^/]}/settings/automation', repo_route=True)
447 pattern='/{repo_name:.*?[^/]}/settings/automation', repo_route=True)
441
448
442 # Strip
449 # Strip
443 config.add_route(
450 config.add_route(
444 name='edit_repo_strip',
451 name='edit_repo_strip',
445 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
452 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
446
453
447 config.add_route(
454 config.add_route(
448 name='strip_check',
455 name='strip_check',
449 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
456 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
450
457
451 config.add_route(
458 config.add_route(
452 name='strip_execute',
459 name='strip_execute',
453 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
460 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
454
461
455 # Audit logs
462 # Audit logs
456 config.add_route(
463 config.add_route(
457 name='edit_repo_audit_logs',
464 name='edit_repo_audit_logs',
458 pattern='/{repo_name:.*?[^/]}/settings/audit_logs', repo_route=True)
465 pattern='/{repo_name:.*?[^/]}/settings/audit_logs', repo_route=True)
459
466
460 # ATOM/RSS Feed
467 # ATOM/RSS Feed
461 config.add_route(
468 config.add_route(
462 name='rss_feed_home',
469 name='rss_feed_home',
463 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
470 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
464
471
465 config.add_route(
472 config.add_route(
466 name='atom_feed_home',
473 name='atom_feed_home',
467 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
474 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
468
475
469 # NOTE(marcink): needs to be at the end for catch-all
476 # NOTE(marcink): needs to be at the end for catch-all
470 add_route_with_slash(
477 add_route_with_slash(
471 config,
478 config,
472 name='repo_summary',
479 name='repo_summary',
473 pattern='/{repo_name:.*?[^/]}', repo_route=True)
480 pattern='/{repo_name:.*?[^/]}', repo_route=True)
474
481
475 # Scan module for configuration decorators.
482 # Scan module for configuration decorators.
476 config.scan('.views', ignore='.tests')
483 config.scan('.views', ignore='.tests')
@@ -1,315 +1,336 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.tests import TestController, assert_session_flash, HG_FORK, GIT_FORK
23 from rhodecode.tests import TestController, assert_session_flash, HG_FORK, GIT_FORK
24
24
25 from rhodecode.tests.fixture import Fixture
25 from rhodecode.tests.fixture import Fixture
26 from rhodecode.lib import helpers as h
26 from rhodecode.lib import helpers as h
27
27
28 from rhodecode.model.db import Repository
28 from rhodecode.model.db import Repository
29 from rhodecode.model.repo import RepoModel
29 from rhodecode.model.repo import RepoModel
30 from rhodecode.model.user import UserModel
30 from rhodecode.model.user import UserModel
31 from rhodecode.model.meta import Session
31 from rhodecode.model.meta import Session
32
32
33 fixture = Fixture()
33 fixture = Fixture()
34
34
35
35
36 def route_path(name, params=None, **kwargs):
36 def route_path(name, params=None, **kwargs):
37 import urllib
37 import urllib
38
38
39 base_url = {
39 base_url = {
40 'repo_summary': '/{repo_name}',
40 'repo_summary': '/{repo_name}',
41 'repo_creating_check': '/{repo_name}/repo_creating_check',
41 'repo_creating_check': '/{repo_name}/repo_creating_check',
42 'repo_fork_new': '/{repo_name}/fork',
42 'repo_fork_new': '/{repo_name}/fork',
43 'repo_fork_create': '/{repo_name}/fork/create',
43 'repo_fork_create': '/{repo_name}/fork/create',
44 'repo_forks_show_all': '/{repo_name}/forks',
44 'repo_forks_show_all': '/{repo_name}/forks',
45 'repo_forks_data': '/{repo_name}/forks/data',
45 'repo_forks_data': '/{repo_name}/forks/data',
46 }[name].format(**kwargs)
46 }[name].format(**kwargs)
47
47
48 if params:
48 if params:
49 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
49 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
50 return base_url
50 return base_url
51
51
52
52
53 FORK_NAME = {
53 FORK_NAME = {
54 'hg': HG_FORK,
54 'hg': HG_FORK,
55 'git': GIT_FORK
55 'git': GIT_FORK
56 }
56 }
57
57
58
58
59 @pytest.mark.skip_backends('svn')
59 @pytest.mark.skip_backends('svn')
60 class TestRepoForkViewTests(TestController):
60 class TestRepoForkViewTests(TestController):
61
61
62 def test_show_forks(self, backend, xhr_header):
62 def test_show_forks(self, backend, xhr_header):
63 self.log_user()
63 self.log_user()
64 response = self.app.get(
64 response = self.app.get(
65 route_path('repo_forks_data', repo_name=backend.repo_name),
65 route_path('repo_forks_data', repo_name=backend.repo_name),
66 extra_environ=xhr_header)
66 extra_environ=xhr_header)
67
67
68 assert response.json == {u'data': [], u'draw': None,
68 assert response.json == {u'data': [], u'draw': None,
69 u'recordsFiltered': 0, u'recordsTotal': 0}
69 u'recordsFiltered': 0, u'recordsTotal': 0}
70
70
71 def test_no_permissions_to_fork_page(self, backend, user_util):
71 def test_no_permissions_to_fork_page(self, backend, user_util):
72 user = user_util.create_user(password='qweqwe')
72 user = user_util.create_user(password='qweqwe')
73 user_id = user.user_id
73 user_id = user.user_id
74 self.log_user(user.username, 'qweqwe')
74 self.log_user(user.username, 'qweqwe')
75
75
76 user_model = UserModel()
76 user_model = UserModel()
77 user_model.revoke_perm(user_id, 'hg.fork.repository')
77 user_model.revoke_perm(user_id, 'hg.fork.repository')
78 user_model.grant_perm(user_id, 'hg.fork.none')
78 user_model.grant_perm(user_id, 'hg.fork.none')
79 u = UserModel().get(user_id)
79 u = UserModel().get(user_id)
80 u.inherit_default_permissions = False
80 u.inherit_default_permissions = False
81 Session().commit()
81 Session().commit()
82 # try create a fork
82 # try create a fork
83 self.app.get(
83 self.app.get(
84 route_path('repo_fork_new', repo_name=backend.repo_name),
84 route_path('repo_fork_new', repo_name=backend.repo_name),
85 status=404)
85 status=404)
86
86
87 def test_no_permissions_to_fork_submit(self, backend, csrf_token, user_util):
87 def test_no_permissions_to_fork_submit(self, backend, csrf_token, user_util):
88 user = user_util.create_user(password='qweqwe')
88 user = user_util.create_user(password='qweqwe')
89 user_id = user.user_id
89 user_id = user.user_id
90 self.log_user(user.username, 'qweqwe')
90 self.log_user(user.username, 'qweqwe')
91
91
92 user_model = UserModel()
92 user_model = UserModel()
93 user_model.revoke_perm(user_id, 'hg.fork.repository')
93 user_model.revoke_perm(user_id, 'hg.fork.repository')
94 user_model.grant_perm(user_id, 'hg.fork.none')
94 user_model.grant_perm(user_id, 'hg.fork.none')
95 u = UserModel().get(user_id)
95 u = UserModel().get(user_id)
96 u.inherit_default_permissions = False
96 u.inherit_default_permissions = False
97 Session().commit()
97 Session().commit()
98 # try create a fork
98 # try create a fork
99 self.app.post(
99 self.app.post(
100 route_path('repo_fork_create', repo_name=backend.repo_name),
100 route_path('repo_fork_create', repo_name=backend.repo_name),
101 {'csrf_token': csrf_token},
101 {'csrf_token': csrf_token},
102 status=404)
102 status=404)
103
103
104 def test_fork_missing_data(self, autologin_user, backend, csrf_token):
104 def test_fork_missing_data(self, autologin_user, backend, csrf_token):
105 # try create a fork
105 # try create a fork
106 response = self.app.post(
106 response = self.app.post(
107 route_path('repo_fork_create', repo_name=backend.repo_name),
107 route_path('repo_fork_create', repo_name=backend.repo_name),
108 {'csrf_token': csrf_token},
108 {'csrf_token': csrf_token},
109 status=200)
109 status=200)
110 # test if html fill works fine
110 # test if html fill works fine
111 response.mustcontain('Missing value')
111 response.mustcontain('Missing value')
112
112
113 def test_create_fork_page(self, autologin_user, backend):
113 def test_create_fork_page(self, autologin_user, backend):
114 self.app.get(
114 self.app.get(
115 route_path('repo_fork_new', repo_name=backend.repo_name),
115 route_path('repo_fork_new', repo_name=backend.repo_name),
116 status=200)
116 status=200)
117
117
118 def test_create_and_show_fork(
118 def test_create_and_show_fork(
119 self, autologin_user, backend, csrf_token, xhr_header):
119 self, autologin_user, backend, csrf_token, xhr_header):
120
120
121 # create a fork
121 # create a fork
122 fork_name = FORK_NAME[backend.alias]
122 fork_name = FORK_NAME[backend.alias]
123 description = 'fork of vcs test'
123 description = 'fork of vcs test'
124 repo_name = backend.repo_name
124 repo_name = backend.repo_name
125 source_repo = Repository.get_by_repo_name(repo_name)
125 source_repo = Repository.get_by_repo_name(repo_name)
126 creation_args = {
126 creation_args = {
127 'repo_name': fork_name,
127 'repo_name': fork_name,
128 'repo_group': '',
128 'repo_group': '',
129 'fork_parent_id': source_repo.repo_id,
129 'fork_parent_id': source_repo.repo_id,
130 'repo_type': backend.alias,
130 'repo_type': backend.alias,
131 'description': description,
131 'description': description,
132 'private': 'False',
132 'private': 'False',
133 'landing_rev': 'rev:tip',
133 'landing_rev': 'rev:tip',
134 'csrf_token': csrf_token,
134 'csrf_token': csrf_token,
135 }
135 }
136
136
137 self.app.post(
137 self.app.post(
138 route_path('repo_fork_create', repo_name=repo_name), creation_args)
138 route_path('repo_fork_create', repo_name=repo_name), creation_args)
139
139
140 response = self.app.get(
140 response = self.app.get(
141 route_path('repo_forks_data', repo_name=repo_name),
141 route_path('repo_forks_data', repo_name=repo_name),
142 extra_environ=xhr_header)
142 extra_environ=xhr_header)
143
143
144 assert response.json['data'][0]['fork_name'] == \
144 assert response.json['data'][0]['fork_name'] == \
145 """<a href="/%s">%s</a>""" % (fork_name, fork_name)
145 """<a href="/%s">%s</a>""" % (fork_name, fork_name)
146
146
147 # remove this fork
147 # remove this fork
148 fixture.destroy_repo(fork_name)
148 fixture.destroy_repo(fork_name)
149
149
150 def test_fork_create(self, autologin_user, backend, csrf_token):
150 def test_fork_create(self, autologin_user, backend, csrf_token):
151 fork_name = FORK_NAME[backend.alias]
151 fork_name = FORK_NAME[backend.alias]
152 description = 'fork of vcs test'
152 description = 'fork of vcs test'
153 repo_name = backend.repo_name
153 repo_name = backend.repo_name
154 source_repo = Repository.get_by_repo_name(repo_name)
154 source_repo = Repository.get_by_repo_name(repo_name)
155 creation_args = {
155 creation_args = {
156 'repo_name': fork_name,
156 'repo_name': fork_name,
157 'repo_group': '',
157 'repo_group': '',
158 'fork_parent_id': source_repo.repo_id,
158 'fork_parent_id': source_repo.repo_id,
159 'repo_type': backend.alias,
159 'repo_type': backend.alias,
160 'description': description,
160 'description': description,
161 'private': 'False',
161 'private': 'False',
162 'landing_rev': 'rev:tip',
162 'landing_rev': 'rev:tip',
163 'csrf_token': csrf_token,
163 'csrf_token': csrf_token,
164 }
164 }
165 self.app.post(
165 self.app.post(
166 route_path('repo_fork_create', repo_name=repo_name), creation_args)
166 route_path('repo_fork_create', repo_name=repo_name), creation_args)
167 repo = Repository.get_by_repo_name(FORK_NAME[backend.alias])
167 repo = Repository.get_by_repo_name(FORK_NAME[backend.alias])
168 assert repo.fork.repo_name == backend.repo_name
168 assert repo.fork.repo_name == backend.repo_name
169
169
170 # run the check page that triggers the flash message
170 # run the check page that triggers the flash message
171 response = self.app.get(
171 response = self.app.get(
172 route_path('repo_creating_check', repo_name=fork_name))
172 route_path('repo_creating_check', repo_name=fork_name))
173 # test if we have a message that fork is ok
173 # test if we have a message that fork is ok
174 assert_session_flash(response,
174 assert_session_flash(response,
175 'Forked repository %s as <a href="/%s">%s</a>'
175 'Forked repository %s as <a href="/%s">%s</a>'
176 % (repo_name, fork_name, fork_name))
176 % (repo_name, fork_name, fork_name))
177
177
178 # test if the fork was created in the database
178 # test if the fork was created in the database
179 fork_repo = Session().query(Repository)\
179 fork_repo = Session().query(Repository)\
180 .filter(Repository.repo_name == fork_name).one()
180 .filter(Repository.repo_name == fork_name).one()
181
181
182 assert fork_repo.repo_name == fork_name
182 assert fork_repo.repo_name == fork_name
183 assert fork_repo.fork.repo_name == repo_name
183 assert fork_repo.fork.repo_name == repo_name
184
184
185 # test if the repository is visible in the list ?
185 # test if the repository is visible in the list ?
186 response = self.app.get(
186 response = self.app.get(
187 h.route_path('repo_summary', repo_name=fork_name))
187 h.route_path('repo_summary', repo_name=fork_name))
188 response.mustcontain(fork_name)
188 response.mustcontain(fork_name)
189 response.mustcontain(backend.alias)
189 response.mustcontain(backend.alias)
190 response.mustcontain('Fork of')
190 response.mustcontain('Fork of')
191 response.mustcontain('<a href="/%s">%s</a>' % (repo_name, repo_name))
191 response.mustcontain('<a href="/%s">%s</a>' % (repo_name, repo_name))
192
192
193 def test_fork_create_into_group(self, autologin_user, backend, csrf_token):
193 def test_fork_create_into_group(self, autologin_user, backend, csrf_token):
194 group = fixture.create_repo_group('vc')
194 group = fixture.create_repo_group('vc')
195 group_id = group.group_id
195 group_id = group.group_id
196 fork_name = FORK_NAME[backend.alias]
196 fork_name = FORK_NAME[backend.alias]
197 fork_name_full = 'vc/%s' % fork_name
197 fork_name_full = 'vc/%s' % fork_name
198 description = 'fork of vcs test'
198 description = 'fork of vcs test'
199 repo_name = backend.repo_name
199 repo_name = backend.repo_name
200 source_repo = Repository.get_by_repo_name(repo_name)
200 source_repo = Repository.get_by_repo_name(repo_name)
201 creation_args = {
201 creation_args = {
202 'repo_name': fork_name,
202 'repo_name': fork_name,
203 'repo_group': group_id,
203 'repo_group': group_id,
204 'fork_parent_id': source_repo.repo_id,
204 'fork_parent_id': source_repo.repo_id,
205 'repo_type': backend.alias,
205 'repo_type': backend.alias,
206 'description': description,
206 'description': description,
207 'private': 'False',
207 'private': 'False',
208 'landing_rev': 'rev:tip',
208 'landing_rev': 'rev:tip',
209 'csrf_token': csrf_token,
209 'csrf_token': csrf_token,
210 }
210 }
211 self.app.post(
211 self.app.post(
212 route_path('repo_fork_create', repo_name=repo_name), creation_args)
212 route_path('repo_fork_create', repo_name=repo_name), creation_args)
213 repo = Repository.get_by_repo_name(fork_name_full)
213 repo = Repository.get_by_repo_name(fork_name_full)
214 assert repo.fork.repo_name == backend.repo_name
214 assert repo.fork.repo_name == backend.repo_name
215
215
216 # run the check page that triggers the flash message
216 # run the check page that triggers the flash message
217 response = self.app.get(
217 response = self.app.get(
218 route_path('repo_creating_check', repo_name=fork_name_full))
218 route_path('repo_creating_check', repo_name=fork_name_full))
219 # test if we have a message that fork is ok
219 # test if we have a message that fork is ok
220 assert_session_flash(response,
220 assert_session_flash(response,
221 'Forked repository %s as <a href="/%s">%s</a>'
221 'Forked repository %s as <a href="/%s">%s</a>'
222 % (repo_name, fork_name_full, fork_name_full))
222 % (repo_name, fork_name_full, fork_name_full))
223
223
224 # test if the fork was created in the database
224 # test if the fork was created in the database
225 fork_repo = Session().query(Repository)\
225 fork_repo = Session().query(Repository)\
226 .filter(Repository.repo_name == fork_name_full).one()
226 .filter(Repository.repo_name == fork_name_full).one()
227
227
228 assert fork_repo.repo_name == fork_name_full
228 assert fork_repo.repo_name == fork_name_full
229 assert fork_repo.fork.repo_name == repo_name
229 assert fork_repo.fork.repo_name == repo_name
230
230
231 # test if the repository is visible in the list ?
231 # test if the repository is visible in the list ?
232 response = self.app.get(
232 response = self.app.get(
233 h.route_path('repo_summary', repo_name=fork_name_full))
233 h.route_path('repo_summary', repo_name=fork_name_full))
234 response.mustcontain(fork_name_full)
234 response.mustcontain(fork_name_full)
235 response.mustcontain(backend.alias)
235 response.mustcontain(backend.alias)
236
236
237 response.mustcontain('Fork of')
237 response.mustcontain('Fork of')
238 response.mustcontain('<a href="/%s">%s</a>' % (repo_name, repo_name))
238 response.mustcontain('<a href="/%s">%s</a>' % (repo_name, repo_name))
239
239
240 fixture.destroy_repo(fork_name_full)
240 fixture.destroy_repo(fork_name_full)
241 fixture.destroy_repo_group(group_id)
241 fixture.destroy_repo_group(group_id)
242
242
243 def test_fork_read_permission(self, backend, xhr_header, user_util):
243 def test_fork_read_permission(self, backend, xhr_header, user_util):
244 user = user_util.create_user(password='qweqwe')
244 user = user_util.create_user(password='qweqwe')
245 user_id = user.user_id
245 user_id = user.user_id
246 self.log_user(user.username, 'qweqwe')
246 self.log_user(user.username, 'qweqwe')
247
247
248 # create a fake fork
248 # create a fake fork
249 fork = user_util.create_repo(repo_type=backend.alias)
249 fork = user_util.create_repo(repo_type=backend.alias)
250 source = user_util.create_repo(repo_type=backend.alias)
250 source = user_util.create_repo(repo_type=backend.alias)
251 repo_name = source.repo_name
251 repo_name = source.repo_name
252
252
253 fork.fork_id = source.repo_id
253 fork.fork_id = source.repo_id
254 fork_name = fork.repo_name
254 fork_name = fork.repo_name
255 Session().commit()
255 Session().commit()
256
256
257 forks = Repository.query()\
257 forks = Repository.query()\
258 .filter(Repository.repo_type == backend.alias)\
258 .filter(Repository.repo_type == backend.alias)\
259 .filter(Repository.fork_id == source.repo_id).all()
259 .filter(Repository.fork_id == source.repo_id).all()
260 assert 1 == len(forks)
260 assert 1 == len(forks)
261
261
262 # set read permissions for this
262 # set read permissions for this
263 RepoModel().grant_user_permission(
263 RepoModel().grant_user_permission(
264 repo=forks[0], user=user_id, perm='repository.read')
264 repo=forks[0], user=user_id, perm='repository.read')
265 Session().commit()
265 Session().commit()
266
266
267 response = self.app.get(
267 response = self.app.get(
268 route_path('repo_forks_data', repo_name=repo_name),
268 route_path('repo_forks_data', repo_name=repo_name),
269 extra_environ=xhr_header)
269 extra_environ=xhr_header)
270
270
271 assert response.json['data'][0]['fork_name'] == \
271 assert response.json['data'][0]['fork_name'] == \
272 """<a href="/%s">%s</a>""" % (fork_name, fork_name)
272 """<a href="/%s">%s</a>""" % (fork_name, fork_name)
273
273
274 def test_fork_none_permission(self, backend, xhr_header, user_util):
274 def test_fork_none_permission(self, backend, xhr_header, user_util):
275 user = user_util.create_user(password='qweqwe')
275 user = user_util.create_user(password='qweqwe')
276 user_id = user.user_id
276 user_id = user.user_id
277 self.log_user(user.username, 'qweqwe')
277 self.log_user(user.username, 'qweqwe')
278
278
279 # create a fake fork
279 # create a fake fork
280 fork = user_util.create_repo(repo_type=backend.alias)
280 fork = user_util.create_repo(repo_type=backend.alias)
281 source = user_util.create_repo(repo_type=backend.alias)
281 source = user_util.create_repo(repo_type=backend.alias)
282 repo_name = source.repo_name
282 repo_name = source.repo_name
283
283
284 fork.fork_id = source.repo_id
284 fork.fork_id = source.repo_id
285
285
286 Session().commit()
286 Session().commit()
287
287
288 forks = Repository.query()\
288 forks = Repository.query()\
289 .filter(Repository.repo_type == backend.alias)\
289 .filter(Repository.repo_type == backend.alias)\
290 .filter(Repository.fork_id == source.repo_id).all()
290 .filter(Repository.fork_id == source.repo_id).all()
291 assert 1 == len(forks)
291 assert 1 == len(forks)
292
292
293 # set none
293 # set none
294 RepoModel().grant_user_permission(
294 RepoModel().grant_user_permission(
295 repo=forks[0], user=user_id, perm='repository.none')
295 repo=forks[0], user=user_id, perm='repository.none')
296 Session().commit()
296 Session().commit()
297
297
298 # fork shouldn't be there
298 # fork shouldn't be there
299 response = self.app.get(
299 response = self.app.get(
300 route_path('repo_forks_data', repo_name=repo_name),
300 route_path('repo_forks_data', repo_name=repo_name),
301 extra_environ=xhr_header)
301 extra_environ=xhr_header)
302
302
303 assert response.json == {u'data': [], u'draw': None,
303 assert response.json == {u'data': [], u'draw': None,
304 u'recordsFiltered': 0, u'recordsTotal': 0}
304 u'recordsFiltered': 0, u'recordsTotal': 0}
305
305
306 @pytest.mark.parametrize('url_type', [
307 'repo_fork_new',
308 'repo_fork_create'
309 ])
310 def test_fork_is_forbidden_on_archived_repo(self, backend, xhr_header, user_util, url_type):
311 user = user_util.create_user(password='qweqwe')
312 self.log_user(user.username, 'qweqwe')
313
314 # create a temporary repo
315 source = user_util.create_repo(repo_type=backend.alias)
316 repo_name = source.repo_name
317 repo = Repository.get_by_repo_name(repo_name)
318 repo.archived = True
319 Session().commit()
320
321 response = self.app.get(
322 route_path(url_type, repo_name=repo_name), status=302)
323
324 msg = 'Action not supported for archived repository.'
325 assert_session_flash(response, msg)
326
306
327
307 class TestSVNFork(TestController):
328 class TestSVNFork(TestController):
308 @pytest.mark.parametrize('route_name', [
329 @pytest.mark.parametrize('route_name', [
309 'repo_fork_create', 'repo_fork_new'
330 'repo_fork_create', 'repo_fork_new'
310 ])
331 ])
311 def test_fork_redirects(self, autologin_user, backend_svn, route_name):
332 def test_fork_redirects(self, autologin_user, backend_svn, route_name):
312
333
313 self.app.get(route_path(
334 self.app.get(route_path(
314 route_name, repo_name=backend_svn.repo_name),
335 route_name, repo_name=backend_svn.repo_name),
315 status=404)
336 status=404)
@@ -1,1206 +1,1228 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 import rhodecode
23 import rhodecode
24 from rhodecode.lib.vcs.backends.base import MergeResponse, MergeFailureReason
24 from rhodecode.lib.vcs.backends.base import MergeResponse, MergeFailureReason
25 from rhodecode.lib.vcs.nodes import FileNode
25 from rhodecode.lib.vcs.nodes import FileNode
26 from rhodecode.lib import helpers as h
26 from rhodecode.lib import helpers as h
27 from rhodecode.model.changeset_status import ChangesetStatusModel
27 from rhodecode.model.changeset_status import ChangesetStatusModel
28 from rhodecode.model.db import (
28 from rhodecode.model.db import (
29 PullRequest, ChangesetStatus, UserLog, Notification, ChangesetComment)
29 PullRequest, ChangesetStatus, UserLog, Notification, ChangesetComment, Repository)
30 from rhodecode.model.meta import Session
30 from rhodecode.model.meta import Session
31 from rhodecode.model.pull_request import PullRequestModel
31 from rhodecode.model.pull_request import PullRequestModel
32 from rhodecode.model.user import UserModel
32 from rhodecode.model.user import UserModel
33 from rhodecode.tests import (
33 from rhodecode.tests import (
34 assert_session_flash, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN)
34 assert_session_flash, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN)
35 from rhodecode.tests.utils import AssertResponse
35 from rhodecode.tests.utils import AssertResponse
36
36
37
37
38 def route_path(name, params=None, **kwargs):
38 def route_path(name, params=None, **kwargs):
39 import urllib
39 import urllib
40
40
41 base_url = {
41 base_url = {
42 'repo_changelog': '/{repo_name}/changelog',
42 'repo_changelog': '/{repo_name}/changelog',
43 'repo_changelog_file': '/{repo_name}/changelog/{commit_id}/{f_path}',
43 'repo_changelog_file': '/{repo_name}/changelog/{commit_id}/{f_path}',
44 'pullrequest_show': '/{repo_name}/pull-request/{pull_request_id}',
44 'pullrequest_show': '/{repo_name}/pull-request/{pull_request_id}',
45 'pullrequest_show_all': '/{repo_name}/pull-request',
45 'pullrequest_show_all': '/{repo_name}/pull-request',
46 'pullrequest_show_all_data': '/{repo_name}/pull-request-data',
46 'pullrequest_show_all_data': '/{repo_name}/pull-request-data',
47 'pullrequest_repo_refs': '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
47 'pullrequest_repo_refs': '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
48 'pullrequest_repo_destinations': '/{repo_name}/pull-request/repo-destinations',
48 'pullrequest_repo_destinations': '/{repo_name}/pull-request/repo-destinations',
49 'pullrequest_new': '/{repo_name}/pull-request/new',
49 'pullrequest_new': '/{repo_name}/pull-request/new',
50 'pullrequest_create': '/{repo_name}/pull-request/create',
50 'pullrequest_create': '/{repo_name}/pull-request/create',
51 'pullrequest_update': '/{repo_name}/pull-request/{pull_request_id}/update',
51 'pullrequest_update': '/{repo_name}/pull-request/{pull_request_id}/update',
52 'pullrequest_merge': '/{repo_name}/pull-request/{pull_request_id}/merge',
52 'pullrequest_merge': '/{repo_name}/pull-request/{pull_request_id}/merge',
53 'pullrequest_delete': '/{repo_name}/pull-request/{pull_request_id}/delete',
53 'pullrequest_delete': '/{repo_name}/pull-request/{pull_request_id}/delete',
54 'pullrequest_comment_create': '/{repo_name}/pull-request/{pull_request_id}/comment',
54 'pullrequest_comment_create': '/{repo_name}/pull-request/{pull_request_id}/comment',
55 'pullrequest_comment_delete': '/{repo_name}/pull-request/{pull_request_id}/comment/{comment_id}/delete',
55 'pullrequest_comment_delete': '/{repo_name}/pull-request/{pull_request_id}/comment/{comment_id}/delete',
56 }[name].format(**kwargs)
56 }[name].format(**kwargs)
57
57
58 if params:
58 if params:
59 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
59 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
60 return base_url
60 return base_url
61
61
62
62
63 @pytest.mark.usefixtures('app', 'autologin_user')
63 @pytest.mark.usefixtures('app', 'autologin_user')
64 @pytest.mark.backends("git", "hg")
64 @pytest.mark.backends("git", "hg")
65 class TestPullrequestsView(object):
65 class TestPullrequestsView(object):
66
66
67 def test_index(self, backend):
67 def test_index(self, backend):
68 self.app.get(route_path(
68 self.app.get(route_path(
69 'pullrequest_new',
69 'pullrequest_new',
70 repo_name=backend.repo_name))
70 repo_name=backend.repo_name))
71
71
72 def test_option_menu_create_pull_request_exists(self, backend):
72 def test_option_menu_create_pull_request_exists(self, backend):
73 repo_name = backend.repo_name
73 repo_name = backend.repo_name
74 response = self.app.get(h.route_path('repo_summary', repo_name=repo_name))
74 response = self.app.get(h.route_path('repo_summary', repo_name=repo_name))
75
75
76 create_pr_link = '<a href="%s">Create Pull Request</a>' % route_path(
76 create_pr_link = '<a href="%s">Create Pull Request</a>' % route_path(
77 'pullrequest_new', repo_name=repo_name)
77 'pullrequest_new', repo_name=repo_name)
78 response.mustcontain(create_pr_link)
78 response.mustcontain(create_pr_link)
79
79
80 def test_create_pr_form_with_raw_commit_id(self, backend):
80 def test_create_pr_form_with_raw_commit_id(self, backend):
81 repo = backend.repo
81 repo = backend.repo
82
82
83 self.app.get(
83 self.app.get(
84 route_path('pullrequest_new',
84 route_path('pullrequest_new',
85 repo_name=repo.repo_name,
85 repo_name=repo.repo_name,
86 commit=repo.get_commit().raw_id),
86 commit=repo.get_commit().raw_id),
87 status=200)
87 status=200)
88
88
89 @pytest.mark.parametrize('pr_merge_enabled', [True, False])
89 @pytest.mark.parametrize('pr_merge_enabled', [True, False])
90 def test_show(self, pr_util, pr_merge_enabled):
90 def test_show(self, pr_util, pr_merge_enabled):
91 pull_request = pr_util.create_pull_request(
91 pull_request = pr_util.create_pull_request(
92 mergeable=pr_merge_enabled, enable_notifications=False)
92 mergeable=pr_merge_enabled, enable_notifications=False)
93
93
94 response = self.app.get(route_path(
94 response = self.app.get(route_path(
95 'pullrequest_show',
95 'pullrequest_show',
96 repo_name=pull_request.target_repo.scm_instance().name,
96 repo_name=pull_request.target_repo.scm_instance().name,
97 pull_request_id=pull_request.pull_request_id))
97 pull_request_id=pull_request.pull_request_id))
98
98
99 for commit_id in pull_request.revisions:
99 for commit_id in pull_request.revisions:
100 response.mustcontain(commit_id)
100 response.mustcontain(commit_id)
101
101
102 assert pull_request.target_ref_parts.type in response
102 assert pull_request.target_ref_parts.type in response
103 assert pull_request.target_ref_parts.name in response
103 assert pull_request.target_ref_parts.name in response
104 target_clone_url = pull_request.target_repo.clone_url()
104 target_clone_url = pull_request.target_repo.clone_url()
105 assert target_clone_url in response
105 assert target_clone_url in response
106
106
107 assert 'class="pull-request-merge"' in response
107 assert 'class="pull-request-merge"' in response
108 assert (
108 assert (
109 'Server-side pull request merging is disabled.'
109 'Server-side pull request merging is disabled.'
110 in response) != pr_merge_enabled
110 in response) != pr_merge_enabled
111
111
112 def test_close_status_visibility(self, pr_util, user_util, csrf_token):
112 def test_close_status_visibility(self, pr_util, user_util, csrf_token):
113 # Logout
113 # Logout
114 response = self.app.post(
114 response = self.app.post(
115 h.route_path('logout'),
115 h.route_path('logout'),
116 params={'csrf_token': csrf_token})
116 params={'csrf_token': csrf_token})
117 # Login as regular user
117 # Login as regular user
118 response = self.app.post(h.route_path('login'),
118 response = self.app.post(h.route_path('login'),
119 {'username': TEST_USER_REGULAR_LOGIN,
119 {'username': TEST_USER_REGULAR_LOGIN,
120 'password': 'test12'})
120 'password': 'test12'})
121
121
122 pull_request = pr_util.create_pull_request(
122 pull_request = pr_util.create_pull_request(
123 author=TEST_USER_REGULAR_LOGIN)
123 author=TEST_USER_REGULAR_LOGIN)
124
124
125 response = self.app.get(route_path(
125 response = self.app.get(route_path(
126 'pullrequest_show',
126 'pullrequest_show',
127 repo_name=pull_request.target_repo.scm_instance().name,
127 repo_name=pull_request.target_repo.scm_instance().name,
128 pull_request_id=pull_request.pull_request_id))
128 pull_request_id=pull_request.pull_request_id))
129
129
130 response.mustcontain('Server-side pull request merging is disabled.')
130 response.mustcontain('Server-side pull request merging is disabled.')
131
131
132 assert_response = response.assert_response()
132 assert_response = response.assert_response()
133 # for regular user without a merge permissions, we don't see it
133 # for regular user without a merge permissions, we don't see it
134 assert_response.no_element_exists('#close-pull-request-action')
134 assert_response.no_element_exists('#close-pull-request-action')
135
135
136 user_util.grant_user_permission_to_repo(
136 user_util.grant_user_permission_to_repo(
137 pull_request.target_repo,
137 pull_request.target_repo,
138 UserModel().get_by_username(TEST_USER_REGULAR_LOGIN),
138 UserModel().get_by_username(TEST_USER_REGULAR_LOGIN),
139 'repository.write')
139 'repository.write')
140 response = self.app.get(route_path(
140 response = self.app.get(route_path(
141 'pullrequest_show',
141 'pullrequest_show',
142 repo_name=pull_request.target_repo.scm_instance().name,
142 repo_name=pull_request.target_repo.scm_instance().name,
143 pull_request_id=pull_request.pull_request_id))
143 pull_request_id=pull_request.pull_request_id))
144
144
145 response.mustcontain('Server-side pull request merging is disabled.')
145 response.mustcontain('Server-side pull request merging is disabled.')
146
146
147 assert_response = response.assert_response()
147 assert_response = response.assert_response()
148 # now regular user has a merge permissions, we have CLOSE button
148 # now regular user has a merge permissions, we have CLOSE button
149 assert_response.one_element_exists('#close-pull-request-action')
149 assert_response.one_element_exists('#close-pull-request-action')
150
150
151 def test_show_invalid_commit_id(self, pr_util):
151 def test_show_invalid_commit_id(self, pr_util):
152 # Simulating invalid revisions which will cause a lookup error
152 # Simulating invalid revisions which will cause a lookup error
153 pull_request = pr_util.create_pull_request()
153 pull_request = pr_util.create_pull_request()
154 pull_request.revisions = ['invalid']
154 pull_request.revisions = ['invalid']
155 Session().add(pull_request)
155 Session().add(pull_request)
156 Session().commit()
156 Session().commit()
157
157
158 response = self.app.get(route_path(
158 response = self.app.get(route_path(
159 'pullrequest_show',
159 'pullrequest_show',
160 repo_name=pull_request.target_repo.scm_instance().name,
160 repo_name=pull_request.target_repo.scm_instance().name,
161 pull_request_id=pull_request.pull_request_id))
161 pull_request_id=pull_request.pull_request_id))
162
162
163 for commit_id in pull_request.revisions:
163 for commit_id in pull_request.revisions:
164 response.mustcontain(commit_id)
164 response.mustcontain(commit_id)
165
165
166 def test_show_invalid_source_reference(self, pr_util):
166 def test_show_invalid_source_reference(self, pr_util):
167 pull_request = pr_util.create_pull_request()
167 pull_request = pr_util.create_pull_request()
168 pull_request.source_ref = 'branch:b:invalid'
168 pull_request.source_ref = 'branch:b:invalid'
169 Session().add(pull_request)
169 Session().add(pull_request)
170 Session().commit()
170 Session().commit()
171
171
172 self.app.get(route_path(
172 self.app.get(route_path(
173 'pullrequest_show',
173 'pullrequest_show',
174 repo_name=pull_request.target_repo.scm_instance().name,
174 repo_name=pull_request.target_repo.scm_instance().name,
175 pull_request_id=pull_request.pull_request_id))
175 pull_request_id=pull_request.pull_request_id))
176
176
177 def test_edit_title_description(self, pr_util, csrf_token):
177 def test_edit_title_description(self, pr_util, csrf_token):
178 pull_request = pr_util.create_pull_request()
178 pull_request = pr_util.create_pull_request()
179 pull_request_id = pull_request.pull_request_id
179 pull_request_id = pull_request.pull_request_id
180
180
181 response = self.app.post(
181 response = self.app.post(
182 route_path('pullrequest_update',
182 route_path('pullrequest_update',
183 repo_name=pull_request.target_repo.repo_name,
183 repo_name=pull_request.target_repo.repo_name,
184 pull_request_id=pull_request_id),
184 pull_request_id=pull_request_id),
185 params={
185 params={
186 'edit_pull_request': 'true',
186 'edit_pull_request': 'true',
187 'title': 'New title',
187 'title': 'New title',
188 'description': 'New description',
188 'description': 'New description',
189 'csrf_token': csrf_token})
189 'csrf_token': csrf_token})
190
190
191 assert_session_flash(
191 assert_session_flash(
192 response, u'Pull request title & description updated.',
192 response, u'Pull request title & description updated.',
193 category='success')
193 category='success')
194
194
195 pull_request = PullRequest.get(pull_request_id)
195 pull_request = PullRequest.get(pull_request_id)
196 assert pull_request.title == 'New title'
196 assert pull_request.title == 'New title'
197 assert pull_request.description == 'New description'
197 assert pull_request.description == 'New description'
198
198
199 def test_edit_title_description_closed(self, pr_util, csrf_token):
199 def test_edit_title_description_closed(self, pr_util, csrf_token):
200 pull_request = pr_util.create_pull_request()
200 pull_request = pr_util.create_pull_request()
201 pull_request_id = pull_request.pull_request_id
201 pull_request_id = pull_request.pull_request_id
202 repo_name = pull_request.target_repo.repo_name
202 repo_name = pull_request.target_repo.repo_name
203 pr_util.close()
203 pr_util.close()
204
204
205 response = self.app.post(
205 response = self.app.post(
206 route_path('pullrequest_update',
206 route_path('pullrequest_update',
207 repo_name=repo_name, pull_request_id=pull_request_id),
207 repo_name=repo_name, pull_request_id=pull_request_id),
208 params={
208 params={
209 'edit_pull_request': 'true',
209 'edit_pull_request': 'true',
210 'title': 'New title',
210 'title': 'New title',
211 'description': 'New description',
211 'description': 'New description',
212 'csrf_token': csrf_token}, status=200)
212 'csrf_token': csrf_token}, status=200)
213 assert_session_flash(
213 assert_session_flash(
214 response, u'Cannot update closed pull requests.',
214 response, u'Cannot update closed pull requests.',
215 category='error')
215 category='error')
216
216
217 def test_update_invalid_source_reference(self, pr_util, csrf_token):
217 def test_update_invalid_source_reference(self, pr_util, csrf_token):
218 from rhodecode.lib.vcs.backends.base import UpdateFailureReason
218 from rhodecode.lib.vcs.backends.base import UpdateFailureReason
219
219
220 pull_request = pr_util.create_pull_request()
220 pull_request = pr_util.create_pull_request()
221 pull_request.source_ref = 'branch:invalid-branch:invalid-commit-id'
221 pull_request.source_ref = 'branch:invalid-branch:invalid-commit-id'
222 Session().add(pull_request)
222 Session().add(pull_request)
223 Session().commit()
223 Session().commit()
224
224
225 pull_request_id = pull_request.pull_request_id
225 pull_request_id = pull_request.pull_request_id
226
226
227 response = self.app.post(
227 response = self.app.post(
228 route_path('pullrequest_update',
228 route_path('pullrequest_update',
229 repo_name=pull_request.target_repo.repo_name,
229 repo_name=pull_request.target_repo.repo_name,
230 pull_request_id=pull_request_id),
230 pull_request_id=pull_request_id),
231 params={'update_commits': 'true',
231 params={'update_commits': 'true',
232 'csrf_token': csrf_token})
232 'csrf_token': csrf_token})
233
233
234 expected_msg = str(PullRequestModel.UPDATE_STATUS_MESSAGES[
234 expected_msg = str(PullRequestModel.UPDATE_STATUS_MESSAGES[
235 UpdateFailureReason.MISSING_SOURCE_REF])
235 UpdateFailureReason.MISSING_SOURCE_REF])
236 assert_session_flash(response, expected_msg, category='error')
236 assert_session_flash(response, expected_msg, category='error')
237
237
238 def test_missing_target_reference(self, pr_util, csrf_token):
238 def test_missing_target_reference(self, pr_util, csrf_token):
239 from rhodecode.lib.vcs.backends.base import MergeFailureReason
239 from rhodecode.lib.vcs.backends.base import MergeFailureReason
240 pull_request = pr_util.create_pull_request(
240 pull_request = pr_util.create_pull_request(
241 approved=True, mergeable=True)
241 approved=True, mergeable=True)
242 pull_request.target_ref = 'branch:invalid-branch:invalid-commit-id'
242 pull_request.target_ref = 'branch:invalid-branch:invalid-commit-id'
243 Session().add(pull_request)
243 Session().add(pull_request)
244 Session().commit()
244 Session().commit()
245
245
246 pull_request_id = pull_request.pull_request_id
246 pull_request_id = pull_request.pull_request_id
247 pull_request_url = route_path(
247 pull_request_url = route_path(
248 'pullrequest_show',
248 'pullrequest_show',
249 repo_name=pull_request.target_repo.repo_name,
249 repo_name=pull_request.target_repo.repo_name,
250 pull_request_id=pull_request_id)
250 pull_request_id=pull_request_id)
251
251
252 response = self.app.get(pull_request_url)
252 response = self.app.get(pull_request_url)
253
253
254 assertr = AssertResponse(response)
254 assertr = AssertResponse(response)
255 expected_msg = PullRequestModel.MERGE_STATUS_MESSAGES[
255 expected_msg = PullRequestModel.MERGE_STATUS_MESSAGES[
256 MergeFailureReason.MISSING_TARGET_REF]
256 MergeFailureReason.MISSING_TARGET_REF]
257 assertr.element_contains(
257 assertr.element_contains(
258 'span[data-role="merge-message"]', str(expected_msg))
258 'span[data-role="merge-message"]', str(expected_msg))
259
259
260 def test_comment_and_close_pull_request_custom_message_approved(
260 def test_comment_and_close_pull_request_custom_message_approved(
261 self, pr_util, csrf_token, xhr_header):
261 self, pr_util, csrf_token, xhr_header):
262
262
263 pull_request = pr_util.create_pull_request(approved=True)
263 pull_request = pr_util.create_pull_request(approved=True)
264 pull_request_id = pull_request.pull_request_id
264 pull_request_id = pull_request.pull_request_id
265 author = pull_request.user_id
265 author = pull_request.user_id
266 repo = pull_request.target_repo.repo_id
266 repo = pull_request.target_repo.repo_id
267
267
268 self.app.post(
268 self.app.post(
269 route_path('pullrequest_comment_create',
269 route_path('pullrequest_comment_create',
270 repo_name=pull_request.target_repo.scm_instance().name,
270 repo_name=pull_request.target_repo.scm_instance().name,
271 pull_request_id=pull_request_id),
271 pull_request_id=pull_request_id),
272 params={
272 params={
273 'close_pull_request': '1',
273 'close_pull_request': '1',
274 'text': 'Closing a PR',
274 'text': 'Closing a PR',
275 'csrf_token': csrf_token},
275 'csrf_token': csrf_token},
276 extra_environ=xhr_header,)
276 extra_environ=xhr_header,)
277
277
278 journal = UserLog.query()\
278 journal = UserLog.query()\
279 .filter(UserLog.user_id == author)\
279 .filter(UserLog.user_id == author)\
280 .filter(UserLog.repository_id == repo) \
280 .filter(UserLog.repository_id == repo) \
281 .order_by('user_log_id') \
281 .order_by('user_log_id') \
282 .all()
282 .all()
283 assert journal[-1].action == 'repo.pull_request.close'
283 assert journal[-1].action == 'repo.pull_request.close'
284
284
285 pull_request = PullRequest.get(pull_request_id)
285 pull_request = PullRequest.get(pull_request_id)
286 assert pull_request.is_closed()
286 assert pull_request.is_closed()
287
287
288 status = ChangesetStatusModel().get_status(
288 status = ChangesetStatusModel().get_status(
289 pull_request.source_repo, pull_request=pull_request)
289 pull_request.source_repo, pull_request=pull_request)
290 assert status == ChangesetStatus.STATUS_APPROVED
290 assert status == ChangesetStatus.STATUS_APPROVED
291 comments = ChangesetComment().query() \
291 comments = ChangesetComment().query() \
292 .filter(ChangesetComment.pull_request == pull_request) \
292 .filter(ChangesetComment.pull_request == pull_request) \
293 .order_by(ChangesetComment.comment_id.asc())\
293 .order_by(ChangesetComment.comment_id.asc())\
294 .all()
294 .all()
295 assert comments[-1].text == 'Closing a PR'
295 assert comments[-1].text == 'Closing a PR'
296
296
297 def test_comment_force_close_pull_request_rejected(
297 def test_comment_force_close_pull_request_rejected(
298 self, pr_util, csrf_token, xhr_header):
298 self, pr_util, csrf_token, xhr_header):
299 pull_request = pr_util.create_pull_request()
299 pull_request = pr_util.create_pull_request()
300 pull_request_id = pull_request.pull_request_id
300 pull_request_id = pull_request.pull_request_id
301 PullRequestModel().update_reviewers(
301 PullRequestModel().update_reviewers(
302 pull_request_id, [(1, ['reason'], False, []), (2, ['reason2'], False, [])],
302 pull_request_id, [(1, ['reason'], False, []), (2, ['reason2'], False, [])],
303 pull_request.author)
303 pull_request.author)
304 author = pull_request.user_id
304 author = pull_request.user_id
305 repo = pull_request.target_repo.repo_id
305 repo = pull_request.target_repo.repo_id
306
306
307 self.app.post(
307 self.app.post(
308 route_path('pullrequest_comment_create',
308 route_path('pullrequest_comment_create',
309 repo_name=pull_request.target_repo.scm_instance().name,
309 repo_name=pull_request.target_repo.scm_instance().name,
310 pull_request_id=pull_request_id),
310 pull_request_id=pull_request_id),
311 params={
311 params={
312 'close_pull_request': '1',
312 'close_pull_request': '1',
313 'csrf_token': csrf_token},
313 'csrf_token': csrf_token},
314 extra_environ=xhr_header)
314 extra_environ=xhr_header)
315
315
316 pull_request = PullRequest.get(pull_request_id)
316 pull_request = PullRequest.get(pull_request_id)
317
317
318 journal = UserLog.query()\
318 journal = UserLog.query()\
319 .filter(UserLog.user_id == author, UserLog.repository_id == repo) \
319 .filter(UserLog.user_id == author, UserLog.repository_id == repo) \
320 .order_by('user_log_id') \
320 .order_by('user_log_id') \
321 .all()
321 .all()
322 assert journal[-1].action == 'repo.pull_request.close'
322 assert journal[-1].action == 'repo.pull_request.close'
323
323
324 # check only the latest status, not the review status
324 # check only the latest status, not the review status
325 status = ChangesetStatusModel().get_status(
325 status = ChangesetStatusModel().get_status(
326 pull_request.source_repo, pull_request=pull_request)
326 pull_request.source_repo, pull_request=pull_request)
327 assert status == ChangesetStatus.STATUS_REJECTED
327 assert status == ChangesetStatus.STATUS_REJECTED
328
328
329 def test_comment_and_close_pull_request(
329 def test_comment_and_close_pull_request(
330 self, pr_util, csrf_token, xhr_header):
330 self, pr_util, csrf_token, xhr_header):
331 pull_request = pr_util.create_pull_request()
331 pull_request = pr_util.create_pull_request()
332 pull_request_id = pull_request.pull_request_id
332 pull_request_id = pull_request.pull_request_id
333
333
334 response = self.app.post(
334 response = self.app.post(
335 route_path('pullrequest_comment_create',
335 route_path('pullrequest_comment_create',
336 repo_name=pull_request.target_repo.scm_instance().name,
336 repo_name=pull_request.target_repo.scm_instance().name,
337 pull_request_id=pull_request.pull_request_id),
337 pull_request_id=pull_request.pull_request_id),
338 params={
338 params={
339 'close_pull_request': 'true',
339 'close_pull_request': 'true',
340 'csrf_token': csrf_token},
340 'csrf_token': csrf_token},
341 extra_environ=xhr_header)
341 extra_environ=xhr_header)
342
342
343 assert response.json
343 assert response.json
344
344
345 pull_request = PullRequest.get(pull_request_id)
345 pull_request = PullRequest.get(pull_request_id)
346 assert pull_request.is_closed()
346 assert pull_request.is_closed()
347
347
348 # check only the latest status, not the review status
348 # check only the latest status, not the review status
349 status = ChangesetStatusModel().get_status(
349 status = ChangesetStatusModel().get_status(
350 pull_request.source_repo, pull_request=pull_request)
350 pull_request.source_repo, pull_request=pull_request)
351 assert status == ChangesetStatus.STATUS_REJECTED
351 assert status == ChangesetStatus.STATUS_REJECTED
352
352
353 def test_create_pull_request(self, backend, csrf_token):
353 def test_create_pull_request(self, backend, csrf_token):
354 commits = [
354 commits = [
355 {'message': 'ancestor'},
355 {'message': 'ancestor'},
356 {'message': 'change'},
356 {'message': 'change'},
357 {'message': 'change2'},
357 {'message': 'change2'},
358 ]
358 ]
359 commit_ids = backend.create_master_repo(commits)
359 commit_ids = backend.create_master_repo(commits)
360 target = backend.create_repo(heads=['ancestor'])
360 target = backend.create_repo(heads=['ancestor'])
361 source = backend.create_repo(heads=['change2'])
361 source = backend.create_repo(heads=['change2'])
362
362
363 response = self.app.post(
363 response = self.app.post(
364 route_path('pullrequest_create', repo_name=source.repo_name),
364 route_path('pullrequest_create', repo_name=source.repo_name),
365 [
365 [
366 ('source_repo', source.repo_name),
366 ('source_repo', source.repo_name),
367 ('source_ref', 'branch:default:' + commit_ids['change2']),
367 ('source_ref', 'branch:default:' + commit_ids['change2']),
368 ('target_repo', target.repo_name),
368 ('target_repo', target.repo_name),
369 ('target_ref', 'branch:default:' + commit_ids['ancestor']),
369 ('target_ref', 'branch:default:' + commit_ids['ancestor']),
370 ('common_ancestor', commit_ids['ancestor']),
370 ('common_ancestor', commit_ids['ancestor']),
371 ('pullrequest_title', 'Title'),
371 ('pullrequest_title', 'Title'),
372 ('pullrequest_desc', 'Description'),
372 ('pullrequest_desc', 'Description'),
373 ('description_renderer', 'markdown'),
373 ('description_renderer', 'markdown'),
374 ('__start__', 'review_members:sequence'),
374 ('__start__', 'review_members:sequence'),
375 ('__start__', 'reviewer:mapping'),
375 ('__start__', 'reviewer:mapping'),
376 ('user_id', '1'),
376 ('user_id', '1'),
377 ('__start__', 'reasons:sequence'),
377 ('__start__', 'reasons:sequence'),
378 ('reason', 'Some reason'),
378 ('reason', 'Some reason'),
379 ('__end__', 'reasons:sequence'),
379 ('__end__', 'reasons:sequence'),
380 ('__start__', 'rules:sequence'),
380 ('__start__', 'rules:sequence'),
381 ('__end__', 'rules:sequence'),
381 ('__end__', 'rules:sequence'),
382 ('mandatory', 'False'),
382 ('mandatory', 'False'),
383 ('__end__', 'reviewer:mapping'),
383 ('__end__', 'reviewer:mapping'),
384 ('__end__', 'review_members:sequence'),
384 ('__end__', 'review_members:sequence'),
385 ('__start__', 'revisions:sequence'),
385 ('__start__', 'revisions:sequence'),
386 ('revisions', commit_ids['change']),
386 ('revisions', commit_ids['change']),
387 ('revisions', commit_ids['change2']),
387 ('revisions', commit_ids['change2']),
388 ('__end__', 'revisions:sequence'),
388 ('__end__', 'revisions:sequence'),
389 ('user', ''),
389 ('user', ''),
390 ('csrf_token', csrf_token),
390 ('csrf_token', csrf_token),
391 ],
391 ],
392 status=302)
392 status=302)
393
393
394 location = response.headers['Location']
394 location = response.headers['Location']
395 pull_request_id = location.rsplit('/', 1)[1]
395 pull_request_id = location.rsplit('/', 1)[1]
396 assert pull_request_id != 'new'
396 assert pull_request_id != 'new'
397 pull_request = PullRequest.get(int(pull_request_id))
397 pull_request = PullRequest.get(int(pull_request_id))
398
398
399 # check that we have now both revisions
399 # check that we have now both revisions
400 assert pull_request.revisions == [commit_ids['change2'], commit_ids['change']]
400 assert pull_request.revisions == [commit_ids['change2'], commit_ids['change']]
401 assert pull_request.source_ref == 'branch:default:' + commit_ids['change2']
401 assert pull_request.source_ref == 'branch:default:' + commit_ids['change2']
402 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
402 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
403 assert pull_request.target_ref == expected_target_ref
403 assert pull_request.target_ref == expected_target_ref
404
404
405 def test_reviewer_notifications(self, backend, csrf_token):
405 def test_reviewer_notifications(self, backend, csrf_token):
406 # We have to use the app.post for this test so it will create the
406 # We have to use the app.post for this test so it will create the
407 # notifications properly with the new PR
407 # notifications properly with the new PR
408 commits = [
408 commits = [
409 {'message': 'ancestor',
409 {'message': 'ancestor',
410 'added': [FileNode('file_A', content='content_of_ancestor')]},
410 'added': [FileNode('file_A', content='content_of_ancestor')]},
411 {'message': 'change',
411 {'message': 'change',
412 'added': [FileNode('file_a', content='content_of_change')]},
412 'added': [FileNode('file_a', content='content_of_change')]},
413 {'message': 'change-child'},
413 {'message': 'change-child'},
414 {'message': 'ancestor-child', 'parents': ['ancestor'],
414 {'message': 'ancestor-child', 'parents': ['ancestor'],
415 'added': [
415 'added': [
416 FileNode('file_B', content='content_of_ancestor_child')]},
416 FileNode('file_B', content='content_of_ancestor_child')]},
417 {'message': 'ancestor-child-2'},
417 {'message': 'ancestor-child-2'},
418 ]
418 ]
419 commit_ids = backend.create_master_repo(commits)
419 commit_ids = backend.create_master_repo(commits)
420 target = backend.create_repo(heads=['ancestor-child'])
420 target = backend.create_repo(heads=['ancestor-child'])
421 source = backend.create_repo(heads=['change'])
421 source = backend.create_repo(heads=['change'])
422
422
423 response = self.app.post(
423 response = self.app.post(
424 route_path('pullrequest_create', repo_name=source.repo_name),
424 route_path('pullrequest_create', repo_name=source.repo_name),
425 [
425 [
426 ('source_repo', source.repo_name),
426 ('source_repo', source.repo_name),
427 ('source_ref', 'branch:default:' + commit_ids['change']),
427 ('source_ref', 'branch:default:' + commit_ids['change']),
428 ('target_repo', target.repo_name),
428 ('target_repo', target.repo_name),
429 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
429 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
430 ('common_ancestor', commit_ids['ancestor']),
430 ('common_ancestor', commit_ids['ancestor']),
431 ('pullrequest_title', 'Title'),
431 ('pullrequest_title', 'Title'),
432 ('pullrequest_desc', 'Description'),
432 ('pullrequest_desc', 'Description'),
433 ('description_renderer', 'markdown'),
433 ('description_renderer', 'markdown'),
434 ('__start__', 'review_members:sequence'),
434 ('__start__', 'review_members:sequence'),
435 ('__start__', 'reviewer:mapping'),
435 ('__start__', 'reviewer:mapping'),
436 ('user_id', '2'),
436 ('user_id', '2'),
437 ('__start__', 'reasons:sequence'),
437 ('__start__', 'reasons:sequence'),
438 ('reason', 'Some reason'),
438 ('reason', 'Some reason'),
439 ('__end__', 'reasons:sequence'),
439 ('__end__', 'reasons:sequence'),
440 ('__start__', 'rules:sequence'),
440 ('__start__', 'rules:sequence'),
441 ('__end__', 'rules:sequence'),
441 ('__end__', 'rules:sequence'),
442 ('mandatory', 'False'),
442 ('mandatory', 'False'),
443 ('__end__', 'reviewer:mapping'),
443 ('__end__', 'reviewer:mapping'),
444 ('__end__', 'review_members:sequence'),
444 ('__end__', 'review_members:sequence'),
445 ('__start__', 'revisions:sequence'),
445 ('__start__', 'revisions:sequence'),
446 ('revisions', commit_ids['change']),
446 ('revisions', commit_ids['change']),
447 ('__end__', 'revisions:sequence'),
447 ('__end__', 'revisions:sequence'),
448 ('user', ''),
448 ('user', ''),
449 ('csrf_token', csrf_token),
449 ('csrf_token', csrf_token),
450 ],
450 ],
451 status=302)
451 status=302)
452
452
453 location = response.headers['Location']
453 location = response.headers['Location']
454
454
455 pull_request_id = location.rsplit('/', 1)[1]
455 pull_request_id = location.rsplit('/', 1)[1]
456 assert pull_request_id != 'new'
456 assert pull_request_id != 'new'
457 pull_request = PullRequest.get(int(pull_request_id))
457 pull_request = PullRequest.get(int(pull_request_id))
458
458
459 # Check that a notification was made
459 # Check that a notification was made
460 notifications = Notification.query()\
460 notifications = Notification.query()\
461 .filter(Notification.created_by == pull_request.author.user_id,
461 .filter(Notification.created_by == pull_request.author.user_id,
462 Notification.type_ == Notification.TYPE_PULL_REQUEST,
462 Notification.type_ == Notification.TYPE_PULL_REQUEST,
463 Notification.subject.contains(
463 Notification.subject.contains(
464 "wants you to review pull request #%s" % pull_request_id))
464 "wants you to review pull request #%s" % pull_request_id))
465 assert len(notifications.all()) == 1
465 assert len(notifications.all()) == 1
466
466
467 # Change reviewers and check that a notification was made
467 # Change reviewers and check that a notification was made
468 PullRequestModel().update_reviewers(
468 PullRequestModel().update_reviewers(
469 pull_request.pull_request_id, [(1, [], False, [])],
469 pull_request.pull_request_id, [(1, [], False, [])],
470 pull_request.author)
470 pull_request.author)
471 assert len(notifications.all()) == 2
471 assert len(notifications.all()) == 2
472
472
473 def test_create_pull_request_stores_ancestor_commit_id(self, backend,
473 def test_create_pull_request_stores_ancestor_commit_id(self, backend,
474 csrf_token):
474 csrf_token):
475 commits = [
475 commits = [
476 {'message': 'ancestor',
476 {'message': 'ancestor',
477 'added': [FileNode('file_A', content='content_of_ancestor')]},
477 'added': [FileNode('file_A', content='content_of_ancestor')]},
478 {'message': 'change',
478 {'message': 'change',
479 'added': [FileNode('file_a', content='content_of_change')]},
479 'added': [FileNode('file_a', content='content_of_change')]},
480 {'message': 'change-child'},
480 {'message': 'change-child'},
481 {'message': 'ancestor-child', 'parents': ['ancestor'],
481 {'message': 'ancestor-child', 'parents': ['ancestor'],
482 'added': [
482 'added': [
483 FileNode('file_B', content='content_of_ancestor_child')]},
483 FileNode('file_B', content='content_of_ancestor_child')]},
484 {'message': 'ancestor-child-2'},
484 {'message': 'ancestor-child-2'},
485 ]
485 ]
486 commit_ids = backend.create_master_repo(commits)
486 commit_ids = backend.create_master_repo(commits)
487 target = backend.create_repo(heads=['ancestor-child'])
487 target = backend.create_repo(heads=['ancestor-child'])
488 source = backend.create_repo(heads=['change'])
488 source = backend.create_repo(heads=['change'])
489
489
490 response = self.app.post(
490 response = self.app.post(
491 route_path('pullrequest_create', repo_name=source.repo_name),
491 route_path('pullrequest_create', repo_name=source.repo_name),
492 [
492 [
493 ('source_repo', source.repo_name),
493 ('source_repo', source.repo_name),
494 ('source_ref', 'branch:default:' + commit_ids['change']),
494 ('source_ref', 'branch:default:' + commit_ids['change']),
495 ('target_repo', target.repo_name),
495 ('target_repo', target.repo_name),
496 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
496 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
497 ('common_ancestor', commit_ids['ancestor']),
497 ('common_ancestor', commit_ids['ancestor']),
498 ('pullrequest_title', 'Title'),
498 ('pullrequest_title', 'Title'),
499 ('pullrequest_desc', 'Description'),
499 ('pullrequest_desc', 'Description'),
500 ('description_renderer', 'markdown'),
500 ('description_renderer', 'markdown'),
501 ('__start__', 'review_members:sequence'),
501 ('__start__', 'review_members:sequence'),
502 ('__start__', 'reviewer:mapping'),
502 ('__start__', 'reviewer:mapping'),
503 ('user_id', '1'),
503 ('user_id', '1'),
504 ('__start__', 'reasons:sequence'),
504 ('__start__', 'reasons:sequence'),
505 ('reason', 'Some reason'),
505 ('reason', 'Some reason'),
506 ('__end__', 'reasons:sequence'),
506 ('__end__', 'reasons:sequence'),
507 ('__start__', 'rules:sequence'),
507 ('__start__', 'rules:sequence'),
508 ('__end__', 'rules:sequence'),
508 ('__end__', 'rules:sequence'),
509 ('mandatory', 'False'),
509 ('mandatory', 'False'),
510 ('__end__', 'reviewer:mapping'),
510 ('__end__', 'reviewer:mapping'),
511 ('__end__', 'review_members:sequence'),
511 ('__end__', 'review_members:sequence'),
512 ('__start__', 'revisions:sequence'),
512 ('__start__', 'revisions:sequence'),
513 ('revisions', commit_ids['change']),
513 ('revisions', commit_ids['change']),
514 ('__end__', 'revisions:sequence'),
514 ('__end__', 'revisions:sequence'),
515 ('user', ''),
515 ('user', ''),
516 ('csrf_token', csrf_token),
516 ('csrf_token', csrf_token),
517 ],
517 ],
518 status=302)
518 status=302)
519
519
520 location = response.headers['Location']
520 location = response.headers['Location']
521
521
522 pull_request_id = location.rsplit('/', 1)[1]
522 pull_request_id = location.rsplit('/', 1)[1]
523 assert pull_request_id != 'new'
523 assert pull_request_id != 'new'
524 pull_request = PullRequest.get(int(pull_request_id))
524 pull_request = PullRequest.get(int(pull_request_id))
525
525
526 # target_ref has to point to the ancestor's commit_id in order to
526 # target_ref has to point to the ancestor's commit_id in order to
527 # show the correct diff
527 # show the correct diff
528 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
528 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
529 assert pull_request.target_ref == expected_target_ref
529 assert pull_request.target_ref == expected_target_ref
530
530
531 # Check generated diff contents
531 # Check generated diff contents
532 response = response.follow()
532 response = response.follow()
533 assert 'content_of_ancestor' not in response.body
533 assert 'content_of_ancestor' not in response.body
534 assert 'content_of_ancestor-child' not in response.body
534 assert 'content_of_ancestor-child' not in response.body
535 assert 'content_of_change' in response.body
535 assert 'content_of_change' in response.body
536
536
537 def test_merge_pull_request_enabled(self, pr_util, csrf_token):
537 def test_merge_pull_request_enabled(self, pr_util, csrf_token):
538 # Clear any previous calls to rcextensions
538 # Clear any previous calls to rcextensions
539 rhodecode.EXTENSIONS.calls.clear()
539 rhodecode.EXTENSIONS.calls.clear()
540
540
541 pull_request = pr_util.create_pull_request(
541 pull_request = pr_util.create_pull_request(
542 approved=True, mergeable=True)
542 approved=True, mergeable=True)
543 pull_request_id = pull_request.pull_request_id
543 pull_request_id = pull_request.pull_request_id
544 repo_name = pull_request.target_repo.scm_instance().name,
544 repo_name = pull_request.target_repo.scm_instance().name,
545
545
546 response = self.app.post(
546 response = self.app.post(
547 route_path('pullrequest_merge',
547 route_path('pullrequest_merge',
548 repo_name=str(repo_name[0]),
548 repo_name=str(repo_name[0]),
549 pull_request_id=pull_request_id),
549 pull_request_id=pull_request_id),
550 params={'csrf_token': csrf_token}).follow()
550 params={'csrf_token': csrf_token}).follow()
551
551
552 pull_request = PullRequest.get(pull_request_id)
552 pull_request = PullRequest.get(pull_request_id)
553
553
554 assert response.status_int == 200
554 assert response.status_int == 200
555 assert pull_request.is_closed()
555 assert pull_request.is_closed()
556 assert_pull_request_status(
556 assert_pull_request_status(
557 pull_request, ChangesetStatus.STATUS_APPROVED)
557 pull_request, ChangesetStatus.STATUS_APPROVED)
558
558
559 # Check the relevant log entries were added
559 # Check the relevant log entries were added
560 user_logs = UserLog.query().order_by('-user_log_id').limit(3)
560 user_logs = UserLog.query().order_by('-user_log_id').limit(3)
561 actions = [log.action for log in user_logs]
561 actions = [log.action for log in user_logs]
562 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
562 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
563 expected_actions = [
563 expected_actions = [
564 u'repo.pull_request.close',
564 u'repo.pull_request.close',
565 u'repo.pull_request.merge',
565 u'repo.pull_request.merge',
566 u'repo.pull_request.comment.create'
566 u'repo.pull_request.comment.create'
567 ]
567 ]
568 assert actions == expected_actions
568 assert actions == expected_actions
569
569
570 user_logs = UserLog.query().order_by('-user_log_id').limit(4)
570 user_logs = UserLog.query().order_by('-user_log_id').limit(4)
571 actions = [log for log in user_logs]
571 actions = [log for log in user_logs]
572 assert actions[-1].action == 'user.push'
572 assert actions[-1].action == 'user.push'
573 assert actions[-1].action_data['commit_ids'] == pr_commit_ids
573 assert actions[-1].action_data['commit_ids'] == pr_commit_ids
574
574
575 # Check post_push rcextension was really executed
575 # Check post_push rcextension was really executed
576 push_calls = rhodecode.EXTENSIONS.calls['post_push']
576 push_calls = rhodecode.EXTENSIONS.calls['post_push']
577 assert len(push_calls) == 1
577 assert len(push_calls) == 1
578 unused_last_call_args, last_call_kwargs = push_calls[0]
578 unused_last_call_args, last_call_kwargs = push_calls[0]
579 assert last_call_kwargs['action'] == 'push'
579 assert last_call_kwargs['action'] == 'push'
580 assert last_call_kwargs['pushed_revs'] == pr_commit_ids
580 assert last_call_kwargs['pushed_revs'] == pr_commit_ids
581
581
582 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
582 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
583 pull_request = pr_util.create_pull_request(mergeable=False)
583 pull_request = pr_util.create_pull_request(mergeable=False)
584 pull_request_id = pull_request.pull_request_id
584 pull_request_id = pull_request.pull_request_id
585 pull_request = PullRequest.get(pull_request_id)
585 pull_request = PullRequest.get(pull_request_id)
586
586
587 response = self.app.post(
587 response = self.app.post(
588 route_path('pullrequest_merge',
588 route_path('pullrequest_merge',
589 repo_name=pull_request.target_repo.scm_instance().name,
589 repo_name=pull_request.target_repo.scm_instance().name,
590 pull_request_id=pull_request.pull_request_id),
590 pull_request_id=pull_request.pull_request_id),
591 params={'csrf_token': csrf_token}).follow()
591 params={'csrf_token': csrf_token}).follow()
592
592
593 assert response.status_int == 200
593 assert response.status_int == 200
594 response.mustcontain(
594 response.mustcontain(
595 'Merge is not currently possible because of below failed checks.')
595 'Merge is not currently possible because of below failed checks.')
596 response.mustcontain('Server-side pull request merging is disabled.')
596 response.mustcontain('Server-side pull request merging is disabled.')
597
597
598 @pytest.mark.skip_backends('svn')
598 @pytest.mark.skip_backends('svn')
599 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
599 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
600 pull_request = pr_util.create_pull_request(mergeable=True)
600 pull_request = pr_util.create_pull_request(mergeable=True)
601 pull_request_id = pull_request.pull_request_id
601 pull_request_id = pull_request.pull_request_id
602 repo_name = pull_request.target_repo.scm_instance().name
602 repo_name = pull_request.target_repo.scm_instance().name
603
603
604 response = self.app.post(
604 response = self.app.post(
605 route_path('pullrequest_merge',
605 route_path('pullrequest_merge',
606 repo_name=repo_name,
606 repo_name=repo_name,
607 pull_request_id=pull_request_id),
607 pull_request_id=pull_request_id),
608 params={'csrf_token': csrf_token}).follow()
608 params={'csrf_token': csrf_token}).follow()
609
609
610 assert response.status_int == 200
610 assert response.status_int == 200
611
611
612 response.mustcontain(
612 response.mustcontain(
613 'Merge is not currently possible because of below failed checks.')
613 'Merge is not currently possible because of below failed checks.')
614 response.mustcontain('Pull request reviewer approval is pending.')
614 response.mustcontain('Pull request reviewer approval is pending.')
615
615
616 def test_merge_pull_request_renders_failure_reason(
616 def test_merge_pull_request_renders_failure_reason(
617 self, user_regular, csrf_token, pr_util):
617 self, user_regular, csrf_token, pr_util):
618 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
618 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
619 pull_request_id = pull_request.pull_request_id
619 pull_request_id = pull_request.pull_request_id
620 repo_name = pull_request.target_repo.scm_instance().name
620 repo_name = pull_request.target_repo.scm_instance().name
621
621
622 model_patcher = mock.patch.multiple(
622 model_patcher = mock.patch.multiple(
623 PullRequestModel,
623 PullRequestModel,
624 merge_repo=mock.Mock(return_value=MergeResponse(
624 merge_repo=mock.Mock(return_value=MergeResponse(
625 True, False, 'STUB_COMMIT_ID', MergeFailureReason.PUSH_FAILED)),
625 True, False, 'STUB_COMMIT_ID', MergeFailureReason.PUSH_FAILED)),
626 merge_status=mock.Mock(return_value=(True, 'WRONG_MESSAGE')))
626 merge_status=mock.Mock(return_value=(True, 'WRONG_MESSAGE')))
627
627
628 with model_patcher:
628 with model_patcher:
629 response = self.app.post(
629 response = self.app.post(
630 route_path('pullrequest_merge',
630 route_path('pullrequest_merge',
631 repo_name=repo_name,
631 repo_name=repo_name,
632 pull_request_id=pull_request_id),
632 pull_request_id=pull_request_id),
633 params={'csrf_token': csrf_token}, status=302)
633 params={'csrf_token': csrf_token}, status=302)
634
634
635 assert_session_flash(response, PullRequestModel.MERGE_STATUS_MESSAGES[
635 assert_session_flash(response, PullRequestModel.MERGE_STATUS_MESSAGES[
636 MergeFailureReason.PUSH_FAILED])
636 MergeFailureReason.PUSH_FAILED])
637
637
638 def test_update_source_revision(self, backend, csrf_token):
638 def test_update_source_revision(self, backend, csrf_token):
639 commits = [
639 commits = [
640 {'message': 'ancestor'},
640 {'message': 'ancestor'},
641 {'message': 'change'},
641 {'message': 'change'},
642 {'message': 'change-2'},
642 {'message': 'change-2'},
643 ]
643 ]
644 commit_ids = backend.create_master_repo(commits)
644 commit_ids = backend.create_master_repo(commits)
645 target = backend.create_repo(heads=['ancestor'])
645 target = backend.create_repo(heads=['ancestor'])
646 source = backend.create_repo(heads=['change'])
646 source = backend.create_repo(heads=['change'])
647
647
648 # create pr from a in source to A in target
648 # create pr from a in source to A in target
649 pull_request = PullRequest()
649 pull_request = PullRequest()
650 pull_request.source_repo = source
650 pull_request.source_repo = source
651 # TODO: johbo: Make sure that we write the source ref this way!
651 # TODO: johbo: Make sure that we write the source ref this way!
652 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
652 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
653 branch=backend.default_branch_name, commit_id=commit_ids['change'])
653 branch=backend.default_branch_name, commit_id=commit_ids['change'])
654 pull_request.target_repo = target
654 pull_request.target_repo = target
655
655
656 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
656 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
657 branch=backend.default_branch_name,
657 branch=backend.default_branch_name,
658 commit_id=commit_ids['ancestor'])
658 commit_id=commit_ids['ancestor'])
659 pull_request.revisions = [commit_ids['change']]
659 pull_request.revisions = [commit_ids['change']]
660 pull_request.title = u"Test"
660 pull_request.title = u"Test"
661 pull_request.description = u"Description"
661 pull_request.description = u"Description"
662 pull_request.author = UserModel().get_by_username(
662 pull_request.author = UserModel().get_by_username(
663 TEST_USER_ADMIN_LOGIN)
663 TEST_USER_ADMIN_LOGIN)
664 Session().add(pull_request)
664 Session().add(pull_request)
665 Session().commit()
665 Session().commit()
666 pull_request_id = pull_request.pull_request_id
666 pull_request_id = pull_request.pull_request_id
667
667
668 # source has ancestor - change - change-2
668 # source has ancestor - change - change-2
669 backend.pull_heads(source, heads=['change-2'])
669 backend.pull_heads(source, heads=['change-2'])
670
670
671 # update PR
671 # update PR
672 self.app.post(
672 self.app.post(
673 route_path('pullrequest_update',
673 route_path('pullrequest_update',
674 repo_name=target.repo_name,
674 repo_name=target.repo_name,
675 pull_request_id=pull_request_id),
675 pull_request_id=pull_request_id),
676 params={'update_commits': 'true',
676 params={'update_commits': 'true',
677 'csrf_token': csrf_token})
677 'csrf_token': csrf_token})
678
678
679 # check that we have now both revisions
679 # check that we have now both revisions
680 pull_request = PullRequest.get(pull_request_id)
680 pull_request = PullRequest.get(pull_request_id)
681 assert pull_request.revisions == [
681 assert pull_request.revisions == [
682 commit_ids['change-2'], commit_ids['change']]
682 commit_ids['change-2'], commit_ids['change']]
683
683
684 # TODO: johbo: this should be a test on its own
684 # TODO: johbo: this should be a test on its own
685 response = self.app.get(route_path(
685 response = self.app.get(route_path(
686 'pullrequest_new',
686 'pullrequest_new',
687 repo_name=target.repo_name))
687 repo_name=target.repo_name))
688 assert response.status_int == 200
688 assert response.status_int == 200
689 assert 'Pull request updated to' in response.body
689 assert 'Pull request updated to' in response.body
690 assert 'with 1 added, 0 removed commits.' in response.body
690 assert 'with 1 added, 0 removed commits.' in response.body
691
691
692 def test_update_target_revision(self, backend, csrf_token):
692 def test_update_target_revision(self, backend, csrf_token):
693 commits = [
693 commits = [
694 {'message': 'ancestor'},
694 {'message': 'ancestor'},
695 {'message': 'change'},
695 {'message': 'change'},
696 {'message': 'ancestor-new', 'parents': ['ancestor']},
696 {'message': 'ancestor-new', 'parents': ['ancestor']},
697 {'message': 'change-rebased'},
697 {'message': 'change-rebased'},
698 ]
698 ]
699 commit_ids = backend.create_master_repo(commits)
699 commit_ids = backend.create_master_repo(commits)
700 target = backend.create_repo(heads=['ancestor'])
700 target = backend.create_repo(heads=['ancestor'])
701 source = backend.create_repo(heads=['change'])
701 source = backend.create_repo(heads=['change'])
702
702
703 # create pr from a in source to A in target
703 # create pr from a in source to A in target
704 pull_request = PullRequest()
704 pull_request = PullRequest()
705 pull_request.source_repo = source
705 pull_request.source_repo = source
706 # TODO: johbo: Make sure that we write the source ref this way!
706 # TODO: johbo: Make sure that we write the source ref this way!
707 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
707 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
708 branch=backend.default_branch_name, commit_id=commit_ids['change'])
708 branch=backend.default_branch_name, commit_id=commit_ids['change'])
709 pull_request.target_repo = target
709 pull_request.target_repo = target
710 # TODO: johbo: Target ref should be branch based, since tip can jump
710 # TODO: johbo: Target ref should be branch based, since tip can jump
711 # from branch to branch
711 # from branch to branch
712 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
712 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
713 branch=backend.default_branch_name,
713 branch=backend.default_branch_name,
714 commit_id=commit_ids['ancestor'])
714 commit_id=commit_ids['ancestor'])
715 pull_request.revisions = [commit_ids['change']]
715 pull_request.revisions = [commit_ids['change']]
716 pull_request.title = u"Test"
716 pull_request.title = u"Test"
717 pull_request.description = u"Description"
717 pull_request.description = u"Description"
718 pull_request.author = UserModel().get_by_username(
718 pull_request.author = UserModel().get_by_username(
719 TEST_USER_ADMIN_LOGIN)
719 TEST_USER_ADMIN_LOGIN)
720 Session().add(pull_request)
720 Session().add(pull_request)
721 Session().commit()
721 Session().commit()
722 pull_request_id = pull_request.pull_request_id
722 pull_request_id = pull_request.pull_request_id
723
723
724 # target has ancestor - ancestor-new
724 # target has ancestor - ancestor-new
725 # source has ancestor - ancestor-new - change-rebased
725 # source has ancestor - ancestor-new - change-rebased
726 backend.pull_heads(target, heads=['ancestor-new'])
726 backend.pull_heads(target, heads=['ancestor-new'])
727 backend.pull_heads(source, heads=['change-rebased'])
727 backend.pull_heads(source, heads=['change-rebased'])
728
728
729 # update PR
729 # update PR
730 self.app.post(
730 self.app.post(
731 route_path('pullrequest_update',
731 route_path('pullrequest_update',
732 repo_name=target.repo_name,
732 repo_name=target.repo_name,
733 pull_request_id=pull_request_id),
733 pull_request_id=pull_request_id),
734 params={'update_commits': 'true',
734 params={'update_commits': 'true',
735 'csrf_token': csrf_token},
735 'csrf_token': csrf_token},
736 status=200)
736 status=200)
737
737
738 # check that we have now both revisions
738 # check that we have now both revisions
739 pull_request = PullRequest.get(pull_request_id)
739 pull_request = PullRequest.get(pull_request_id)
740 assert pull_request.revisions == [commit_ids['change-rebased']]
740 assert pull_request.revisions == [commit_ids['change-rebased']]
741 assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format(
741 assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format(
742 branch=backend.default_branch_name,
742 branch=backend.default_branch_name,
743 commit_id=commit_ids['ancestor-new'])
743 commit_id=commit_ids['ancestor-new'])
744
744
745 # TODO: johbo: This should be a test on its own
745 # TODO: johbo: This should be a test on its own
746 response = self.app.get(route_path(
746 response = self.app.get(route_path(
747 'pullrequest_new',
747 'pullrequest_new',
748 repo_name=target.repo_name))
748 repo_name=target.repo_name))
749 assert response.status_int == 200
749 assert response.status_int == 200
750 assert 'Pull request updated to' in response.body
750 assert 'Pull request updated to' in response.body
751 assert 'with 1 added, 1 removed commits.' in response.body
751 assert 'with 1 added, 1 removed commits.' in response.body
752
752
753 def test_update_target_revision_with_removal_of_1_commit_git(self, backend_git, csrf_token):
753 def test_update_target_revision_with_removal_of_1_commit_git(self, backend_git, csrf_token):
754 backend = backend_git
754 backend = backend_git
755 commits = [
755 commits = [
756 {'message': 'master-commit-1'},
756 {'message': 'master-commit-1'},
757 {'message': 'master-commit-2-change-1'},
757 {'message': 'master-commit-2-change-1'},
758 {'message': 'master-commit-3-change-2'},
758 {'message': 'master-commit-3-change-2'},
759
759
760 {'message': 'feat-commit-1', 'parents': ['master-commit-1']},
760 {'message': 'feat-commit-1', 'parents': ['master-commit-1']},
761 {'message': 'feat-commit-2'},
761 {'message': 'feat-commit-2'},
762 ]
762 ]
763 commit_ids = backend.create_master_repo(commits)
763 commit_ids = backend.create_master_repo(commits)
764 target = backend.create_repo(heads=['master-commit-3-change-2'])
764 target = backend.create_repo(heads=['master-commit-3-change-2'])
765 source = backend.create_repo(heads=['feat-commit-2'])
765 source = backend.create_repo(heads=['feat-commit-2'])
766
766
767 # create pr from a in source to A in target
767 # create pr from a in source to A in target
768 pull_request = PullRequest()
768 pull_request = PullRequest()
769 pull_request.source_repo = source
769 pull_request.source_repo = source
770 # TODO: johbo: Make sure that we write the source ref this way!
770 # TODO: johbo: Make sure that we write the source ref this way!
771 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
771 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
772 branch=backend.default_branch_name,
772 branch=backend.default_branch_name,
773 commit_id=commit_ids['master-commit-3-change-2'])
773 commit_id=commit_ids['master-commit-3-change-2'])
774
774
775 pull_request.target_repo = target
775 pull_request.target_repo = target
776 # TODO: johbo: Target ref should be branch based, since tip can jump
776 # TODO: johbo: Target ref should be branch based, since tip can jump
777 # from branch to branch
777 # from branch to branch
778 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
778 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
779 branch=backend.default_branch_name,
779 branch=backend.default_branch_name,
780 commit_id=commit_ids['feat-commit-2'])
780 commit_id=commit_ids['feat-commit-2'])
781
781
782 pull_request.revisions = [
782 pull_request.revisions = [
783 commit_ids['feat-commit-1'],
783 commit_ids['feat-commit-1'],
784 commit_ids['feat-commit-2']
784 commit_ids['feat-commit-2']
785 ]
785 ]
786 pull_request.title = u"Test"
786 pull_request.title = u"Test"
787 pull_request.description = u"Description"
787 pull_request.description = u"Description"
788 pull_request.author = UserModel().get_by_username(
788 pull_request.author = UserModel().get_by_username(
789 TEST_USER_ADMIN_LOGIN)
789 TEST_USER_ADMIN_LOGIN)
790 Session().add(pull_request)
790 Session().add(pull_request)
791 Session().commit()
791 Session().commit()
792 pull_request_id = pull_request.pull_request_id
792 pull_request_id = pull_request.pull_request_id
793
793
794 # PR is created, now we simulate a force-push into target,
794 # PR is created, now we simulate a force-push into target,
795 # that drops a 2 last commits
795 # that drops a 2 last commits
796 vcsrepo = target.scm_instance()
796 vcsrepo = target.scm_instance()
797 vcsrepo.config.clear_section('hooks')
797 vcsrepo.config.clear_section('hooks')
798 vcsrepo.run_git_command(['reset', '--soft', 'HEAD~2'])
798 vcsrepo.run_git_command(['reset', '--soft', 'HEAD~2'])
799
799
800 # update PR
800 # update PR
801 self.app.post(
801 self.app.post(
802 route_path('pullrequest_update',
802 route_path('pullrequest_update',
803 repo_name=target.repo_name,
803 repo_name=target.repo_name,
804 pull_request_id=pull_request_id),
804 pull_request_id=pull_request_id),
805 params={'update_commits': 'true',
805 params={'update_commits': 'true',
806 'csrf_token': csrf_token},
806 'csrf_token': csrf_token},
807 status=200)
807 status=200)
808
808
809 response = self.app.get(route_path(
809 response = self.app.get(route_path(
810 'pullrequest_new',
810 'pullrequest_new',
811 repo_name=target.repo_name))
811 repo_name=target.repo_name))
812 assert response.status_int == 200
812 assert response.status_int == 200
813 response.mustcontain('Pull request updated to')
813 response.mustcontain('Pull request updated to')
814 response.mustcontain('with 0 added, 0 removed commits.')
814 response.mustcontain('with 0 added, 0 removed commits.')
815
815
816 def test_update_of_ancestor_reference(self, backend, csrf_token):
816 def test_update_of_ancestor_reference(self, backend, csrf_token):
817 commits = [
817 commits = [
818 {'message': 'ancestor'},
818 {'message': 'ancestor'},
819 {'message': 'change'},
819 {'message': 'change'},
820 {'message': 'change-2'},
820 {'message': 'change-2'},
821 {'message': 'ancestor-new', 'parents': ['ancestor']},
821 {'message': 'ancestor-new', 'parents': ['ancestor']},
822 {'message': 'change-rebased'},
822 {'message': 'change-rebased'},
823 ]
823 ]
824 commit_ids = backend.create_master_repo(commits)
824 commit_ids = backend.create_master_repo(commits)
825 target = backend.create_repo(heads=['ancestor'])
825 target = backend.create_repo(heads=['ancestor'])
826 source = backend.create_repo(heads=['change'])
826 source = backend.create_repo(heads=['change'])
827
827
828 # create pr from a in source to A in target
828 # create pr from a in source to A in target
829 pull_request = PullRequest()
829 pull_request = PullRequest()
830 pull_request.source_repo = source
830 pull_request.source_repo = source
831 # TODO: johbo: Make sure that we write the source ref this way!
831 # TODO: johbo: Make sure that we write the source ref this way!
832 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
832 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
833 branch=backend.default_branch_name,
833 branch=backend.default_branch_name,
834 commit_id=commit_ids['change'])
834 commit_id=commit_ids['change'])
835 pull_request.target_repo = target
835 pull_request.target_repo = target
836 # TODO: johbo: Target ref should be branch based, since tip can jump
836 # TODO: johbo: Target ref should be branch based, since tip can jump
837 # from branch to branch
837 # from branch to branch
838 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
838 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
839 branch=backend.default_branch_name,
839 branch=backend.default_branch_name,
840 commit_id=commit_ids['ancestor'])
840 commit_id=commit_ids['ancestor'])
841 pull_request.revisions = [commit_ids['change']]
841 pull_request.revisions = [commit_ids['change']]
842 pull_request.title = u"Test"
842 pull_request.title = u"Test"
843 pull_request.description = u"Description"
843 pull_request.description = u"Description"
844 pull_request.author = UserModel().get_by_username(
844 pull_request.author = UserModel().get_by_username(
845 TEST_USER_ADMIN_LOGIN)
845 TEST_USER_ADMIN_LOGIN)
846 Session().add(pull_request)
846 Session().add(pull_request)
847 Session().commit()
847 Session().commit()
848 pull_request_id = pull_request.pull_request_id
848 pull_request_id = pull_request.pull_request_id
849
849
850 # target has ancestor - ancestor-new
850 # target has ancestor - ancestor-new
851 # source has ancestor - ancestor-new - change-rebased
851 # source has ancestor - ancestor-new - change-rebased
852 backend.pull_heads(target, heads=['ancestor-new'])
852 backend.pull_heads(target, heads=['ancestor-new'])
853 backend.pull_heads(source, heads=['change-rebased'])
853 backend.pull_heads(source, heads=['change-rebased'])
854
854
855 # update PR
855 # update PR
856 self.app.post(
856 self.app.post(
857 route_path('pullrequest_update',
857 route_path('pullrequest_update',
858 repo_name=target.repo_name,
858 repo_name=target.repo_name,
859 pull_request_id=pull_request_id),
859 pull_request_id=pull_request_id),
860 params={'update_commits': 'true',
860 params={'update_commits': 'true',
861 'csrf_token': csrf_token},
861 'csrf_token': csrf_token},
862 status=200)
862 status=200)
863
863
864 # Expect the target reference to be updated correctly
864 # Expect the target reference to be updated correctly
865 pull_request = PullRequest.get(pull_request_id)
865 pull_request = PullRequest.get(pull_request_id)
866 assert pull_request.revisions == [commit_ids['change-rebased']]
866 assert pull_request.revisions == [commit_ids['change-rebased']]
867 expected_target_ref = 'branch:{branch}:{commit_id}'.format(
867 expected_target_ref = 'branch:{branch}:{commit_id}'.format(
868 branch=backend.default_branch_name,
868 branch=backend.default_branch_name,
869 commit_id=commit_ids['ancestor-new'])
869 commit_id=commit_ids['ancestor-new'])
870 assert pull_request.target_ref == expected_target_ref
870 assert pull_request.target_ref == expected_target_ref
871
871
872 def test_remove_pull_request_branch(self, backend_git, csrf_token):
872 def test_remove_pull_request_branch(self, backend_git, csrf_token):
873 branch_name = 'development'
873 branch_name = 'development'
874 commits = [
874 commits = [
875 {'message': 'initial-commit'},
875 {'message': 'initial-commit'},
876 {'message': 'old-feature'},
876 {'message': 'old-feature'},
877 {'message': 'new-feature', 'branch': branch_name},
877 {'message': 'new-feature', 'branch': branch_name},
878 ]
878 ]
879 repo = backend_git.create_repo(commits)
879 repo = backend_git.create_repo(commits)
880 commit_ids = backend_git.commit_ids
880 commit_ids = backend_git.commit_ids
881
881
882 pull_request = PullRequest()
882 pull_request = PullRequest()
883 pull_request.source_repo = repo
883 pull_request.source_repo = repo
884 pull_request.target_repo = repo
884 pull_request.target_repo = repo
885 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
885 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
886 branch=branch_name, commit_id=commit_ids['new-feature'])
886 branch=branch_name, commit_id=commit_ids['new-feature'])
887 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
887 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
888 branch=backend_git.default_branch_name,
888 branch=backend_git.default_branch_name,
889 commit_id=commit_ids['old-feature'])
889 commit_id=commit_ids['old-feature'])
890 pull_request.revisions = [commit_ids['new-feature']]
890 pull_request.revisions = [commit_ids['new-feature']]
891 pull_request.title = u"Test"
891 pull_request.title = u"Test"
892 pull_request.description = u"Description"
892 pull_request.description = u"Description"
893 pull_request.author = UserModel().get_by_username(
893 pull_request.author = UserModel().get_by_username(
894 TEST_USER_ADMIN_LOGIN)
894 TEST_USER_ADMIN_LOGIN)
895 Session().add(pull_request)
895 Session().add(pull_request)
896 Session().commit()
896 Session().commit()
897
897
898 vcs = repo.scm_instance()
898 vcs = repo.scm_instance()
899 vcs.remove_ref('refs/heads/{}'.format(branch_name))
899 vcs.remove_ref('refs/heads/{}'.format(branch_name))
900
900
901 response = self.app.get(route_path(
901 response = self.app.get(route_path(
902 'pullrequest_show',
902 'pullrequest_show',
903 repo_name=repo.repo_name,
903 repo_name=repo.repo_name,
904 pull_request_id=pull_request.pull_request_id))
904 pull_request_id=pull_request.pull_request_id))
905
905
906 assert response.status_int == 200
906 assert response.status_int == 200
907 assert_response = AssertResponse(response)
907 assert_response = AssertResponse(response)
908 assert_response.element_contains(
908 assert_response.element_contains(
909 '#changeset_compare_view_content .alert strong',
909 '#changeset_compare_view_content .alert strong',
910 'Missing commits')
910 'Missing commits')
911 assert_response.element_contains(
911 assert_response.element_contains(
912 '#changeset_compare_view_content .alert',
912 '#changeset_compare_view_content .alert',
913 'This pull request cannot be displayed, because one or more'
913 'This pull request cannot be displayed, because one or more'
914 ' commits no longer exist in the source repository.')
914 ' commits no longer exist in the source repository.')
915
915
916 def test_strip_commits_from_pull_request(
916 def test_strip_commits_from_pull_request(
917 self, backend, pr_util, csrf_token):
917 self, backend, pr_util, csrf_token):
918 commits = [
918 commits = [
919 {'message': 'initial-commit'},
919 {'message': 'initial-commit'},
920 {'message': 'old-feature'},
920 {'message': 'old-feature'},
921 {'message': 'new-feature', 'parents': ['initial-commit']},
921 {'message': 'new-feature', 'parents': ['initial-commit']},
922 ]
922 ]
923 pull_request = pr_util.create_pull_request(
923 pull_request = pr_util.create_pull_request(
924 commits, target_head='initial-commit', source_head='new-feature',
924 commits, target_head='initial-commit', source_head='new-feature',
925 revisions=['new-feature'])
925 revisions=['new-feature'])
926
926
927 vcs = pr_util.source_repository.scm_instance()
927 vcs = pr_util.source_repository.scm_instance()
928 if backend.alias == 'git':
928 if backend.alias == 'git':
929 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
929 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
930 else:
930 else:
931 vcs.strip(pr_util.commit_ids['new-feature'])
931 vcs.strip(pr_util.commit_ids['new-feature'])
932
932
933 response = self.app.get(route_path(
933 response = self.app.get(route_path(
934 'pullrequest_show',
934 'pullrequest_show',
935 repo_name=pr_util.target_repository.repo_name,
935 repo_name=pr_util.target_repository.repo_name,
936 pull_request_id=pull_request.pull_request_id))
936 pull_request_id=pull_request.pull_request_id))
937
937
938 assert response.status_int == 200
938 assert response.status_int == 200
939 assert_response = AssertResponse(response)
939 assert_response = AssertResponse(response)
940 assert_response.element_contains(
940 assert_response.element_contains(
941 '#changeset_compare_view_content .alert strong',
941 '#changeset_compare_view_content .alert strong',
942 'Missing commits')
942 'Missing commits')
943 assert_response.element_contains(
943 assert_response.element_contains(
944 '#changeset_compare_view_content .alert',
944 '#changeset_compare_view_content .alert',
945 'This pull request cannot be displayed, because one or more'
945 'This pull request cannot be displayed, because one or more'
946 ' commits no longer exist in the source repository.')
946 ' commits no longer exist in the source repository.')
947 assert_response.element_contains(
947 assert_response.element_contains(
948 '#update_commits',
948 '#update_commits',
949 'Update commits')
949 'Update commits')
950
950
951 def test_strip_commits_and_update(
951 def test_strip_commits_and_update(
952 self, backend, pr_util, csrf_token):
952 self, backend, pr_util, csrf_token):
953 commits = [
953 commits = [
954 {'message': 'initial-commit'},
954 {'message': 'initial-commit'},
955 {'message': 'old-feature'},
955 {'message': 'old-feature'},
956 {'message': 'new-feature', 'parents': ['old-feature']},
956 {'message': 'new-feature', 'parents': ['old-feature']},
957 ]
957 ]
958 pull_request = pr_util.create_pull_request(
958 pull_request = pr_util.create_pull_request(
959 commits, target_head='old-feature', source_head='new-feature',
959 commits, target_head='old-feature', source_head='new-feature',
960 revisions=['new-feature'], mergeable=True)
960 revisions=['new-feature'], mergeable=True)
961
961
962 vcs = pr_util.source_repository.scm_instance()
962 vcs = pr_util.source_repository.scm_instance()
963 if backend.alias == 'git':
963 if backend.alias == 'git':
964 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
964 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
965 else:
965 else:
966 vcs.strip(pr_util.commit_ids['new-feature'])
966 vcs.strip(pr_util.commit_ids['new-feature'])
967
967
968 response = self.app.post(
968 response = self.app.post(
969 route_path('pullrequest_update',
969 route_path('pullrequest_update',
970 repo_name=pull_request.target_repo.repo_name,
970 repo_name=pull_request.target_repo.repo_name,
971 pull_request_id=pull_request.pull_request_id),
971 pull_request_id=pull_request.pull_request_id),
972 params={'update_commits': 'true',
972 params={'update_commits': 'true',
973 'csrf_token': csrf_token})
973 'csrf_token': csrf_token})
974
974
975 assert response.status_int == 200
975 assert response.status_int == 200
976 assert response.body == 'true'
976 assert response.body == 'true'
977
977
978 # Make sure that after update, it won't raise 500 errors
978 # Make sure that after update, it won't raise 500 errors
979 response = self.app.get(route_path(
979 response = self.app.get(route_path(
980 'pullrequest_show',
980 'pullrequest_show',
981 repo_name=pr_util.target_repository.repo_name,
981 repo_name=pr_util.target_repository.repo_name,
982 pull_request_id=pull_request.pull_request_id))
982 pull_request_id=pull_request.pull_request_id))
983
983
984 assert response.status_int == 200
984 assert response.status_int == 200
985 assert_response = AssertResponse(response)
985 assert_response = AssertResponse(response)
986 assert_response.element_contains(
986 assert_response.element_contains(
987 '#changeset_compare_view_content .alert strong',
987 '#changeset_compare_view_content .alert strong',
988 'Missing commits')
988 'Missing commits')
989
989
990 def test_branch_is_a_link(self, pr_util):
990 def test_branch_is_a_link(self, pr_util):
991 pull_request = pr_util.create_pull_request()
991 pull_request = pr_util.create_pull_request()
992 pull_request.source_ref = 'branch:origin:1234567890abcdef'
992 pull_request.source_ref = 'branch:origin:1234567890abcdef'
993 pull_request.target_ref = 'branch:target:abcdef1234567890'
993 pull_request.target_ref = 'branch:target:abcdef1234567890'
994 Session().add(pull_request)
994 Session().add(pull_request)
995 Session().commit()
995 Session().commit()
996
996
997 response = self.app.get(route_path(
997 response = self.app.get(route_path(
998 'pullrequest_show',
998 'pullrequest_show',
999 repo_name=pull_request.target_repo.scm_instance().name,
999 repo_name=pull_request.target_repo.scm_instance().name,
1000 pull_request_id=pull_request.pull_request_id))
1000 pull_request_id=pull_request.pull_request_id))
1001 assert response.status_int == 200
1001 assert response.status_int == 200
1002 assert_response = AssertResponse(response)
1002 assert_response = AssertResponse(response)
1003
1003
1004 origin = assert_response.get_element('.pr-origininfo .tag')
1004 origin = assert_response.get_element('.pr-origininfo .tag')
1005 origin_children = origin.getchildren()
1005 origin_children = origin.getchildren()
1006 assert len(origin_children) == 1
1006 assert len(origin_children) == 1
1007 target = assert_response.get_element('.pr-targetinfo .tag')
1007 target = assert_response.get_element('.pr-targetinfo .tag')
1008 target_children = target.getchildren()
1008 target_children = target.getchildren()
1009 assert len(target_children) == 1
1009 assert len(target_children) == 1
1010
1010
1011 expected_origin_link = route_path(
1011 expected_origin_link = route_path(
1012 'repo_changelog',
1012 'repo_changelog',
1013 repo_name=pull_request.source_repo.scm_instance().name,
1013 repo_name=pull_request.source_repo.scm_instance().name,
1014 params=dict(branch='origin'))
1014 params=dict(branch='origin'))
1015 expected_target_link = route_path(
1015 expected_target_link = route_path(
1016 'repo_changelog',
1016 'repo_changelog',
1017 repo_name=pull_request.target_repo.scm_instance().name,
1017 repo_name=pull_request.target_repo.scm_instance().name,
1018 params=dict(branch='target'))
1018 params=dict(branch='target'))
1019 assert origin_children[0].attrib['href'] == expected_origin_link
1019 assert origin_children[0].attrib['href'] == expected_origin_link
1020 assert origin_children[0].text == 'branch: origin'
1020 assert origin_children[0].text == 'branch: origin'
1021 assert target_children[0].attrib['href'] == expected_target_link
1021 assert target_children[0].attrib['href'] == expected_target_link
1022 assert target_children[0].text == 'branch: target'
1022 assert target_children[0].text == 'branch: target'
1023
1023
1024 def test_bookmark_is_not_a_link(self, pr_util):
1024 def test_bookmark_is_not_a_link(self, pr_util):
1025 pull_request = pr_util.create_pull_request()
1025 pull_request = pr_util.create_pull_request()
1026 pull_request.source_ref = 'bookmark:origin:1234567890abcdef'
1026 pull_request.source_ref = 'bookmark:origin:1234567890abcdef'
1027 pull_request.target_ref = 'bookmark:target:abcdef1234567890'
1027 pull_request.target_ref = 'bookmark:target:abcdef1234567890'
1028 Session().add(pull_request)
1028 Session().add(pull_request)
1029 Session().commit()
1029 Session().commit()
1030
1030
1031 response = self.app.get(route_path(
1031 response = self.app.get(route_path(
1032 'pullrequest_show',
1032 'pullrequest_show',
1033 repo_name=pull_request.target_repo.scm_instance().name,
1033 repo_name=pull_request.target_repo.scm_instance().name,
1034 pull_request_id=pull_request.pull_request_id))
1034 pull_request_id=pull_request.pull_request_id))
1035 assert response.status_int == 200
1035 assert response.status_int == 200
1036 assert_response = AssertResponse(response)
1036 assert_response = AssertResponse(response)
1037
1037
1038 origin = assert_response.get_element('.pr-origininfo .tag')
1038 origin = assert_response.get_element('.pr-origininfo .tag')
1039 assert origin.text.strip() == 'bookmark: origin'
1039 assert origin.text.strip() == 'bookmark: origin'
1040 assert origin.getchildren() == []
1040 assert origin.getchildren() == []
1041
1041
1042 target = assert_response.get_element('.pr-targetinfo .tag')
1042 target = assert_response.get_element('.pr-targetinfo .tag')
1043 assert target.text.strip() == 'bookmark: target'
1043 assert target.text.strip() == 'bookmark: target'
1044 assert target.getchildren() == []
1044 assert target.getchildren() == []
1045
1045
1046 def test_tag_is_not_a_link(self, pr_util):
1046 def test_tag_is_not_a_link(self, pr_util):
1047 pull_request = pr_util.create_pull_request()
1047 pull_request = pr_util.create_pull_request()
1048 pull_request.source_ref = 'tag:origin:1234567890abcdef'
1048 pull_request.source_ref = 'tag:origin:1234567890abcdef'
1049 pull_request.target_ref = 'tag:target:abcdef1234567890'
1049 pull_request.target_ref = 'tag:target:abcdef1234567890'
1050 Session().add(pull_request)
1050 Session().add(pull_request)
1051 Session().commit()
1051 Session().commit()
1052
1052
1053 response = self.app.get(route_path(
1053 response = self.app.get(route_path(
1054 'pullrequest_show',
1054 'pullrequest_show',
1055 repo_name=pull_request.target_repo.scm_instance().name,
1055 repo_name=pull_request.target_repo.scm_instance().name,
1056 pull_request_id=pull_request.pull_request_id))
1056 pull_request_id=pull_request.pull_request_id))
1057 assert response.status_int == 200
1057 assert response.status_int == 200
1058 assert_response = AssertResponse(response)
1058 assert_response = AssertResponse(response)
1059
1059
1060 origin = assert_response.get_element('.pr-origininfo .tag')
1060 origin = assert_response.get_element('.pr-origininfo .tag')
1061 assert origin.text.strip() == 'tag: origin'
1061 assert origin.text.strip() == 'tag: origin'
1062 assert origin.getchildren() == []
1062 assert origin.getchildren() == []
1063
1063
1064 target = assert_response.get_element('.pr-targetinfo .tag')
1064 target = assert_response.get_element('.pr-targetinfo .tag')
1065 assert target.text.strip() == 'tag: target'
1065 assert target.text.strip() == 'tag: target'
1066 assert target.getchildren() == []
1066 assert target.getchildren() == []
1067
1067
1068 @pytest.mark.parametrize('mergeable', [True, False])
1068 @pytest.mark.parametrize('mergeable', [True, False])
1069 def test_shadow_repository_link(
1069 def test_shadow_repository_link(
1070 self, mergeable, pr_util, http_host_only_stub):
1070 self, mergeable, pr_util, http_host_only_stub):
1071 """
1071 """
1072 Check that the pull request summary page displays a link to the shadow
1072 Check that the pull request summary page displays a link to the shadow
1073 repository if the pull request is mergeable. If it is not mergeable
1073 repository if the pull request is mergeable. If it is not mergeable
1074 the link should not be displayed.
1074 the link should not be displayed.
1075 """
1075 """
1076 pull_request = pr_util.create_pull_request(
1076 pull_request = pr_util.create_pull_request(
1077 mergeable=mergeable, enable_notifications=False)
1077 mergeable=mergeable, enable_notifications=False)
1078 target_repo = pull_request.target_repo.scm_instance()
1078 target_repo = pull_request.target_repo.scm_instance()
1079 pr_id = pull_request.pull_request_id
1079 pr_id = pull_request.pull_request_id
1080 shadow_url = '{host}/{repo}/pull-request/{pr_id}/repository'.format(
1080 shadow_url = '{host}/{repo}/pull-request/{pr_id}/repository'.format(
1081 host=http_host_only_stub, repo=target_repo.name, pr_id=pr_id)
1081 host=http_host_only_stub, repo=target_repo.name, pr_id=pr_id)
1082
1082
1083 response = self.app.get(route_path(
1083 response = self.app.get(route_path(
1084 'pullrequest_show',
1084 'pullrequest_show',
1085 repo_name=target_repo.name,
1085 repo_name=target_repo.name,
1086 pull_request_id=pr_id))
1086 pull_request_id=pr_id))
1087
1087
1088 assertr = AssertResponse(response)
1088 assertr = AssertResponse(response)
1089 if mergeable:
1089 if mergeable:
1090 assertr.element_value_contains('input.pr-mergeinfo', shadow_url)
1090 assertr.element_value_contains('input.pr-mergeinfo', shadow_url)
1091 assertr.element_value_contains('input.pr-mergeinfo ', 'pr-merge')
1091 assertr.element_value_contains('input.pr-mergeinfo ', 'pr-merge')
1092 else:
1092 else:
1093 assertr.no_element_exists('.pr-mergeinfo')
1093 assertr.no_element_exists('.pr-mergeinfo')
1094
1094
1095
1095
1096 @pytest.mark.usefixtures('app')
1096 @pytest.mark.usefixtures('app')
1097 @pytest.mark.backends("git", "hg")
1097 @pytest.mark.backends("git", "hg")
1098 class TestPullrequestsControllerDelete(object):
1098 class TestPullrequestsControllerDelete(object):
1099 def test_pull_request_delete_button_permissions_admin(
1099 def test_pull_request_delete_button_permissions_admin(
1100 self, autologin_user, user_admin, pr_util):
1100 self, autologin_user, user_admin, pr_util):
1101 pull_request = pr_util.create_pull_request(
1101 pull_request = pr_util.create_pull_request(
1102 author=user_admin.username, enable_notifications=False)
1102 author=user_admin.username, enable_notifications=False)
1103
1103
1104 response = self.app.get(route_path(
1104 response = self.app.get(route_path(
1105 'pullrequest_show',
1105 'pullrequest_show',
1106 repo_name=pull_request.target_repo.scm_instance().name,
1106 repo_name=pull_request.target_repo.scm_instance().name,
1107 pull_request_id=pull_request.pull_request_id))
1107 pull_request_id=pull_request.pull_request_id))
1108
1108
1109 response.mustcontain('id="delete_pullrequest"')
1109 response.mustcontain('id="delete_pullrequest"')
1110 response.mustcontain('Confirm to delete this pull request')
1110 response.mustcontain('Confirm to delete this pull request')
1111
1111
1112 def test_pull_request_delete_button_permissions_owner(
1112 def test_pull_request_delete_button_permissions_owner(
1113 self, autologin_regular_user, user_regular, pr_util):
1113 self, autologin_regular_user, user_regular, pr_util):
1114 pull_request = pr_util.create_pull_request(
1114 pull_request = pr_util.create_pull_request(
1115 author=user_regular.username, enable_notifications=False)
1115 author=user_regular.username, enable_notifications=False)
1116
1116
1117 response = self.app.get(route_path(
1117 response = self.app.get(route_path(
1118 'pullrequest_show',
1118 'pullrequest_show',
1119 repo_name=pull_request.target_repo.scm_instance().name,
1119 repo_name=pull_request.target_repo.scm_instance().name,
1120 pull_request_id=pull_request.pull_request_id))
1120 pull_request_id=pull_request.pull_request_id))
1121
1121
1122 response.mustcontain('id="delete_pullrequest"')
1122 response.mustcontain('id="delete_pullrequest"')
1123 response.mustcontain('Confirm to delete this pull request')
1123 response.mustcontain('Confirm to delete this pull request')
1124
1124
1125 def test_pull_request_delete_button_permissions_forbidden(
1125 def test_pull_request_delete_button_permissions_forbidden(
1126 self, autologin_regular_user, user_regular, user_admin, pr_util):
1126 self, autologin_regular_user, user_regular, user_admin, pr_util):
1127 pull_request = pr_util.create_pull_request(
1127 pull_request = pr_util.create_pull_request(
1128 author=user_admin.username, enable_notifications=False)
1128 author=user_admin.username, enable_notifications=False)
1129
1129
1130 response = self.app.get(route_path(
1130 response = self.app.get(route_path(
1131 'pullrequest_show',
1131 'pullrequest_show',
1132 repo_name=pull_request.target_repo.scm_instance().name,
1132 repo_name=pull_request.target_repo.scm_instance().name,
1133 pull_request_id=pull_request.pull_request_id))
1133 pull_request_id=pull_request.pull_request_id))
1134 response.mustcontain(no=['id="delete_pullrequest"'])
1134 response.mustcontain(no=['id="delete_pullrequest"'])
1135 response.mustcontain(no=['Confirm to delete this pull request'])
1135 response.mustcontain(no=['Confirm to delete this pull request'])
1136
1136
1137 def test_pull_request_delete_button_permissions_can_update_cannot_delete(
1137 def test_pull_request_delete_button_permissions_can_update_cannot_delete(
1138 self, autologin_regular_user, user_regular, user_admin, pr_util,
1138 self, autologin_regular_user, user_regular, user_admin, pr_util,
1139 user_util):
1139 user_util):
1140
1140
1141 pull_request = pr_util.create_pull_request(
1141 pull_request = pr_util.create_pull_request(
1142 author=user_admin.username, enable_notifications=False)
1142 author=user_admin.username, enable_notifications=False)
1143
1143
1144 user_util.grant_user_permission_to_repo(
1144 user_util.grant_user_permission_to_repo(
1145 pull_request.target_repo, user_regular,
1145 pull_request.target_repo, user_regular,
1146 'repository.write')
1146 'repository.write')
1147
1147
1148 response = self.app.get(route_path(
1148 response = self.app.get(route_path(
1149 'pullrequest_show',
1149 'pullrequest_show',
1150 repo_name=pull_request.target_repo.scm_instance().name,
1150 repo_name=pull_request.target_repo.scm_instance().name,
1151 pull_request_id=pull_request.pull_request_id))
1151 pull_request_id=pull_request.pull_request_id))
1152
1152
1153 response.mustcontain('id="open_edit_pullrequest"')
1153 response.mustcontain('id="open_edit_pullrequest"')
1154 response.mustcontain('id="delete_pullrequest"')
1154 response.mustcontain('id="delete_pullrequest"')
1155 response.mustcontain(no=['Confirm to delete this pull request'])
1155 response.mustcontain(no=['Confirm to delete this pull request'])
1156
1156
1157 def test_delete_comment_returns_404_if_comment_does_not_exist(
1157 def test_delete_comment_returns_404_if_comment_does_not_exist(
1158 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header):
1158 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header):
1159
1159
1160 pull_request = pr_util.create_pull_request(
1160 pull_request = pr_util.create_pull_request(
1161 author=user_admin.username, enable_notifications=False)
1161 author=user_admin.username, enable_notifications=False)
1162
1162
1163 self.app.post(
1163 self.app.post(
1164 route_path(
1164 route_path(
1165 'pullrequest_comment_delete',
1165 'pullrequest_comment_delete',
1166 repo_name=pull_request.target_repo.scm_instance().name,
1166 repo_name=pull_request.target_repo.scm_instance().name,
1167 pull_request_id=pull_request.pull_request_id,
1167 pull_request_id=pull_request.pull_request_id,
1168 comment_id=1024404),
1168 comment_id=1024404),
1169 extra_environ=xhr_header,
1169 extra_environ=xhr_header,
1170 params={'csrf_token': csrf_token},
1170 params={'csrf_token': csrf_token},
1171 status=404
1171 status=404
1172 )
1172 )
1173
1173
1174 def test_delete_comment(
1174 def test_delete_comment(
1175 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header):
1175 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header):
1176
1176
1177 pull_request = pr_util.create_pull_request(
1177 pull_request = pr_util.create_pull_request(
1178 author=user_admin.username, enable_notifications=False)
1178 author=user_admin.username, enable_notifications=False)
1179 comment = pr_util.create_comment()
1179 comment = pr_util.create_comment()
1180 comment_id = comment.comment_id
1180 comment_id = comment.comment_id
1181
1181
1182 response = self.app.post(
1182 response = self.app.post(
1183 route_path(
1183 route_path(
1184 'pullrequest_comment_delete',
1184 'pullrequest_comment_delete',
1185 repo_name=pull_request.target_repo.scm_instance().name,
1185 repo_name=pull_request.target_repo.scm_instance().name,
1186 pull_request_id=pull_request.pull_request_id,
1186 pull_request_id=pull_request.pull_request_id,
1187 comment_id=comment_id),
1187 comment_id=comment_id),
1188 extra_environ=xhr_header,
1188 extra_environ=xhr_header,
1189 params={'csrf_token': csrf_token},
1189 params={'csrf_token': csrf_token},
1190 status=200
1190 status=200
1191 )
1191 )
1192 assert response.body == 'true'
1192 assert response.body == 'true'
1193
1193
1194 @pytest.mark.parametrize('url_type', [
1195 'pullrequest_new',
1196 'pullrequest_create',
1197 'pullrequest_update',
1198 'pullrequest_merge',
1199 ])
1200 def test_pull_request_is_forbidden_on_archived_repo(
1201 self, autologin_user, backend, xhr_header, user_util, url_type):
1202
1203 # create a temporary repo
1204 source = user_util.create_repo(repo_type=backend.alias)
1205 repo_name = source.repo_name
1206 repo = Repository.get_by_repo_name(repo_name)
1207 repo.archived = True
1208 Session().commit()
1209
1210 response = self.app.get(
1211 route_path(url_type, repo_name=repo_name, pull_request_id=1), status=302)
1212
1213 msg = 'Action not supported for archived repository.'
1214 assert_session_flash(response, msg)
1215
1194
1216
1195 def assert_pull_request_status(pull_request, expected_status):
1217 def assert_pull_request_status(pull_request, expected_status):
1196 status = ChangesetStatusModel().calculated_review_status(
1218 status = ChangesetStatusModel().calculated_review_status(
1197 pull_request=pull_request)
1219 pull_request=pull_request)
1198 assert status == expected_status
1220 assert status == expected_status
1199
1221
1200
1222
1201 @pytest.mark.parametrize('route', ['pullrequest_new', 'pullrequest_create'])
1223 @pytest.mark.parametrize('route', ['pullrequest_new', 'pullrequest_create'])
1202 @pytest.mark.usefixtures("autologin_user")
1224 @pytest.mark.usefixtures("autologin_user")
1203 def test_forbidde_to_repo_summary_for_svn_repositories(backend_svn, app, route):
1225 def test_forbidde_to_repo_summary_for_svn_repositories(backend_svn, app, route):
1204 response = app.get(
1226 response = app.get(
1205 route_path(route, repo_name=backend_svn.repo_name), status=404)
1227 route_path(route, repo_name=backend_svn.repo_name), status=404)
1206
1228
@@ -1,150 +1,173 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.lib.utils2 import safe_unicode, safe_str
23 from rhodecode.lib.utils2 import safe_unicode, safe_str
24 from rhodecode.model.db import Repository
24 from rhodecode.model.db import Repository
25 from rhodecode.model.repo import RepoModel
25 from rhodecode.model.repo import RepoModel
26 from rhodecode.tests import (
26 from rhodecode.tests import (
27 HG_REPO, GIT_REPO, assert_session_flash, no_newline_id_generator)
27 HG_REPO, GIT_REPO, assert_session_flash, no_newline_id_generator)
28 from rhodecode.tests.fixture import Fixture
28 from rhodecode.tests.fixture import Fixture
29 from rhodecode.tests.utils import repo_on_filesystem
29 from rhodecode.tests.utils import repo_on_filesystem
30
30
31 fixture = Fixture()
31 fixture = Fixture()
32
32
33
33
34 def route_path(name, params=None, **kwargs):
34 def route_path(name, params=None, **kwargs):
35 import urllib
35 import urllib
36
36
37 base_url = {
37 base_url = {
38 'repo_summary_explicit': '/{repo_name}/summary',
38 'repo_summary_explicit': '/{repo_name}/summary',
39 'repo_summary': '/{repo_name}',
39 'repo_summary': '/{repo_name}',
40 'edit_repo_advanced': '/{repo_name}/settings/advanced',
40 'edit_repo_advanced': '/{repo_name}/settings/advanced',
41 'edit_repo_advanced_delete': '/{repo_name}/settings/advanced/delete',
41 'edit_repo_advanced_delete': '/{repo_name}/settings/advanced/delete',
42 'edit_repo_advanced_archive': '/{repo_name}/settings/advanced/archive',
42 'edit_repo_advanced_fork': '/{repo_name}/settings/advanced/fork',
43 'edit_repo_advanced_fork': '/{repo_name}/settings/advanced/fork',
43 'edit_repo_advanced_locking': '/{repo_name}/settings/advanced/locking',
44 'edit_repo_advanced_locking': '/{repo_name}/settings/advanced/locking',
44 'edit_repo_advanced_journal': '/{repo_name}/settings/advanced/journal',
45 'edit_repo_advanced_journal': '/{repo_name}/settings/advanced/journal',
45
46
46 }[name].format(**kwargs)
47 }[name].format(**kwargs)
47
48
48 if params:
49 if params:
49 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
50 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
50 return base_url
51 return base_url
51
52
52
53
53 @pytest.mark.usefixtures('autologin_user', 'app')
54 @pytest.mark.usefixtures('autologin_user', 'app')
54 class TestAdminRepoSettingsAdvanced(object):
55 class TestAdminRepoSettingsAdvanced(object):
55
56
56 def test_set_repo_fork_has_no_self_id(self, autologin_user, backend):
57 def test_set_repo_fork_has_no_self_id(self, autologin_user, backend):
57 repo = backend.repo
58 repo = backend.repo
58 response = self.app.get(
59 response = self.app.get(
59 route_path('edit_repo_advanced', repo_name=backend.repo_name))
60 route_path('edit_repo_advanced', repo_name=backend.repo_name))
60 opt = """<option value="%s">vcs_test_git</option>""" % repo.repo_id
61 opt = """<option value="%s">vcs_test_git</option>""" % repo.repo_id
61 response.mustcontain(no=[opt])
62 response.mustcontain(no=[opt])
62
63
63 def test_set_fork_of_target_repo(
64 def test_set_fork_of_target_repo(
64 self, autologin_user, backend, csrf_token):
65 self, autologin_user, backend, csrf_token):
65 target_repo = 'target_%s' % backend.alias
66 target_repo = 'target_%s' % backend.alias
66 fixture.create_repo(target_repo, repo_type=backend.alias)
67 fixture.create_repo(target_repo, repo_type=backend.alias)
67 repo2 = Repository.get_by_repo_name(target_repo)
68 repo2 = Repository.get_by_repo_name(target_repo)
68 response = self.app.post(
69 response = self.app.post(
69 route_path('edit_repo_advanced_fork', repo_name=backend.repo_name),
70 route_path('edit_repo_advanced_fork', repo_name=backend.repo_name),
70 params={'id_fork_of': repo2.repo_id,
71 params={'id_fork_of': repo2.repo_id,
71 'csrf_token': csrf_token})
72 'csrf_token': csrf_token})
72 repo = Repository.get_by_repo_name(backend.repo_name)
73 repo = Repository.get_by_repo_name(backend.repo_name)
73 repo2 = Repository.get_by_repo_name(target_repo)
74 repo2 = Repository.get_by_repo_name(target_repo)
74 assert_session_flash(
75 assert_session_flash(
75 response,
76 response,
76 'Marked repo %s as fork of %s' % (repo.repo_name, repo2.repo_name))
77 'Marked repo %s as fork of %s' % (repo.repo_name, repo2.repo_name))
77
78
78 assert repo.fork == repo2
79 assert repo.fork == repo2
79 response = response.follow()
80 response = response.follow()
80 # check if given repo is selected
81 # check if given repo is selected
81
82
82 opt = 'This repository is a fork of <a href="%s">%s</a>' % (
83 opt = 'This repository is a fork of <a href="%s">%s</a>' % (
83 route_path('repo_summary', repo_name=repo2.repo_name),
84 route_path('repo_summary', repo_name=repo2.repo_name),
84 repo2.repo_name)
85 repo2.repo_name)
85
86
86 response.mustcontain(opt)
87 response.mustcontain(opt)
87
88
88 fixture.destroy_repo(target_repo, forks='detach')
89 fixture.destroy_repo(target_repo, forks='detach')
89
90
90 @pytest.mark.backends("hg", "git")
91 @pytest.mark.backends("hg", "git")
91 def test_set_fork_of_other_type_repo(
92 def test_set_fork_of_other_type_repo(
92 self, autologin_user, backend, csrf_token):
93 self, autologin_user, backend, csrf_token):
93 TARGET_REPO_MAP = {
94 TARGET_REPO_MAP = {
94 'git': {
95 'git': {
95 'type': 'hg',
96 'type': 'hg',
96 'repo_name': HG_REPO},
97 'repo_name': HG_REPO},
97 'hg': {
98 'hg': {
98 'type': 'git',
99 'type': 'git',
99 'repo_name': GIT_REPO},
100 'repo_name': GIT_REPO},
100 }
101 }
101 target_repo = TARGET_REPO_MAP[backend.alias]
102 target_repo = TARGET_REPO_MAP[backend.alias]
102
103
103 repo2 = Repository.get_by_repo_name(target_repo['repo_name'])
104 repo2 = Repository.get_by_repo_name(target_repo['repo_name'])
104 response = self.app.post(
105 response = self.app.post(
105 route_path('edit_repo_advanced_fork', repo_name=backend.repo_name),
106 route_path('edit_repo_advanced_fork', repo_name=backend.repo_name),
106 params={'id_fork_of': repo2.repo_id,
107 params={'id_fork_of': repo2.repo_id,
107 'csrf_token': csrf_token})
108 'csrf_token': csrf_token})
108 assert_session_flash(
109 assert_session_flash(
109 response,
110 response,
110 'Cannot set repository as fork of repository with other type')
111 'Cannot set repository as fork of repository with other type')
111
112
112 def test_set_fork_of_none(self, autologin_user, backend, csrf_token):
113 def test_set_fork_of_none(self, autologin_user, backend, csrf_token):
113 # mark it as None
114 # mark it as None
114 response = self.app.post(
115 response = self.app.post(
115 route_path('edit_repo_advanced_fork', repo_name=backend.repo_name),
116 route_path('edit_repo_advanced_fork', repo_name=backend.repo_name),
116 params={'id_fork_of': None,
117 params={'id_fork_of': None,
117 'csrf_token': csrf_token})
118 'csrf_token': csrf_token})
118 assert_session_flash(
119 assert_session_flash(
119 response,
120 response,
120 'Marked repo %s as fork of %s'
121 'Marked repo %s as fork of %s'
121 % (backend.repo_name, "Nothing"))
122 % (backend.repo_name, "Nothing"))
122 assert backend.repo.fork is None
123 assert backend.repo.fork is None
123
124
124 def test_set_fork_of_same_repo(self, autologin_user, backend, csrf_token):
125 def test_set_fork_of_same_repo(self, autologin_user, backend, csrf_token):
125 repo = Repository.get_by_repo_name(backend.repo_name)
126 repo = Repository.get_by_repo_name(backend.repo_name)
126 response = self.app.post(
127 response = self.app.post(
127 route_path('edit_repo_advanced_fork', repo_name=backend.repo_name),
128 route_path('edit_repo_advanced_fork', repo_name=backend.repo_name),
128 params={'id_fork_of': repo.repo_id, 'csrf_token': csrf_token})
129 params={'id_fork_of': repo.repo_id, 'csrf_token': csrf_token})
129 assert_session_flash(
130 assert_session_flash(
130 response, 'An error occurred during this operation')
131 response, 'An error occurred during this operation')
131
132
132 @pytest.mark.parametrize(
133 @pytest.mark.parametrize(
133 "suffix",
134 "suffix",
134 ['', u'ąęł' , '123'],
135 ['', u'ąęł' , '123'],
135 ids=no_newline_id_generator)
136 ids=no_newline_id_generator)
136 def test_advanced_delete(self, autologin_user, backend, suffix, csrf_token):
137 def test_advanced_repo_delete(self, autologin_user, backend, suffix, csrf_token):
137 repo = backend.create_repo(name_suffix=suffix)
138 repo = backend.create_repo(name_suffix=suffix)
138 repo_name = repo.repo_name
139 repo_name = repo.repo_name
139 repo_name_str = safe_str(repo.repo_name)
140 repo_name_str = safe_str(repo.repo_name)
140
141
141 response = self.app.post(
142 response = self.app.post(
142 route_path('edit_repo_advanced_delete', repo_name=repo_name_str),
143 route_path('edit_repo_advanced_delete', repo_name=repo_name_str),
143 params={'csrf_token': csrf_token})
144 params={'csrf_token': csrf_token})
144 assert_session_flash(response,
145 assert_session_flash(response,
145 u'Deleted repository `{}`'.format(repo_name))
146 u'Deleted repository `{}`'.format(repo_name))
146 response.follow()
147 response.follow()
147
148
148 # check if repo was deleted from db
149 # check if repo was deleted from db
149 assert RepoModel().get_by_repo_name(repo_name) is None
150 assert RepoModel().get_by_repo_name(repo_name) is None
150 assert not repo_on_filesystem(repo_name_str)
151 assert not repo_on_filesystem(repo_name_str)
152
153 @pytest.mark.parametrize(
154 "suffix",
155 ['', u'ąęł' , '123'],
156 ids=no_newline_id_generator)
157 def test_advanced_repo_archive(self, autologin_user, backend, suffix, csrf_token):
158 repo = backend.create_repo(name_suffix=suffix)
159 repo_name = repo.repo_name
160 repo_name_str = safe_str(repo.repo_name)
161
162 response = self.app.post(
163 route_path('edit_repo_advanced_archive', repo_name=repo_name_str),
164 params={'csrf_token': csrf_token})
165
166 assert_session_flash(response,
167 u'Archived repository `{}`'.format(repo_name))
168
169 response = self.app.get(route_path('repo_summary', repo_name=repo_name_str))
170 response.mustcontain('This repository has been archived. It is now read-only.')
171
172 # check if repo was deleted from db
173 assert RepoModel().get_by_repo_name(repo_name).archived is True
@@ -1,263 +1,314 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2018 RhodeCode GmbH
3 # Copyright (C) 2011-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.view import view_config
23 from pyramid.view import view_config
24 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
25
25
26 from rhodecode import events
26 from rhodecode.apps._base import RepoAppView
27 from rhodecode.apps._base import RepoAppView
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib import helpers as h
28 from rhodecode.lib import audit_logger
29 from rhodecode.lib import audit_logger
29 from rhodecode.lib.auth import (
30 from rhodecode.lib.auth import (
30 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired,
31 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired,
31 HasRepoPermissionAny)
32 HasRepoPermissionAny)
32 from rhodecode.lib.exceptions import AttachedForksError, AttachedPullRequestsError
33 from rhodecode.lib.exceptions import AttachedForksError, AttachedPullRequestsError
33 from rhodecode.lib.utils2 import safe_int
34 from rhodecode.lib.utils2 import safe_int
34 from rhodecode.lib.vcs import RepositoryError
35 from rhodecode.lib.vcs import RepositoryError
35 from rhodecode.model.db import Session, UserFollowing, User, Repository
36 from rhodecode.model.db import Session, UserFollowing, User, Repository
36 from rhodecode.model.repo import RepoModel
37 from rhodecode.model.repo import RepoModel
37 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.scm import ScmModel
38
39
39 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
40
41
41
42
42 class RepoSettingsView(RepoAppView):
43 class RepoSettingsView(RepoAppView):
43
44
44 def load_default_context(self):
45 def load_default_context(self):
45 c = self._get_local_tmpl_context()
46 c = self._get_local_tmpl_context()
46 return c
47 return c
47
48
49 def _get_users_with_permissions(self):
50 user_permissions = {}
51 for perm in self.db_repo.permissions():
52 user_permissions[perm.user_id] = perm
53
54 return user_permissions
55
48 @LoginRequired()
56 @LoginRequired()
49 @HasRepoPermissionAnyDecorator('repository.admin')
57 @HasRepoPermissionAnyDecorator('repository.admin')
50 @view_config(
58 @view_config(
51 route_name='edit_repo_advanced', request_method='GET',
59 route_name='edit_repo_advanced', request_method='GET',
52 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
60 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
53 def edit_advanced(self):
61 def edit_advanced(self):
54 c = self.load_default_context()
62 c = self.load_default_context()
55 c.active = 'advanced'
63 c.active = 'advanced'
56
64
57 c.default_user_id = User.get_default_user().user_id
65 c.default_user_id = User.get_default_user().user_id
58 c.in_public_journal = UserFollowing.query() \
66 c.in_public_journal = UserFollowing.query() \
59 .filter(UserFollowing.user_id == c.default_user_id) \
67 .filter(UserFollowing.user_id == c.default_user_id) \
60 .filter(UserFollowing.follows_repository == self.db_repo).scalar()
68 .filter(UserFollowing.follows_repository == self.db_repo).scalar()
61
69
62 c.has_origin_repo_read_perm = False
70 c.has_origin_repo_read_perm = False
63 if self.db_repo.fork:
71 if self.db_repo.fork:
64 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
72 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
65 'repository.write', 'repository.read', 'repository.admin')(
73 'repository.write', 'repository.read', 'repository.admin')(
66 self.db_repo.fork.repo_name, 'repo set as fork page')
74 self.db_repo.fork.repo_name, 'repo set as fork page')
67
75
68 return self._get_template_context(c)
76 return self._get_template_context(c)
69
77
70 @LoginRequired()
78 @LoginRequired()
71 @HasRepoPermissionAnyDecorator('repository.admin')
79 @HasRepoPermissionAnyDecorator('repository.admin')
72 @CSRFRequired()
80 @CSRFRequired()
73 @view_config(
81 @view_config(
82 route_name='edit_repo_advanced_archive', request_method='POST',
83 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
84 def edit_advanced_archive(self):
85 """
86 Archives the repository. It will become read-only, and not visible in search
87 or other queries. But still visible for super-admins.
88 """
89
90 _ = self.request.translate
91
92 try:
93 old_data = self.db_repo.get_api_data()
94 RepoModel().archive(self.db_repo)
95
96 repo = audit_logger.RepoWrap(repo_id=None, repo_name=self.db_repo.repo_name)
97 audit_logger.store_web(
98 'repo.archive', action_data={'old_data': old_data},
99 user=self._rhodecode_user, repo=repo)
100
101 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
102 h.flash(
103 _('Archived repository `%s`') % self.db_repo_name,
104 category='success')
105 Session().commit()
106 except Exception:
107 log.exception("Exception during archiving of repository")
108 h.flash(_('An error occurred during archiving of `%s`')
109 % self.db_repo_name, category='error')
110 # redirect to advanced for more deletion options
111 raise HTTPFound(
112 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name,
113 _anchor='advanced-archive'))
114
115 # flush permissions for all users defined in permissions
116 affected_user_ids = self._get_users_with_permissions().keys()
117 events.trigger(events.UserPermissionsChange(affected_user_ids))
118
119 raise HTTPFound(h.route_path('home'))
120
121 @LoginRequired()
122 @HasRepoPermissionAnyDecorator('repository.admin')
123 @CSRFRequired()
124 @view_config(
74 route_name='edit_repo_advanced_delete', request_method='POST',
125 route_name='edit_repo_advanced_delete', request_method='POST',
75 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
126 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
76 def edit_advanced_delete(self):
127 def edit_advanced_delete(self):
77 """
128 """
78 Deletes the repository, or shows warnings if deletion is not possible
129 Deletes the repository, or shows warnings if deletion is not possible
79 because of attached forks or other errors.
130 because of attached forks or other errors.
80 """
131 """
81 _ = self.request.translate
132 _ = self.request.translate
82 handle_forks = self.request.POST.get('forks', None)
133 handle_forks = self.request.POST.get('forks', None)
83 if handle_forks == 'detach_forks':
134 if handle_forks == 'detach_forks':
84 handle_forks = 'detach'
135 handle_forks = 'detach'
85 elif handle_forks == 'delete_forks':
136 elif handle_forks == 'delete_forks':
86 handle_forks = 'delete'
137 handle_forks = 'delete'
87
138
88 try:
139 try:
89 old_data = self.db_repo.get_api_data()
140 old_data = self.db_repo.get_api_data()
90 RepoModel().delete(self.db_repo, forks=handle_forks)
141 RepoModel().delete(self.db_repo, forks=handle_forks)
91
142
92 _forks = self.db_repo.forks.count()
143 _forks = self.db_repo.forks.count()
93 if _forks and handle_forks:
144 if _forks and handle_forks:
94 if handle_forks == 'detach_forks':
145 if handle_forks == 'detach_forks':
95 h.flash(_('Detached %s forks') % _forks, category='success')
146 h.flash(_('Detached %s forks') % _forks, category='success')
96 elif handle_forks == 'delete_forks':
147 elif handle_forks == 'delete_forks':
97 h.flash(_('Deleted %s forks') % _forks, category='success')
148 h.flash(_('Deleted %s forks') % _forks, category='success')
98
149
99 repo = audit_logger.RepoWrap(repo_id=None, repo_name=self.db_repo.repo_name)
150 repo = audit_logger.RepoWrap(repo_id=None, repo_name=self.db_repo.repo_name)
100 audit_logger.store_web(
151 audit_logger.store_web(
101 'repo.delete', action_data={'old_data': old_data},
152 'repo.delete', action_data={'old_data': old_data},
102 user=self._rhodecode_user, repo=repo)
153 user=self._rhodecode_user, repo=repo)
103
154
104 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
155 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
105 h.flash(
156 h.flash(
106 _('Deleted repository `%s`') % self.db_repo_name,
157 _('Deleted repository `%s`') % self.db_repo_name,
107 category='success')
158 category='success')
108 Session().commit()
159 Session().commit()
109 except AttachedForksError:
160 except AttachedForksError:
110 repo_advanced_url = h.route_path(
161 repo_advanced_url = h.route_path(
111 'edit_repo_advanced', repo_name=self.db_repo_name,
162 'edit_repo_advanced', repo_name=self.db_repo_name,
112 _anchor='advanced-delete')
163 _anchor='advanced-delete')
113 delete_anchor = h.link_to(_('detach or delete'), repo_advanced_url)
164 delete_anchor = h.link_to(_('detach or delete'), repo_advanced_url)
114 h.flash(_('Cannot delete `{repo}` it still contains attached forks. '
165 h.flash(_('Cannot delete `{repo}` it still contains attached forks. '
115 'Try using {delete_or_detach} option.')
166 'Try using {delete_or_detach} option.')
116 .format(repo=self.db_repo_name, delete_or_detach=delete_anchor),
167 .format(repo=self.db_repo_name, delete_or_detach=delete_anchor),
117 category='warning')
168 category='warning')
118
169
119 # redirect to advanced for forks handle action ?
170 # redirect to advanced for forks handle action ?
120 raise HTTPFound(repo_advanced_url)
171 raise HTTPFound(repo_advanced_url)
121
172
122 except AttachedPullRequestsError:
173 except AttachedPullRequestsError:
123 repo_advanced_url = h.route_path(
174 repo_advanced_url = h.route_path(
124 'edit_repo_advanced', repo_name=self.db_repo_name,
175 'edit_repo_advanced', repo_name=self.db_repo_name,
125 _anchor='advanced-delete')
176 _anchor='advanced-delete')
126 attached_prs = len(self.db_repo.pull_requests_source +
177 attached_prs = len(self.db_repo.pull_requests_source +
127 self.db_repo.pull_requests_target)
178 self.db_repo.pull_requests_target)
128 h.flash(
179 h.flash(
129 _('Cannot delete `{repo}` it still contains {num} attached pull requests. '
180 _('Cannot delete `{repo}` it still contains {num} attached pull requests. '
130 'Consider archiving the repository instead.').format(
181 'Consider archiving the repository instead.').format(
131 repo=self.db_repo_name, num=attached_prs), category='warning')
182 repo=self.db_repo_name, num=attached_prs), category='warning')
132
183
133 # redirect to advanced for forks handle action ?
184 # redirect to advanced for forks handle action ?
134 raise HTTPFound(repo_advanced_url)
185 raise HTTPFound(repo_advanced_url)
135
186
136 except Exception:
187 except Exception:
137 log.exception("Exception during deletion of repository")
188 log.exception("Exception during deletion of repository")
138 h.flash(_('An error occurred during deletion of `%s`')
189 h.flash(_('An error occurred during deletion of `%s`')
139 % self.db_repo_name, category='error')
190 % self.db_repo_name, category='error')
140 # redirect to advanced for more deletion options
191 # redirect to advanced for more deletion options
141 raise HTTPFound(
192 raise HTTPFound(
142 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name,
193 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name,
143 _anchor='advanced-delete'))
194 _anchor='advanced-delete'))
144
195
145 raise HTTPFound(h.route_path('home'))
196 raise HTTPFound(h.route_path('home'))
146
197
147 @LoginRequired()
198 @LoginRequired()
148 @HasRepoPermissionAnyDecorator('repository.admin')
199 @HasRepoPermissionAnyDecorator('repository.admin')
149 @CSRFRequired()
200 @CSRFRequired()
150 @view_config(
201 @view_config(
151 route_name='edit_repo_advanced_journal', request_method='POST',
202 route_name='edit_repo_advanced_journal', request_method='POST',
152 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
203 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
153 def edit_advanced_journal(self):
204 def edit_advanced_journal(self):
154 """
205 """
155 Set's this repository to be visible in public journal,
206 Set's this repository to be visible in public journal,
156 in other words making default user to follow this repo
207 in other words making default user to follow this repo
157 """
208 """
158 _ = self.request.translate
209 _ = self.request.translate
159
210
160 try:
211 try:
161 user_id = User.get_default_user().user_id
212 user_id = User.get_default_user().user_id
162 ScmModel().toggle_following_repo(self.db_repo.repo_id, user_id)
213 ScmModel().toggle_following_repo(self.db_repo.repo_id, user_id)
163 h.flash(_('Updated repository visibility in public journal'),
214 h.flash(_('Updated repository visibility in public journal'),
164 category='success')
215 category='success')
165 Session().commit()
216 Session().commit()
166 except Exception:
217 except Exception:
167 h.flash(_('An error occurred during setting this '
218 h.flash(_('An error occurred during setting this '
168 'repository in public journal'),
219 'repository in public journal'),
169 category='error')
220 category='error')
170
221
171 raise HTTPFound(
222 raise HTTPFound(
172 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
223 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
173
224
174 @LoginRequired()
225 @LoginRequired()
175 @HasRepoPermissionAnyDecorator('repository.admin')
226 @HasRepoPermissionAnyDecorator('repository.admin')
176 @CSRFRequired()
227 @CSRFRequired()
177 @view_config(
228 @view_config(
178 route_name='edit_repo_advanced_fork', request_method='POST',
229 route_name='edit_repo_advanced_fork', request_method='POST',
179 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
230 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
180 def edit_advanced_fork(self):
231 def edit_advanced_fork(self):
181 """
232 """
182 Mark given repository as a fork of another
233 Mark given repository as a fork of another
183 """
234 """
184 _ = self.request.translate
235 _ = self.request.translate
185
236
186 new_fork_id = safe_int(self.request.POST.get('id_fork_of'))
237 new_fork_id = safe_int(self.request.POST.get('id_fork_of'))
187
238
188 # valid repo, re-check permissions
239 # valid repo, re-check permissions
189 if new_fork_id:
240 if new_fork_id:
190 repo = Repository.get(new_fork_id)
241 repo = Repository.get(new_fork_id)
191 # ensure we have at least read access to the repo we mark
242 # ensure we have at least read access to the repo we mark
192 perm_check = HasRepoPermissionAny(
243 perm_check = HasRepoPermissionAny(
193 'repository.read', 'repository.write', 'repository.admin')
244 'repository.read', 'repository.write', 'repository.admin')
194
245
195 if repo and perm_check(repo_name=repo.repo_name):
246 if repo and perm_check(repo_name=repo.repo_name):
196 new_fork_id = repo.repo_id
247 new_fork_id = repo.repo_id
197 else:
248 else:
198 new_fork_id = None
249 new_fork_id = None
199
250
200 try:
251 try:
201 repo = ScmModel().mark_as_fork(
252 repo = ScmModel().mark_as_fork(
202 self.db_repo_name, new_fork_id, self._rhodecode_user.user_id)
253 self.db_repo_name, new_fork_id, self._rhodecode_user.user_id)
203 fork = repo.fork.repo_name if repo.fork else _('Nothing')
254 fork = repo.fork.repo_name if repo.fork else _('Nothing')
204 Session().commit()
255 Session().commit()
205 h.flash(
256 h.flash(
206 _('Marked repo %s as fork of %s') % (self.db_repo_name, fork),
257 _('Marked repo %s as fork of %s') % (self.db_repo_name, fork),
207 category='success')
258 category='success')
208 except RepositoryError as e:
259 except RepositoryError as e:
209 log.exception("Repository Error occurred")
260 log.exception("Repository Error occurred")
210 h.flash(str(e), category='error')
261 h.flash(str(e), category='error')
211 except Exception:
262 except Exception:
212 log.exception("Exception while editing fork")
263 log.exception("Exception while editing fork")
213 h.flash(_('An error occurred during this operation'),
264 h.flash(_('An error occurred during this operation'),
214 category='error')
265 category='error')
215
266
216 raise HTTPFound(
267 raise HTTPFound(
217 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
268 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
218
269
219 @LoginRequired()
270 @LoginRequired()
220 @HasRepoPermissionAnyDecorator('repository.admin')
271 @HasRepoPermissionAnyDecorator('repository.admin')
221 @CSRFRequired()
272 @CSRFRequired()
222 @view_config(
273 @view_config(
223 route_name='edit_repo_advanced_locking', request_method='POST',
274 route_name='edit_repo_advanced_locking', request_method='POST',
224 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
275 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
225 def edit_advanced_locking(self):
276 def edit_advanced_locking(self):
226 """
277 """
227 Toggle locking of repository
278 Toggle locking of repository
228 """
279 """
229 _ = self.request.translate
280 _ = self.request.translate
230 set_lock = self.request.POST.get('set_lock')
281 set_lock = self.request.POST.get('set_lock')
231 set_unlock = self.request.POST.get('set_unlock')
282 set_unlock = self.request.POST.get('set_unlock')
232
283
233 try:
284 try:
234 if set_lock:
285 if set_lock:
235 Repository.lock(self.db_repo, self._rhodecode_user.user_id,
286 Repository.lock(self.db_repo, self._rhodecode_user.user_id,
236 lock_reason=Repository.LOCK_WEB)
287 lock_reason=Repository.LOCK_WEB)
237 h.flash(_('Locked repository'), category='success')
288 h.flash(_('Locked repository'), category='success')
238 elif set_unlock:
289 elif set_unlock:
239 Repository.unlock(self.db_repo)
290 Repository.unlock(self.db_repo)
240 h.flash(_('Unlocked repository'), category='success')
291 h.flash(_('Unlocked repository'), category='success')
241 except Exception as e:
292 except Exception as e:
242 log.exception("Exception during unlocking")
293 log.exception("Exception during unlocking")
243 h.flash(_('An error occurred during unlocking'), category='error')
294 h.flash(_('An error occurred during unlocking'), category='error')
244
295
245 raise HTTPFound(
296 raise HTTPFound(
246 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
297 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
247
298
248 @LoginRequired()
299 @LoginRequired()
249 @HasRepoPermissionAnyDecorator('repository.admin')
300 @HasRepoPermissionAnyDecorator('repository.admin')
250 @view_config(
301 @view_config(
251 route_name='edit_repo_advanced_hooks', request_method='GET',
302 route_name='edit_repo_advanced_hooks', request_method='GET',
252 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
303 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
253 def edit_advanced_install_hooks(self):
304 def edit_advanced_install_hooks(self):
254 """
305 """
255 Install Hooks for repository
306 Install Hooks for repository
256 """
307 """
257 _ = self.request.translate
308 _ = self.request.translate
258 self.load_default_context()
309 self.load_default_context()
259 self.rhodecode_vcs_repo.install_hooks(force=True)
310 self.rhodecode_vcs_repo.install_hooks(force=True)
260 h.flash(_('installed updated hooks into this repository'),
311 h.flash(_('installed updated hooks into this repository'),
261 category='success')
312 category='success')
262 raise HTTPFound(
313 raise HTTPFound(
263 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
314 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
@@ -1,287 +1,288 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2017-2018 RhodeCode GmbH
3 # Copyright (C) 2017-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import datetime
22 import datetime
23
23
24 from rhodecode.lib.jsonalchemy import JsonRaw
24 from rhodecode.lib.jsonalchemy import JsonRaw
25 from rhodecode.model import meta
25 from rhodecode.model import meta
26 from rhodecode.model.db import User, UserLog, Repository
26 from rhodecode.model.db import User, UserLog, Repository
27
27
28
28
29 log = logging.getLogger(__name__)
29 log = logging.getLogger(__name__)
30
30
31 # action as key, and expected action_data as value
31 # action as key, and expected action_data as value
32 ACTIONS_V1 = {
32 ACTIONS_V1 = {
33 'user.login.success': {'user_agent': ''},
33 'user.login.success': {'user_agent': ''},
34 'user.login.failure': {'user_agent': ''},
34 'user.login.failure': {'user_agent': ''},
35 'user.logout': {'user_agent': ''},
35 'user.logout': {'user_agent': ''},
36 'user.register': {},
36 'user.register': {},
37 'user.password.reset_request': {},
37 'user.password.reset_request': {},
38 'user.push': {'user_agent': '', 'commit_ids': []},
38 'user.push': {'user_agent': '', 'commit_ids': []},
39 'user.pull': {'user_agent': ''},
39 'user.pull': {'user_agent': ''},
40
40
41 'user.create': {'data': {}},
41 'user.create': {'data': {}},
42 'user.delete': {'old_data': {}},
42 'user.delete': {'old_data': {}},
43 'user.edit': {'old_data': {}},
43 'user.edit': {'old_data': {}},
44 'user.edit.permissions': {},
44 'user.edit.permissions': {},
45 'user.edit.ip.add': {'ip': {}, 'user': {}},
45 'user.edit.ip.add': {'ip': {}, 'user': {}},
46 'user.edit.ip.delete': {'ip': {}, 'user': {}},
46 'user.edit.ip.delete': {'ip': {}, 'user': {}},
47 'user.edit.token.add': {'token': {}, 'user': {}},
47 'user.edit.token.add': {'token': {}, 'user': {}},
48 'user.edit.token.delete': {'token': {}, 'user': {}},
48 'user.edit.token.delete': {'token': {}, 'user': {}},
49 'user.edit.email.add': {'email': ''},
49 'user.edit.email.add': {'email': ''},
50 'user.edit.email.delete': {'email': ''},
50 'user.edit.email.delete': {'email': ''},
51 'user.edit.ssh_key.add': {'token': {}, 'user': {}},
51 'user.edit.ssh_key.add': {'token': {}, 'user': {}},
52 'user.edit.ssh_key.delete': {'token': {}, 'user': {}},
52 'user.edit.ssh_key.delete': {'token': {}, 'user': {}},
53 'user.edit.password_reset.enabled': {},
53 'user.edit.password_reset.enabled': {},
54 'user.edit.password_reset.disabled': {},
54 'user.edit.password_reset.disabled': {},
55
55
56 'user_group.create': {'data': {}},
56 'user_group.create': {'data': {}},
57 'user_group.delete': {'old_data': {}},
57 'user_group.delete': {'old_data': {}},
58 'user_group.edit': {'old_data': {}},
58 'user_group.edit': {'old_data': {}},
59 'user_group.edit.permissions': {},
59 'user_group.edit.permissions': {},
60 'user_group.edit.member.add': {'user': {}},
60 'user_group.edit.member.add': {'user': {}},
61 'user_group.edit.member.delete': {'user': {}},
61 'user_group.edit.member.delete': {'user': {}},
62
62
63 'repo.create': {'data': {}},
63 'repo.create': {'data': {}},
64 'repo.fork': {'data': {}},
64 'repo.fork': {'data': {}},
65 'repo.edit': {'old_data': {}},
65 'repo.edit': {'old_data': {}},
66 'repo.edit.permissions': {},
66 'repo.edit.permissions': {},
67 'repo.edit.permissions.branch': {},
67 'repo.edit.permissions.branch': {},
68 'repo.archive': {'old_data': {}},
68 'repo.delete': {'old_data': {}},
69 'repo.delete': {'old_data': {}},
69
70
70 'repo.archive.download': {'user_agent': '', 'archive_name': '',
71 'repo.archive.download': {'user_agent': '', 'archive_name': '',
71 'archive_spec': '', 'archive_cached': ''},
72 'archive_spec': '', 'archive_cached': ''},
72
73
73 'repo.permissions.branch_rule.create': {},
74 'repo.permissions.branch_rule.create': {},
74 'repo.permissions.branch_rule.edit': {},
75 'repo.permissions.branch_rule.edit': {},
75 'repo.permissions.branch_rule.delete': {},
76 'repo.permissions.branch_rule.delete': {},
76
77
77 'repo.pull_request.create': '',
78 'repo.pull_request.create': '',
78 'repo.pull_request.edit': '',
79 'repo.pull_request.edit': '',
79 'repo.pull_request.delete': '',
80 'repo.pull_request.delete': '',
80 'repo.pull_request.close': '',
81 'repo.pull_request.close': '',
81 'repo.pull_request.merge': '',
82 'repo.pull_request.merge': '',
82 'repo.pull_request.vote': '',
83 'repo.pull_request.vote': '',
83 'repo.pull_request.comment.create': '',
84 'repo.pull_request.comment.create': '',
84 'repo.pull_request.comment.delete': '',
85 'repo.pull_request.comment.delete': '',
85
86
86 'repo.pull_request.reviewer.add': '',
87 'repo.pull_request.reviewer.add': '',
87 'repo.pull_request.reviewer.delete': '',
88 'repo.pull_request.reviewer.delete': '',
88
89
89 'repo.commit.strip': {'commit_id': ''},
90 'repo.commit.strip': {'commit_id': ''},
90 'repo.commit.comment.create': {'data': {}},
91 'repo.commit.comment.create': {'data': {}},
91 'repo.commit.comment.delete': {'data': {}},
92 'repo.commit.comment.delete': {'data': {}},
92 'repo.commit.vote': '',
93 'repo.commit.vote': '',
93
94
94 'repo_group.create': {'data': {}},
95 'repo_group.create': {'data': {}},
95 'repo_group.edit': {'old_data': {}},
96 'repo_group.edit': {'old_data': {}},
96 'repo_group.edit.permissions': {},
97 'repo_group.edit.permissions': {},
97 'repo_group.delete': {'old_data': {}},
98 'repo_group.delete': {'old_data': {}},
98 }
99 }
99
100
100 ACTIONS = ACTIONS_V1
101 ACTIONS = ACTIONS_V1
101
102
102 SOURCE_WEB = 'source_web'
103 SOURCE_WEB = 'source_web'
103 SOURCE_API = 'source_api'
104 SOURCE_API = 'source_api'
104
105
105
106
106 class UserWrap(object):
107 class UserWrap(object):
107 """
108 """
108 Fake object used to imitate AuthUser
109 Fake object used to imitate AuthUser
109 """
110 """
110
111
111 def __init__(self, user_id=None, username=None, ip_addr=None):
112 def __init__(self, user_id=None, username=None, ip_addr=None):
112 self.user_id = user_id
113 self.user_id = user_id
113 self.username = username
114 self.username = username
114 self.ip_addr = ip_addr
115 self.ip_addr = ip_addr
115
116
116
117
117 class RepoWrap(object):
118 class RepoWrap(object):
118 """
119 """
119 Fake object used to imitate RepoObject that audit logger requires
120 Fake object used to imitate RepoObject that audit logger requires
120 """
121 """
121
122
122 def __init__(self, repo_id=None, repo_name=None):
123 def __init__(self, repo_id=None, repo_name=None):
123 self.repo_id = repo_id
124 self.repo_id = repo_id
124 self.repo_name = repo_name
125 self.repo_name = repo_name
125
126
126
127
127 def _store_log(action_name, action_data, user_id, username, user_data,
128 def _store_log(action_name, action_data, user_id, username, user_data,
128 ip_address, repository_id, repository_name):
129 ip_address, repository_id, repository_name):
129 user_log = UserLog()
130 user_log = UserLog()
130 user_log.version = UserLog.VERSION_2
131 user_log.version = UserLog.VERSION_2
131
132
132 user_log.action = action_name
133 user_log.action = action_name
133 user_log.action_data = action_data or JsonRaw(u'{}')
134 user_log.action_data = action_data or JsonRaw(u'{}')
134
135
135 user_log.user_ip = ip_address
136 user_log.user_ip = ip_address
136
137
137 user_log.user_id = user_id
138 user_log.user_id = user_id
138 user_log.username = username
139 user_log.username = username
139 user_log.user_data = user_data or JsonRaw(u'{}')
140 user_log.user_data = user_data or JsonRaw(u'{}')
140
141
141 user_log.repository_id = repository_id
142 user_log.repository_id = repository_id
142 user_log.repository_name = repository_name
143 user_log.repository_name = repository_name
143
144
144 user_log.action_date = datetime.datetime.now()
145 user_log.action_date = datetime.datetime.now()
145
146
146 return user_log
147 return user_log
147
148
148
149
149 def store_web(*args, **kwargs):
150 def store_web(*args, **kwargs):
150 if 'action_data' not in kwargs:
151 if 'action_data' not in kwargs:
151 kwargs['action_data'] = {}
152 kwargs['action_data'] = {}
152 kwargs['action_data'].update({
153 kwargs['action_data'].update({
153 'source': SOURCE_WEB
154 'source': SOURCE_WEB
154 })
155 })
155 return store(*args, **kwargs)
156 return store(*args, **kwargs)
156
157
157
158
158 def store_api(*args, **kwargs):
159 def store_api(*args, **kwargs):
159 if 'action_data' not in kwargs:
160 if 'action_data' not in kwargs:
160 kwargs['action_data'] = {}
161 kwargs['action_data'] = {}
161 kwargs['action_data'].update({
162 kwargs['action_data'].update({
162 'source': SOURCE_API
163 'source': SOURCE_API
163 })
164 })
164 return store(*args, **kwargs)
165 return store(*args, **kwargs)
165
166
166
167
167 def store(action, user, action_data=None, user_data=None, ip_addr=None,
168 def store(action, user, action_data=None, user_data=None, ip_addr=None,
168 repo=None, sa_session=None, commit=False):
169 repo=None, sa_session=None, commit=False):
169 """
170 """
170 Audit logger for various actions made by users, typically this
171 Audit logger for various actions made by users, typically this
171 results in a call such::
172 results in a call such::
172
173
173 from rhodecode.lib import audit_logger
174 from rhodecode.lib import audit_logger
174
175
175 audit_logger.store(
176 audit_logger.store(
176 'repo.edit', user=self._rhodecode_user)
177 'repo.edit', user=self._rhodecode_user)
177 audit_logger.store(
178 audit_logger.store(
178 'repo.delete', action_data={'data': repo_data},
179 'repo.delete', action_data={'data': repo_data},
179 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'))
180 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'))
180
181
181 # repo action
182 # repo action
182 audit_logger.store(
183 audit_logger.store(
183 'repo.delete',
184 'repo.delete',
184 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'),
185 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'),
185 repo=audit_logger.RepoWrap(repo_name='some-repo'))
186 repo=audit_logger.RepoWrap(repo_name='some-repo'))
186
187
187 # repo action, when we know and have the repository object already
188 # repo action, when we know and have the repository object already
188 audit_logger.store(
189 audit_logger.store(
189 'repo.delete', action_data={'source': audit_logger.SOURCE_WEB, },
190 'repo.delete', action_data={'source': audit_logger.SOURCE_WEB, },
190 user=self._rhodecode_user,
191 user=self._rhodecode_user,
191 repo=repo_object)
192 repo=repo_object)
192
193
193 # alternative wrapper to the above
194 # alternative wrapper to the above
194 audit_logger.store_web(
195 audit_logger.store_web(
195 'repo.delete', action_data={},
196 'repo.delete', action_data={},
196 user=self._rhodecode_user,
197 user=self._rhodecode_user,
197 repo=repo_object)
198 repo=repo_object)
198
199
199 # without an user ?
200 # without an user ?
200 audit_logger.store(
201 audit_logger.store(
201 'user.login.failure',
202 'user.login.failure',
202 user=audit_logger.UserWrap(
203 user=audit_logger.UserWrap(
203 username=self.request.params.get('username'),
204 username=self.request.params.get('username'),
204 ip_addr=self.request.remote_addr))
205 ip_addr=self.request.remote_addr))
205
206
206 """
207 """
207 from rhodecode.lib.utils2 import safe_unicode
208 from rhodecode.lib.utils2 import safe_unicode
208 from rhodecode.lib.auth import AuthUser
209 from rhodecode.lib.auth import AuthUser
209
210
210 action_spec = ACTIONS.get(action, None)
211 action_spec = ACTIONS.get(action, None)
211 if action_spec is None:
212 if action_spec is None:
212 raise ValueError('Action `{}` is not supported'.format(action))
213 raise ValueError('Action `{}` is not supported'.format(action))
213
214
214 if not sa_session:
215 if not sa_session:
215 sa_session = meta.Session()
216 sa_session = meta.Session()
216
217
217 try:
218 try:
218 username = getattr(user, 'username', None)
219 username = getattr(user, 'username', None)
219 if not username:
220 if not username:
220 pass
221 pass
221
222
222 user_id = getattr(user, 'user_id', None)
223 user_id = getattr(user, 'user_id', None)
223 if not user_id:
224 if not user_id:
224 # maybe we have username ? Try to figure user_id from username
225 # maybe we have username ? Try to figure user_id from username
225 if username:
226 if username:
226 user_id = getattr(
227 user_id = getattr(
227 User.get_by_username(username), 'user_id', None)
228 User.get_by_username(username), 'user_id', None)
228
229
229 ip_addr = ip_addr or getattr(user, 'ip_addr', None)
230 ip_addr = ip_addr or getattr(user, 'ip_addr', None)
230 if not ip_addr:
231 if not ip_addr:
231 pass
232 pass
232
233
233 if not user_data:
234 if not user_data:
234 # try to get this from the auth user
235 # try to get this from the auth user
235 if isinstance(user, AuthUser):
236 if isinstance(user, AuthUser):
236 user_data = {
237 user_data = {
237 'username': user.username,
238 'username': user.username,
238 'email': user.email,
239 'email': user.email,
239 }
240 }
240
241
241 repository_name = getattr(repo, 'repo_name', None)
242 repository_name = getattr(repo, 'repo_name', None)
242 repository_id = getattr(repo, 'repo_id', None)
243 repository_id = getattr(repo, 'repo_id', None)
243 if not repository_id:
244 if not repository_id:
244 # maybe we have repo_name ? Try to figure repo_id from repo_name
245 # maybe we have repo_name ? Try to figure repo_id from repo_name
245 if repository_name:
246 if repository_name:
246 repository_id = getattr(
247 repository_id = getattr(
247 Repository.get_by_repo_name(repository_name), 'repo_id', None)
248 Repository.get_by_repo_name(repository_name), 'repo_id', None)
248
249
249 action_name = safe_unicode(action)
250 action_name = safe_unicode(action)
250 ip_address = safe_unicode(ip_addr)
251 ip_address = safe_unicode(ip_addr)
251
252
252 with sa_session.no_autoflush:
253 with sa_session.no_autoflush:
253 update_user_last_activity(sa_session, user_id)
254 update_user_last_activity(sa_session, user_id)
254
255
255 user_log = _store_log(
256 user_log = _store_log(
256 action_name=action_name,
257 action_name=action_name,
257 action_data=action_data or {},
258 action_data=action_data or {},
258 user_id=user_id,
259 user_id=user_id,
259 username=username,
260 username=username,
260 user_data=user_data or {},
261 user_data=user_data or {},
261 ip_address=ip_address,
262 ip_address=ip_address,
262 repository_id=repository_id,
263 repository_id=repository_id,
263 repository_name=repository_name
264 repository_name=repository_name
264 )
265 )
265
266
266 sa_session.add(user_log)
267 sa_session.add(user_log)
267
268
268 if commit:
269 if commit:
269 sa_session.commit()
270 sa_session.commit()
270
271
271 entry_id = user_log.entry_id or ''
272 entry_id = user_log.entry_id or ''
272 log.info('AUDIT[%s]: Logging action: `%s` by user:id:%s[%s] ip:%s',
273 log.info('AUDIT[%s]: Logging action: `%s` by user:id:%s[%s] ip:%s',
273 entry_id, action_name, user_id, username, ip_address)
274 entry_id, action_name, user_id, username, ip_address)
274
275
275 except Exception:
276 except Exception:
276 log.exception('AUDIT: failed to store audit log')
277 log.exception('AUDIT: failed to store audit log')
277
278
278
279
279 def update_user_last_activity(sa_session, user_id):
280 def update_user_last_activity(sa_session, user_id):
280 _last_activity = datetime.datetime.now()
281 _last_activity = datetime.datetime.now()
281 try:
282 try:
282 sa_session.query(User).filter(User.user_id == user_id).update(
283 sa_session.query(User).filter(User.user_id == user_id).update(
283 {"last_activity": _last_activity})
284 {"last_activity": _last_activity})
284 log.debug(
285 log.debug(
285 'updated user `%s` last activity to:%s', user_id, _last_activity)
286 'updated user `%s` last activity to:%s', user_id, _last_activity)
286 except Exception:
287 except Exception:
287 log.exception("Failed last activity update")
288 log.exception("Failed last activity update")
@@ -1,2338 +1,2355 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 authentication and permission libraries
22 authentication and permission libraries
23 """
23 """
24
24
25 import os
25 import os
26 import time
26 import time
27 import inspect
27 import inspect
28 import collections
28 import collections
29 import fnmatch
29 import fnmatch
30 import hashlib
30 import hashlib
31 import itertools
31 import itertools
32 import logging
32 import logging
33 import random
33 import random
34 import traceback
34 import traceback
35 from functools import wraps
35 from functools import wraps
36
36
37 import ipaddress
37 import ipaddress
38
38
39 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
39 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
40 from sqlalchemy.orm.exc import ObjectDeletedError
40 from sqlalchemy.orm.exc import ObjectDeletedError
41 from sqlalchemy.orm import joinedload
41 from sqlalchemy.orm import joinedload
42 from zope.cachedescriptors.property import Lazy as LazyProperty
42 from zope.cachedescriptors.property import Lazy as LazyProperty
43
43
44 import rhodecode
44 import rhodecode
45 from rhodecode.model import meta
45 from rhodecode.model import meta
46 from rhodecode.model.meta import Session
46 from rhodecode.model.meta import Session
47 from rhodecode.model.user import UserModel
47 from rhodecode.model.user import UserModel
48 from rhodecode.model.db import (
48 from rhodecode.model.db import (
49 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
49 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
50 UserIpMap, UserApiKeys, RepoGroup, UserGroup)
50 UserIpMap, UserApiKeys, RepoGroup, UserGroup)
51 from rhodecode.lib import rc_cache
51 from rhodecode.lib import rc_cache
52 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
52 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
53 from rhodecode.lib.utils import (
53 from rhodecode.lib.utils import (
54 get_repo_slug, get_repo_group_slug, get_user_group_slug)
54 get_repo_slug, get_repo_group_slug, get_user_group_slug)
55 from rhodecode.lib.caching_query import FromCache
55 from rhodecode.lib.caching_query import FromCache
56
56
57
57
58 if rhodecode.is_unix:
58 if rhodecode.is_unix:
59 import bcrypt
59 import bcrypt
60
60
61 log = logging.getLogger(__name__)
61 log = logging.getLogger(__name__)
62
62
63 csrf_token_key = "csrf_token"
63 csrf_token_key = "csrf_token"
64
64
65
65
66 class PasswordGenerator(object):
66 class PasswordGenerator(object):
67 """
67 """
68 This is a simple class for generating password from different sets of
68 This is a simple class for generating password from different sets of
69 characters
69 characters
70 usage::
70 usage::
71
71
72 passwd_gen = PasswordGenerator()
72 passwd_gen = PasswordGenerator()
73 #print 8-letter password containing only big and small letters
73 #print 8-letter password containing only big and small letters
74 of alphabet
74 of alphabet
75 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
75 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
76 """
76 """
77 ALPHABETS_NUM = r'''1234567890'''
77 ALPHABETS_NUM = r'''1234567890'''
78 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
78 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
79 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
79 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
80 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
80 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
81 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
81 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
82 + ALPHABETS_NUM + ALPHABETS_SPECIAL
82 + ALPHABETS_NUM + ALPHABETS_SPECIAL
83 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
83 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
84 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
84 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
85 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
85 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
86 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
86 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
87
87
88 def __init__(self, passwd=''):
88 def __init__(self, passwd=''):
89 self.passwd = passwd
89 self.passwd = passwd
90
90
91 def gen_password(self, length, type_=None):
91 def gen_password(self, length, type_=None):
92 if type_ is None:
92 if type_ is None:
93 type_ = self.ALPHABETS_FULL
93 type_ = self.ALPHABETS_FULL
94 self.passwd = ''.join([random.choice(type_) for _ in range(length)])
94 self.passwd = ''.join([random.choice(type_) for _ in range(length)])
95 return self.passwd
95 return self.passwd
96
96
97
97
98 class _RhodeCodeCryptoBase(object):
98 class _RhodeCodeCryptoBase(object):
99 ENC_PREF = None
99 ENC_PREF = None
100
100
101 def hash_create(self, str_):
101 def hash_create(self, str_):
102 """
102 """
103 hash the string using
103 hash the string using
104
104
105 :param str_: password to hash
105 :param str_: password to hash
106 """
106 """
107 raise NotImplementedError
107 raise NotImplementedError
108
108
109 def hash_check_with_upgrade(self, password, hashed):
109 def hash_check_with_upgrade(self, password, hashed):
110 """
110 """
111 Returns tuple in which first element is boolean that states that
111 Returns tuple in which first element is boolean that states that
112 given password matches it's hashed version, and the second is new hash
112 given password matches it's hashed version, and the second is new hash
113 of the password, in case this password should be migrated to new
113 of the password, in case this password should be migrated to new
114 cipher.
114 cipher.
115 """
115 """
116 checked_hash = self.hash_check(password, hashed)
116 checked_hash = self.hash_check(password, hashed)
117 return checked_hash, None
117 return checked_hash, None
118
118
119 def hash_check(self, password, hashed):
119 def hash_check(self, password, hashed):
120 """
120 """
121 Checks matching password with it's hashed value.
121 Checks matching password with it's hashed value.
122
122
123 :param password: password
123 :param password: password
124 :param hashed: password in hashed form
124 :param hashed: password in hashed form
125 """
125 """
126 raise NotImplementedError
126 raise NotImplementedError
127
127
128 def _assert_bytes(self, value):
128 def _assert_bytes(self, value):
129 """
129 """
130 Passing in an `unicode` object can lead to hard to detect issues
130 Passing in an `unicode` object can lead to hard to detect issues
131 if passwords contain non-ascii characters. Doing a type check
131 if passwords contain non-ascii characters. Doing a type check
132 during runtime, so that such mistakes are detected early on.
132 during runtime, so that such mistakes are detected early on.
133 """
133 """
134 if not isinstance(value, str):
134 if not isinstance(value, str):
135 raise TypeError(
135 raise TypeError(
136 "Bytestring required as input, got %r." % (value, ))
136 "Bytestring required as input, got %r." % (value, ))
137
137
138
138
139 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
139 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
140 ENC_PREF = ('$2a$10', '$2b$10')
140 ENC_PREF = ('$2a$10', '$2b$10')
141
141
142 def hash_create(self, str_):
142 def hash_create(self, str_):
143 self._assert_bytes(str_)
143 self._assert_bytes(str_)
144 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
144 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
145
145
146 def hash_check_with_upgrade(self, password, hashed):
146 def hash_check_with_upgrade(self, password, hashed):
147 """
147 """
148 Returns tuple in which first element is boolean that states that
148 Returns tuple in which first element is boolean that states that
149 given password matches it's hashed version, and the second is new hash
149 given password matches it's hashed version, and the second is new hash
150 of the password, in case this password should be migrated to new
150 of the password, in case this password should be migrated to new
151 cipher.
151 cipher.
152
152
153 This implements special upgrade logic which works like that:
153 This implements special upgrade logic which works like that:
154 - check if the given password == bcrypted hash, if yes then we
154 - check if the given password == bcrypted hash, if yes then we
155 properly used password and it was already in bcrypt. Proceed
155 properly used password and it was already in bcrypt. Proceed
156 without any changes
156 without any changes
157 - if bcrypt hash check is not working try with sha256. If hash compare
157 - if bcrypt hash check is not working try with sha256. If hash compare
158 is ok, it means we using correct but old hashed password. indicate
158 is ok, it means we using correct but old hashed password. indicate
159 hash change and proceed
159 hash change and proceed
160 """
160 """
161
161
162 new_hash = None
162 new_hash = None
163
163
164 # regular pw check
164 # regular pw check
165 password_match_bcrypt = self.hash_check(password, hashed)
165 password_match_bcrypt = self.hash_check(password, hashed)
166
166
167 # now we want to know if the password was maybe from sha256
167 # now we want to know if the password was maybe from sha256
168 # basically calling _RhodeCodeCryptoSha256().hash_check()
168 # basically calling _RhodeCodeCryptoSha256().hash_check()
169 if not password_match_bcrypt:
169 if not password_match_bcrypt:
170 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
170 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
171 new_hash = self.hash_create(password) # make new bcrypt hash
171 new_hash = self.hash_create(password) # make new bcrypt hash
172 password_match_bcrypt = True
172 password_match_bcrypt = True
173
173
174 return password_match_bcrypt, new_hash
174 return password_match_bcrypt, new_hash
175
175
176 def hash_check(self, password, hashed):
176 def hash_check(self, password, hashed):
177 """
177 """
178 Checks matching password with it's hashed value.
178 Checks matching password with it's hashed value.
179
179
180 :param password: password
180 :param password: password
181 :param hashed: password in hashed form
181 :param hashed: password in hashed form
182 """
182 """
183 self._assert_bytes(password)
183 self._assert_bytes(password)
184 try:
184 try:
185 return bcrypt.hashpw(password, hashed) == hashed
185 return bcrypt.hashpw(password, hashed) == hashed
186 except ValueError as e:
186 except ValueError as e:
187 # we're having a invalid salt here probably, we should not crash
187 # we're having a invalid salt here probably, we should not crash
188 # just return with False as it would be a wrong password.
188 # just return with False as it would be a wrong password.
189 log.debug('Failed to check password hash using bcrypt %s',
189 log.debug('Failed to check password hash using bcrypt %s',
190 safe_str(e))
190 safe_str(e))
191
191
192 return False
192 return False
193
193
194
194
195 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
195 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
196 ENC_PREF = '_'
196 ENC_PREF = '_'
197
197
198 def hash_create(self, str_):
198 def hash_create(self, str_):
199 self._assert_bytes(str_)
199 self._assert_bytes(str_)
200 return hashlib.sha256(str_).hexdigest()
200 return hashlib.sha256(str_).hexdigest()
201
201
202 def hash_check(self, password, hashed):
202 def hash_check(self, password, hashed):
203 """
203 """
204 Checks matching password with it's hashed value.
204 Checks matching password with it's hashed value.
205
205
206 :param password: password
206 :param password: password
207 :param hashed: password in hashed form
207 :param hashed: password in hashed form
208 """
208 """
209 self._assert_bytes(password)
209 self._assert_bytes(password)
210 return hashlib.sha256(password).hexdigest() == hashed
210 return hashlib.sha256(password).hexdigest() == hashed
211
211
212
212
213 class _RhodeCodeCryptoTest(_RhodeCodeCryptoBase):
213 class _RhodeCodeCryptoTest(_RhodeCodeCryptoBase):
214 ENC_PREF = '_'
214 ENC_PREF = '_'
215
215
216 def hash_create(self, str_):
216 def hash_create(self, str_):
217 self._assert_bytes(str_)
217 self._assert_bytes(str_)
218 return sha1(str_)
218 return sha1(str_)
219
219
220 def hash_check(self, password, hashed):
220 def hash_check(self, password, hashed):
221 """
221 """
222 Checks matching password with it's hashed value.
222 Checks matching password with it's hashed value.
223
223
224 :param password: password
224 :param password: password
225 :param hashed: password in hashed form
225 :param hashed: password in hashed form
226 """
226 """
227 self._assert_bytes(password)
227 self._assert_bytes(password)
228 return sha1(password) == hashed
228 return sha1(password) == hashed
229
229
230
230
231 def crypto_backend():
231 def crypto_backend():
232 """
232 """
233 Return the matching crypto backend.
233 Return the matching crypto backend.
234
234
235 Selection is based on if we run tests or not, we pick sha1-test backend to run
235 Selection is based on if we run tests or not, we pick sha1-test backend to run
236 tests faster since BCRYPT is expensive to calculate
236 tests faster since BCRYPT is expensive to calculate
237 """
237 """
238 if rhodecode.is_test:
238 if rhodecode.is_test:
239 RhodeCodeCrypto = _RhodeCodeCryptoTest()
239 RhodeCodeCrypto = _RhodeCodeCryptoTest()
240 else:
240 else:
241 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
241 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
242
242
243 return RhodeCodeCrypto
243 return RhodeCodeCrypto
244
244
245
245
246 def get_crypt_password(password):
246 def get_crypt_password(password):
247 """
247 """
248 Create the hash of `password` with the active crypto backend.
248 Create the hash of `password` with the active crypto backend.
249
249
250 :param password: The cleartext password.
250 :param password: The cleartext password.
251 :type password: unicode
251 :type password: unicode
252 """
252 """
253 password = safe_str(password)
253 password = safe_str(password)
254 return crypto_backend().hash_create(password)
254 return crypto_backend().hash_create(password)
255
255
256
256
257 def check_password(password, hashed):
257 def check_password(password, hashed):
258 """
258 """
259 Check if the value in `password` matches the hash in `hashed`.
259 Check if the value in `password` matches the hash in `hashed`.
260
260
261 :param password: The cleartext password.
261 :param password: The cleartext password.
262 :type password: unicode
262 :type password: unicode
263
263
264 :param hashed: The expected hashed version of the password.
264 :param hashed: The expected hashed version of the password.
265 :type hashed: The hash has to be passed in in text representation.
265 :type hashed: The hash has to be passed in in text representation.
266 """
266 """
267 password = safe_str(password)
267 password = safe_str(password)
268 return crypto_backend().hash_check(password, hashed)
268 return crypto_backend().hash_check(password, hashed)
269
269
270
270
271 def generate_auth_token(data, salt=None):
271 def generate_auth_token(data, salt=None):
272 """
272 """
273 Generates API KEY from given string
273 Generates API KEY from given string
274 """
274 """
275
275
276 if salt is None:
276 if salt is None:
277 salt = os.urandom(16)
277 salt = os.urandom(16)
278 return hashlib.sha1(safe_str(data) + salt).hexdigest()
278 return hashlib.sha1(safe_str(data) + salt).hexdigest()
279
279
280
280
281 def get_came_from(request):
281 def get_came_from(request):
282 """
282 """
283 get query_string+path from request sanitized after removing auth_token
283 get query_string+path from request sanitized after removing auth_token
284 """
284 """
285 _req = request
285 _req = request
286
286
287 path = _req.path
287 path = _req.path
288 if 'auth_token' in _req.GET:
288 if 'auth_token' in _req.GET:
289 # sanitize the request and remove auth_token for redirection
289 # sanitize the request and remove auth_token for redirection
290 _req.GET.pop('auth_token')
290 _req.GET.pop('auth_token')
291 qs = _req.query_string
291 qs = _req.query_string
292 if qs:
292 if qs:
293 path += '?' + qs
293 path += '?' + qs
294
294
295 return path
295 return path
296
296
297
297
298 class CookieStoreWrapper(object):
298 class CookieStoreWrapper(object):
299
299
300 def __init__(self, cookie_store):
300 def __init__(self, cookie_store):
301 self.cookie_store = cookie_store
301 self.cookie_store = cookie_store
302
302
303 def __repr__(self):
303 def __repr__(self):
304 return 'CookieStore<%s>' % (self.cookie_store)
304 return 'CookieStore<%s>' % (self.cookie_store)
305
305
306 def get(self, key, other=None):
306 def get(self, key, other=None):
307 if isinstance(self.cookie_store, dict):
307 if isinstance(self.cookie_store, dict):
308 return self.cookie_store.get(key, other)
308 return self.cookie_store.get(key, other)
309 elif isinstance(self.cookie_store, AuthUser):
309 elif isinstance(self.cookie_store, AuthUser):
310 return self.cookie_store.__dict__.get(key, other)
310 return self.cookie_store.__dict__.get(key, other)
311
311
312
312
313 def _cached_perms_data(user_id, scope, user_is_admin,
313 def _cached_perms_data(user_id, scope, user_is_admin,
314 user_inherit_default_permissions, explicit, algo,
314 user_inherit_default_permissions, explicit, algo,
315 calculate_super_admin):
315 calculate_super_admin):
316
316
317 permissions = PermissionCalculator(
317 permissions = PermissionCalculator(
318 user_id, scope, user_is_admin, user_inherit_default_permissions,
318 user_id, scope, user_is_admin, user_inherit_default_permissions,
319 explicit, algo, calculate_super_admin)
319 explicit, algo, calculate_super_admin)
320 return permissions.calculate()
320 return permissions.calculate()
321
321
322
322
323 class PermOrigin(object):
323 class PermOrigin(object):
324 SUPER_ADMIN = 'superadmin'
324 SUPER_ADMIN = 'superadmin'
325 ARCHIVED = 'archived'
325
326
326 REPO_USER = 'user:%s'
327 REPO_USER = 'user:%s'
327 REPO_USERGROUP = 'usergroup:%s'
328 REPO_USERGROUP = 'usergroup:%s'
328 REPO_OWNER = 'repo.owner'
329 REPO_OWNER = 'repo.owner'
329 REPO_DEFAULT = 'repo.default'
330 REPO_DEFAULT = 'repo.default'
330 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
331 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
331 REPO_PRIVATE = 'repo.private'
332 REPO_PRIVATE = 'repo.private'
332
333
333 REPOGROUP_USER = 'user:%s'
334 REPOGROUP_USER = 'user:%s'
334 REPOGROUP_USERGROUP = 'usergroup:%s'
335 REPOGROUP_USERGROUP = 'usergroup:%s'
335 REPOGROUP_OWNER = 'group.owner'
336 REPOGROUP_OWNER = 'group.owner'
336 REPOGROUP_DEFAULT = 'group.default'
337 REPOGROUP_DEFAULT = 'group.default'
337 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
338 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
338
339
339 USERGROUP_USER = 'user:%s'
340 USERGROUP_USER = 'user:%s'
340 USERGROUP_USERGROUP = 'usergroup:%s'
341 USERGROUP_USERGROUP = 'usergroup:%s'
341 USERGROUP_OWNER = 'usergroup.owner'
342 USERGROUP_OWNER = 'usergroup.owner'
342 USERGROUP_DEFAULT = 'usergroup.default'
343 USERGROUP_DEFAULT = 'usergroup.default'
343 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
344 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
344
345
345
346
346 class PermOriginDict(dict):
347 class PermOriginDict(dict):
347 """
348 """
348 A special dict used for tracking permissions along with their origins.
349 A special dict used for tracking permissions along with their origins.
349
350
350 `__setitem__` has been overridden to expect a tuple(perm, origin)
351 `__setitem__` has been overridden to expect a tuple(perm, origin)
351 `__getitem__` will return only the perm
352 `__getitem__` will return only the perm
352 `.perm_origin_stack` will return the stack of (perm, origin) set per key
353 `.perm_origin_stack` will return the stack of (perm, origin) set per key
353
354
354 >>> perms = PermOriginDict()
355 >>> perms = PermOriginDict()
355 >>> perms['resource'] = 'read', 'default'
356 >>> perms['resource'] = 'read', 'default'
356 >>> perms['resource']
357 >>> perms['resource']
357 'read'
358 'read'
358 >>> perms['resource'] = 'write', 'admin'
359 >>> perms['resource'] = 'write', 'admin'
359 >>> perms['resource']
360 >>> perms['resource']
360 'write'
361 'write'
361 >>> perms.perm_origin_stack
362 >>> perms.perm_origin_stack
362 {'resource': [('read', 'default'), ('write', 'admin')]}
363 {'resource': [('read', 'default'), ('write', 'admin')]}
363 """
364 """
364
365
365 def __init__(self, *args, **kw):
366 def __init__(self, *args, **kw):
366 dict.__init__(self, *args, **kw)
367 dict.__init__(self, *args, **kw)
367 self.perm_origin_stack = collections.OrderedDict()
368 self.perm_origin_stack = collections.OrderedDict()
368
369
369 def __setitem__(self, key, (perm, origin)):
370 def __setitem__(self, key, (perm, origin)):
370 self.perm_origin_stack.setdefault(key, []).append(
371 self.perm_origin_stack.setdefault(key, []).append(
371 (perm, origin))
372 (perm, origin))
372 dict.__setitem__(self, key, perm)
373 dict.__setitem__(self, key, perm)
373
374
374
375
375 class BranchPermOriginDict(PermOriginDict):
376 class BranchPermOriginDict(PermOriginDict):
376 """
377 """
377 Dedicated branch permissions dict, with tracking of patterns and origins.
378 Dedicated branch permissions dict, with tracking of patterns and origins.
378
379
379 >>> perms = BranchPermOriginDict()
380 >>> perms = BranchPermOriginDict()
380 >>> perms['resource'] = '*pattern', 'read', 'default'
381 >>> perms['resource'] = '*pattern', 'read', 'default'
381 >>> perms['resource']
382 >>> perms['resource']
382 {'*pattern': 'read'}
383 {'*pattern': 'read'}
383 >>> perms['resource'] = '*pattern', 'write', 'admin'
384 >>> perms['resource'] = '*pattern', 'write', 'admin'
384 >>> perms['resource']
385 >>> perms['resource']
385 {'*pattern': 'write'}
386 {'*pattern': 'write'}
386 >>> perms.perm_origin_stack
387 >>> perms.perm_origin_stack
387 {'resource': {'*pattern': [('read', 'default'), ('write', 'admin')]}}
388 {'resource': {'*pattern': [('read', 'default'), ('write', 'admin')]}}
388 """
389 """
389 def __setitem__(self, key, (pattern, perm, origin)):
390 def __setitem__(self, key, (pattern, perm, origin)):
390
391
391 self.perm_origin_stack.setdefault(key, {}) \
392 self.perm_origin_stack.setdefault(key, {}) \
392 .setdefault(pattern, []).append((perm, origin))
393 .setdefault(pattern, []).append((perm, origin))
393
394
394 if key in self:
395 if key in self:
395 self[key].__setitem__(pattern, perm)
396 self[key].__setitem__(pattern, perm)
396 else:
397 else:
397 patterns = collections.OrderedDict()
398 patterns = collections.OrderedDict()
398 patterns[pattern] = perm
399 patterns[pattern] = perm
399 dict.__setitem__(self, key, patterns)
400 dict.__setitem__(self, key, patterns)
400
401
401
402
402 class PermissionCalculator(object):
403 class PermissionCalculator(object):
403
404
404 def __init__(
405 def __init__(
405 self, user_id, scope, user_is_admin,
406 self, user_id, scope, user_is_admin,
406 user_inherit_default_permissions, explicit, algo,
407 user_inherit_default_permissions, explicit, algo,
407 calculate_super_admin_as_user=False):
408 calculate_super_admin_as_user=False):
408
409
409 self.user_id = user_id
410 self.user_id = user_id
410 self.user_is_admin = user_is_admin
411 self.user_is_admin = user_is_admin
411 self.inherit_default_permissions = user_inherit_default_permissions
412 self.inherit_default_permissions = user_inherit_default_permissions
412 self.explicit = explicit
413 self.explicit = explicit
413 self.algo = algo
414 self.algo = algo
414 self.calculate_super_admin_as_user = calculate_super_admin_as_user
415 self.calculate_super_admin_as_user = calculate_super_admin_as_user
415
416
416 scope = scope or {}
417 scope = scope or {}
417 self.scope_repo_id = scope.get('repo_id')
418 self.scope_repo_id = scope.get('repo_id')
418 self.scope_repo_group_id = scope.get('repo_group_id')
419 self.scope_repo_group_id = scope.get('repo_group_id')
419 self.scope_user_group_id = scope.get('user_group_id')
420 self.scope_user_group_id = scope.get('user_group_id')
420
421
421 self.default_user_id = User.get_default_user(cache=True).user_id
422 self.default_user_id = User.get_default_user(cache=True).user_id
422
423
423 self.permissions_repositories = PermOriginDict()
424 self.permissions_repositories = PermOriginDict()
424 self.permissions_repository_groups = PermOriginDict()
425 self.permissions_repository_groups = PermOriginDict()
425 self.permissions_user_groups = PermOriginDict()
426 self.permissions_user_groups = PermOriginDict()
426 self.permissions_repository_branches = BranchPermOriginDict()
427 self.permissions_repository_branches = BranchPermOriginDict()
427 self.permissions_global = set()
428 self.permissions_global = set()
428
429
429 self.default_repo_perms = Permission.get_default_repo_perms(
430 self.default_repo_perms = Permission.get_default_repo_perms(
430 self.default_user_id, self.scope_repo_id)
431 self.default_user_id, self.scope_repo_id)
431 self.default_repo_groups_perms = Permission.get_default_group_perms(
432 self.default_repo_groups_perms = Permission.get_default_group_perms(
432 self.default_user_id, self.scope_repo_group_id)
433 self.default_user_id, self.scope_repo_group_id)
433 self.default_user_group_perms = \
434 self.default_user_group_perms = \
434 Permission.get_default_user_group_perms(
435 Permission.get_default_user_group_perms(
435 self.default_user_id, self.scope_user_group_id)
436 self.default_user_id, self.scope_user_group_id)
436
437
437 # default branch perms
438 # default branch perms
438 self.default_branch_repo_perms = \
439 self.default_branch_repo_perms = \
439 Permission.get_default_repo_branch_perms(
440 Permission.get_default_repo_branch_perms(
440 self.default_user_id, self.scope_repo_id)
441 self.default_user_id, self.scope_repo_id)
441
442
442 def calculate(self):
443 def calculate(self):
443 if self.user_is_admin and not self.calculate_super_admin_as_user:
444 if self.user_is_admin and not self.calculate_super_admin_as_user:
444 return self._calculate_admin_permissions()
445 return self._calculate_admin_permissions()
445
446
446 self._calculate_global_default_permissions()
447 self._calculate_global_default_permissions()
447 self._calculate_global_permissions()
448 self._calculate_global_permissions()
448 self._calculate_default_permissions()
449 self._calculate_default_permissions()
449 self._calculate_repository_permissions()
450 self._calculate_repository_permissions()
450 self._calculate_repository_branch_permissions()
451 self._calculate_repository_branch_permissions()
451 self._calculate_repository_group_permissions()
452 self._calculate_repository_group_permissions()
452 self._calculate_user_group_permissions()
453 self._calculate_user_group_permissions()
453 return self._permission_structure()
454 return self._permission_structure()
454
455
455 def _calculate_admin_permissions(self):
456 def _calculate_admin_permissions(self):
456 """
457 """
457 admin user have all default rights for repositories
458 admin user have all default rights for repositories
458 and groups set to admin
459 and groups set to admin
459 """
460 """
460 self.permissions_global.add('hg.admin')
461 self.permissions_global.add('hg.admin')
461 self.permissions_global.add('hg.create.write_on_repogroup.true')
462 self.permissions_global.add('hg.create.write_on_repogroup.true')
462
463
463 # repositories
464 # repositories
464 for perm in self.default_repo_perms:
465 for perm in self.default_repo_perms:
465 r_k = perm.UserRepoToPerm.repository.repo_name
466 r_k = perm.UserRepoToPerm.repository.repo_name
467 archived = perm.UserRepoToPerm.repository.archived
466 p = 'repository.admin'
468 p = 'repository.admin'
467 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN
469 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN
470 # special case for archived repositories, which we block still even for
471 # super admins
472 if archived:
473 p = 'repository.read'
474 self.permissions_repositories[r_k] = p, PermOrigin.ARCHIVED
468
475
469 # repository groups
476 # repository groups
470 for perm in self.default_repo_groups_perms:
477 for perm in self.default_repo_groups_perms:
471 rg_k = perm.UserRepoGroupToPerm.group.group_name
478 rg_k = perm.UserRepoGroupToPerm.group.group_name
472 p = 'group.admin'
479 p = 'group.admin'
473 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN
480 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN
474
481
475 # user groups
482 # user groups
476 for perm in self.default_user_group_perms:
483 for perm in self.default_user_group_perms:
477 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
484 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
478 p = 'usergroup.admin'
485 p = 'usergroup.admin'
479 self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN
486 self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN
480
487
481 # branch permissions
488 # branch permissions
482 # since super-admin also can have custom rule permissions
489 # since super-admin also can have custom rule permissions
483 # we *always* need to calculate those inherited from default, and also explicit
490 # we *always* need to calculate those inherited from default, and also explicit
484 self._calculate_default_permissions_repository_branches(
491 self._calculate_default_permissions_repository_branches(
485 user_inherit_object_permissions=False)
492 user_inherit_object_permissions=False)
486 self._calculate_repository_branch_permissions()
493 self._calculate_repository_branch_permissions()
487
494
488 return self._permission_structure()
495 return self._permission_structure()
489
496
490 def _calculate_global_default_permissions(self):
497 def _calculate_global_default_permissions(self):
491 """
498 """
492 global permissions taken from the default user
499 global permissions taken from the default user
493 """
500 """
494 default_global_perms = UserToPerm.query()\
501 default_global_perms = UserToPerm.query()\
495 .filter(UserToPerm.user_id == self.default_user_id)\
502 .filter(UserToPerm.user_id == self.default_user_id)\
496 .options(joinedload(UserToPerm.permission))
503 .options(joinedload(UserToPerm.permission))
497
504
498 for perm in default_global_perms:
505 for perm in default_global_perms:
499 self.permissions_global.add(perm.permission.permission_name)
506 self.permissions_global.add(perm.permission.permission_name)
500
507
501 if self.user_is_admin:
508 if self.user_is_admin:
502 self.permissions_global.add('hg.admin')
509 self.permissions_global.add('hg.admin')
503 self.permissions_global.add('hg.create.write_on_repogroup.true')
510 self.permissions_global.add('hg.create.write_on_repogroup.true')
504
511
505 def _calculate_global_permissions(self):
512 def _calculate_global_permissions(self):
506 """
513 """
507 Set global system permissions with user permissions or permissions
514 Set global system permissions with user permissions or permissions
508 taken from the user groups of the current user.
515 taken from the user groups of the current user.
509
516
510 The permissions include repo creating, repo group creating, forking
517 The permissions include repo creating, repo group creating, forking
511 etc.
518 etc.
512 """
519 """
513
520
514 # now we read the defined permissions and overwrite what we have set
521 # now we read the defined permissions and overwrite what we have set
515 # before those can be configured from groups or users explicitly.
522 # before those can be configured from groups or users explicitly.
516
523
517 # In case we want to extend this list we should make sure
524 # In case we want to extend this list we should make sure
518 # this is in sync with User.DEFAULT_USER_PERMISSIONS definitions
525 # this is in sync with User.DEFAULT_USER_PERMISSIONS definitions
519 _configurable = frozenset([
526 _configurable = frozenset([
520 'hg.fork.none', 'hg.fork.repository',
527 'hg.fork.none', 'hg.fork.repository',
521 'hg.create.none', 'hg.create.repository',
528 'hg.create.none', 'hg.create.repository',
522 'hg.usergroup.create.false', 'hg.usergroup.create.true',
529 'hg.usergroup.create.false', 'hg.usergroup.create.true',
523 'hg.repogroup.create.false', 'hg.repogroup.create.true',
530 'hg.repogroup.create.false', 'hg.repogroup.create.true',
524 'hg.create.write_on_repogroup.false', 'hg.create.write_on_repogroup.true',
531 'hg.create.write_on_repogroup.false', 'hg.create.write_on_repogroup.true',
525 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
532 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
526 ])
533 ])
527
534
528 # USER GROUPS comes first user group global permissions
535 # USER GROUPS comes first user group global permissions
529 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
536 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
530 .options(joinedload(UserGroupToPerm.permission))\
537 .options(joinedload(UserGroupToPerm.permission))\
531 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
538 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
532 UserGroupMember.users_group_id))\
539 UserGroupMember.users_group_id))\
533 .filter(UserGroupMember.user_id == self.user_id)\
540 .filter(UserGroupMember.user_id == self.user_id)\
534 .order_by(UserGroupToPerm.users_group_id)\
541 .order_by(UserGroupToPerm.users_group_id)\
535 .all()
542 .all()
536
543
537 # need to group here by groups since user can be in more than
544 # need to group here by groups since user can be in more than
538 # one group, so we get all groups
545 # one group, so we get all groups
539 _explicit_grouped_perms = [
546 _explicit_grouped_perms = [
540 [x, list(y)] for x, y in
547 [x, list(y)] for x, y in
541 itertools.groupby(user_perms_from_users_groups,
548 itertools.groupby(user_perms_from_users_groups,
542 lambda _x: _x.users_group)]
549 lambda _x: _x.users_group)]
543
550
544 for gr, perms in _explicit_grouped_perms:
551 for gr, perms in _explicit_grouped_perms:
545 # since user can be in multiple groups iterate over them and
552 # since user can be in multiple groups iterate over them and
546 # select the lowest permissions first (more explicit)
553 # select the lowest permissions first (more explicit)
547 # TODO(marcink): do this^^
554 # TODO(marcink): do this^^
548
555
549 # group doesn't inherit default permissions so we actually set them
556 # group doesn't inherit default permissions so we actually set them
550 if not gr.inherit_default_permissions:
557 if not gr.inherit_default_permissions:
551 # NEED TO IGNORE all previously set configurable permissions
558 # NEED TO IGNORE all previously set configurable permissions
552 # and replace them with explicitly set from this user
559 # and replace them with explicitly set from this user
553 # group permissions
560 # group permissions
554 self.permissions_global = self.permissions_global.difference(
561 self.permissions_global = self.permissions_global.difference(
555 _configurable)
562 _configurable)
556 for perm in perms:
563 for perm in perms:
557 self.permissions_global.add(perm.permission.permission_name)
564 self.permissions_global.add(perm.permission.permission_name)
558
565
559 # user explicit global permissions
566 # user explicit global permissions
560 user_perms = Session().query(UserToPerm)\
567 user_perms = Session().query(UserToPerm)\
561 .options(joinedload(UserToPerm.permission))\
568 .options(joinedload(UserToPerm.permission))\
562 .filter(UserToPerm.user_id == self.user_id).all()
569 .filter(UserToPerm.user_id == self.user_id).all()
563
570
564 if not self.inherit_default_permissions:
571 if not self.inherit_default_permissions:
565 # NEED TO IGNORE all configurable permissions and
572 # NEED TO IGNORE all configurable permissions and
566 # replace them with explicitly set from this user permissions
573 # replace them with explicitly set from this user permissions
567 self.permissions_global = self.permissions_global.difference(
574 self.permissions_global = self.permissions_global.difference(
568 _configurable)
575 _configurable)
569 for perm in user_perms:
576 for perm in user_perms:
570 self.permissions_global.add(perm.permission.permission_name)
577 self.permissions_global.add(perm.permission.permission_name)
571
578
572 def _calculate_default_permissions_repositories(self, user_inherit_object_permissions):
579 def _calculate_default_permissions_repositories(self, user_inherit_object_permissions):
573 for perm in self.default_repo_perms:
580 for perm in self.default_repo_perms:
574 r_k = perm.UserRepoToPerm.repository.repo_name
581 r_k = perm.UserRepoToPerm.repository.repo_name
582 archived = perm.UserRepoToPerm.repository.archived
575 p = perm.Permission.permission_name
583 p = perm.Permission.permission_name
576 o = PermOrigin.REPO_DEFAULT
584 o = PermOrigin.REPO_DEFAULT
577 self.permissions_repositories[r_k] = p, o
585 self.permissions_repositories[r_k] = p, o
578
586
579 # if we decide this user isn't inheriting permissions from
587 # if we decide this user isn't inheriting permissions from
580 # default user we set him to .none so only explicit
588 # default user we set him to .none so only explicit
581 # permissions work
589 # permissions work
582 if not user_inherit_object_permissions:
590 if not user_inherit_object_permissions:
583 p = 'repository.none'
591 p = 'repository.none'
584 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
592 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
585 self.permissions_repositories[r_k] = p, o
593 self.permissions_repositories[r_k] = p, o
586
594
587 if perm.Repository.private and not (
595 if perm.Repository.private and not (
588 perm.Repository.user_id == self.user_id):
596 perm.Repository.user_id == self.user_id):
589 # disable defaults for private repos,
597 # disable defaults for private repos,
590 p = 'repository.none'
598 p = 'repository.none'
591 o = PermOrigin.REPO_PRIVATE
599 o = PermOrigin.REPO_PRIVATE
592 self.permissions_repositories[r_k] = p, o
600 self.permissions_repositories[r_k] = p, o
593
601
594 elif perm.Repository.user_id == self.user_id:
602 elif perm.Repository.user_id == self.user_id:
595 # set admin if owner
603 # set admin if owner
596 p = 'repository.admin'
604 p = 'repository.admin'
597 o = PermOrigin.REPO_OWNER
605 o = PermOrigin.REPO_OWNER
598 self.permissions_repositories[r_k] = p, o
606 self.permissions_repositories[r_k] = p, o
599
607
600 if self.user_is_admin:
608 if self.user_is_admin:
601 p = 'repository.admin'
609 p = 'repository.admin'
602 o = PermOrigin.SUPER_ADMIN
610 o = PermOrigin.SUPER_ADMIN
603 self.permissions_repositories[r_k] = p, o
611 self.permissions_repositories[r_k] = p, o
604
612
613 # finally in case of archived repositories, we downgrade higher
614 # permissions to read
615 if archived:
616 current_perm = self.permissions_repositories[r_k]
617 if current_perm in ['repository.write', 'repository.admin']:
618 p = 'repository.read'
619 o = PermOrigin.ARCHIVED
620 self.permissions_repositories[r_k] = p, o
621
605 def _calculate_default_permissions_repository_branches(self, user_inherit_object_permissions):
622 def _calculate_default_permissions_repository_branches(self, user_inherit_object_permissions):
606 for perm in self.default_branch_repo_perms:
623 for perm in self.default_branch_repo_perms:
607
624
608 r_k = perm.UserRepoToPerm.repository.repo_name
625 r_k = perm.UserRepoToPerm.repository.repo_name
609 p = perm.Permission.permission_name
626 p = perm.Permission.permission_name
610 pattern = perm.UserToRepoBranchPermission.branch_pattern
627 pattern = perm.UserToRepoBranchPermission.branch_pattern
611 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
628 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
612
629
613 if not self.explicit:
630 if not self.explicit:
614 # TODO(marcink): fix this for multiple entries
631 # TODO(marcink): fix this for multiple entries
615 cur_perm = self.permissions_repository_branches.get(r_k) or 'branch.none'
632 cur_perm = self.permissions_repository_branches.get(r_k) or 'branch.none'
616 p = self._choose_permission(p, cur_perm)
633 p = self._choose_permission(p, cur_perm)
617
634
618 # NOTE(marcink): register all pattern/perm instances in this
635 # NOTE(marcink): register all pattern/perm instances in this
619 # special dict that aggregates entries
636 # special dict that aggregates entries
620 self.permissions_repository_branches[r_k] = pattern, p, o
637 self.permissions_repository_branches[r_k] = pattern, p, o
621
638
622 def _calculate_default_permissions_repository_groups(self, user_inherit_object_permissions):
639 def _calculate_default_permissions_repository_groups(self, user_inherit_object_permissions):
623 for perm in self.default_repo_groups_perms:
640 for perm in self.default_repo_groups_perms:
624 rg_k = perm.UserRepoGroupToPerm.group.group_name
641 rg_k = perm.UserRepoGroupToPerm.group.group_name
625 p = perm.Permission.permission_name
642 p = perm.Permission.permission_name
626 o = PermOrigin.REPOGROUP_DEFAULT
643 o = PermOrigin.REPOGROUP_DEFAULT
627 self.permissions_repository_groups[rg_k] = p, o
644 self.permissions_repository_groups[rg_k] = p, o
628
645
629 # if we decide this user isn't inheriting permissions from default
646 # if we decide this user isn't inheriting permissions from default
630 # user we set him to .none so only explicit permissions work
647 # user we set him to .none so only explicit permissions work
631 if not user_inherit_object_permissions:
648 if not user_inherit_object_permissions:
632 p = 'group.none'
649 p = 'group.none'
633 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
650 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
634 self.permissions_repository_groups[rg_k] = p, o
651 self.permissions_repository_groups[rg_k] = p, o
635
652
636 if perm.RepoGroup.user_id == self.user_id:
653 if perm.RepoGroup.user_id == self.user_id:
637 # set admin if owner
654 # set admin if owner
638 p = 'group.admin'
655 p = 'group.admin'
639 o = PermOrigin.REPOGROUP_OWNER
656 o = PermOrigin.REPOGROUP_OWNER
640 self.permissions_repository_groups[rg_k] = p, o
657 self.permissions_repository_groups[rg_k] = p, o
641
658
642 if self.user_is_admin:
659 if self.user_is_admin:
643 p = 'group.admin'
660 p = 'group.admin'
644 o = PermOrigin.SUPER_ADMIN
661 o = PermOrigin.SUPER_ADMIN
645 self.permissions_repository_groups[rg_k] = p, o
662 self.permissions_repository_groups[rg_k] = p, o
646
663
647 def _calculate_default_permissions_user_groups(self, user_inherit_object_permissions):
664 def _calculate_default_permissions_user_groups(self, user_inherit_object_permissions):
648 for perm in self.default_user_group_perms:
665 for perm in self.default_user_group_perms:
649 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
666 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
650 p = perm.Permission.permission_name
667 p = perm.Permission.permission_name
651 o = PermOrigin.USERGROUP_DEFAULT
668 o = PermOrigin.USERGROUP_DEFAULT
652 self.permissions_user_groups[u_k] = p, o
669 self.permissions_user_groups[u_k] = p, o
653
670
654 # if we decide this user isn't inheriting permissions from default
671 # if we decide this user isn't inheriting permissions from default
655 # user we set him to .none so only explicit permissions work
672 # user we set him to .none so only explicit permissions work
656 if not user_inherit_object_permissions:
673 if not user_inherit_object_permissions:
657 p = 'usergroup.none'
674 p = 'usergroup.none'
658 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
675 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
659 self.permissions_user_groups[u_k] = p, o
676 self.permissions_user_groups[u_k] = p, o
660
677
661 if perm.UserGroup.user_id == self.user_id:
678 if perm.UserGroup.user_id == self.user_id:
662 # set admin if owner
679 # set admin if owner
663 p = 'usergroup.admin'
680 p = 'usergroup.admin'
664 o = PermOrigin.USERGROUP_OWNER
681 o = PermOrigin.USERGROUP_OWNER
665 self.permissions_user_groups[u_k] = p, o
682 self.permissions_user_groups[u_k] = p, o
666
683
667 if self.user_is_admin:
684 if self.user_is_admin:
668 p = 'usergroup.admin'
685 p = 'usergroup.admin'
669 o = PermOrigin.SUPER_ADMIN
686 o = PermOrigin.SUPER_ADMIN
670 self.permissions_user_groups[u_k] = p, o
687 self.permissions_user_groups[u_k] = p, o
671
688
672 def _calculate_default_permissions(self):
689 def _calculate_default_permissions(self):
673 """
690 """
674 Set default user permissions for repositories, repository branches,
691 Set default user permissions for repositories, repository branches,
675 repository groups, user groups taken from the default user.
692 repository groups, user groups taken from the default user.
676
693
677 Calculate inheritance of object permissions based on what we have now
694 Calculate inheritance of object permissions based on what we have now
678 in GLOBAL permissions. We check if .false is in GLOBAL since this is
695 in GLOBAL permissions. We check if .false is in GLOBAL since this is
679 explicitly set. Inherit is the opposite of .false being there.
696 explicitly set. Inherit is the opposite of .false being there.
680
697
681 .. note::
698 .. note::
682
699
683 the syntax is little bit odd but what we need to check here is
700 the syntax is little bit odd but what we need to check here is
684 the opposite of .false permission being in the list so even for
701 the opposite of .false permission being in the list so even for
685 inconsistent state when both .true/.false is there
702 inconsistent state when both .true/.false is there
686 .false is more important
703 .false is more important
687
704
688 """
705 """
689 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
706 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
690 in self.permissions_global)
707 in self.permissions_global)
691
708
692 # default permissions inherited from `default` user permissions
709 # default permissions inherited from `default` user permissions
693 self._calculate_default_permissions_repositories(
710 self._calculate_default_permissions_repositories(
694 user_inherit_object_permissions)
711 user_inherit_object_permissions)
695
712
696 self._calculate_default_permissions_repository_branches(
713 self._calculate_default_permissions_repository_branches(
697 user_inherit_object_permissions)
714 user_inherit_object_permissions)
698
715
699 self._calculate_default_permissions_repository_groups(
716 self._calculate_default_permissions_repository_groups(
700 user_inherit_object_permissions)
717 user_inherit_object_permissions)
701
718
702 self._calculate_default_permissions_user_groups(
719 self._calculate_default_permissions_user_groups(
703 user_inherit_object_permissions)
720 user_inherit_object_permissions)
704
721
705 def _calculate_repository_permissions(self):
722 def _calculate_repository_permissions(self):
706 """
723 """
707 Repository permissions for the current user.
724 Repository permissions for the current user.
708
725
709 Check if the user is part of user groups for this repository and
726 Check if the user is part of user groups for this repository and
710 fill in the permission from it. `_choose_permission` decides of which
727 fill in the permission from it. `_choose_permission` decides of which
711 permission should be selected based on selected method.
728 permission should be selected based on selected method.
712 """
729 """
713
730
714 # user group for repositories permissions
731 # user group for repositories permissions
715 user_repo_perms_from_user_group = Permission\
732 user_repo_perms_from_user_group = Permission\
716 .get_default_repo_perms_from_user_group(
733 .get_default_repo_perms_from_user_group(
717 self.user_id, self.scope_repo_id)
734 self.user_id, self.scope_repo_id)
718
735
719 multiple_counter = collections.defaultdict(int)
736 multiple_counter = collections.defaultdict(int)
720 for perm in user_repo_perms_from_user_group:
737 for perm in user_repo_perms_from_user_group:
721 r_k = perm.UserGroupRepoToPerm.repository.repo_name
738 r_k = perm.UserGroupRepoToPerm.repository.repo_name
722 multiple_counter[r_k] += 1
739 multiple_counter[r_k] += 1
723 p = perm.Permission.permission_name
740 p = perm.Permission.permission_name
724 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
741 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
725 .users_group.users_group_name
742 .users_group.users_group_name
726
743
727 if multiple_counter[r_k] > 1:
744 if multiple_counter[r_k] > 1:
728 cur_perm = self.permissions_repositories[r_k]
745 cur_perm = self.permissions_repositories[r_k]
729 p = self._choose_permission(p, cur_perm)
746 p = self._choose_permission(p, cur_perm)
730
747
731 self.permissions_repositories[r_k] = p, o
748 self.permissions_repositories[r_k] = p, o
732
749
733 if perm.Repository.user_id == self.user_id:
750 if perm.Repository.user_id == self.user_id:
734 # set admin if owner
751 # set admin if owner
735 p = 'repository.admin'
752 p = 'repository.admin'
736 o = PermOrigin.REPO_OWNER
753 o = PermOrigin.REPO_OWNER
737 self.permissions_repositories[r_k] = p, o
754 self.permissions_repositories[r_k] = p, o
738
755
739 if self.user_is_admin:
756 if self.user_is_admin:
740 p = 'repository.admin'
757 p = 'repository.admin'
741 o = PermOrigin.SUPER_ADMIN
758 o = PermOrigin.SUPER_ADMIN
742 self.permissions_repositories[r_k] = p, o
759 self.permissions_repositories[r_k] = p, o
743
760
744 # user explicit permissions for repositories, overrides any specified
761 # user explicit permissions for repositories, overrides any specified
745 # by the group permission
762 # by the group permission
746 user_repo_perms = Permission.get_default_repo_perms(
763 user_repo_perms = Permission.get_default_repo_perms(
747 self.user_id, self.scope_repo_id)
764 self.user_id, self.scope_repo_id)
748 for perm in user_repo_perms:
765 for perm in user_repo_perms:
749 r_k = perm.UserRepoToPerm.repository.repo_name
766 r_k = perm.UserRepoToPerm.repository.repo_name
750 p = perm.Permission.permission_name
767 p = perm.Permission.permission_name
751 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
768 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
752
769
753 if not self.explicit:
770 if not self.explicit:
754 cur_perm = self.permissions_repositories.get(
771 cur_perm = self.permissions_repositories.get(
755 r_k, 'repository.none')
772 r_k, 'repository.none')
756 p = self._choose_permission(p, cur_perm)
773 p = self._choose_permission(p, cur_perm)
757
774
758 self.permissions_repositories[r_k] = p, o
775 self.permissions_repositories[r_k] = p, o
759
776
760 if perm.Repository.user_id == self.user_id:
777 if perm.Repository.user_id == self.user_id:
761 # set admin if owner
778 # set admin if owner
762 p = 'repository.admin'
779 p = 'repository.admin'
763 o = PermOrigin.REPO_OWNER
780 o = PermOrigin.REPO_OWNER
764 self.permissions_repositories[r_k] = p, o
781 self.permissions_repositories[r_k] = p, o
765
782
766 if self.user_is_admin:
783 if self.user_is_admin:
767 p = 'repository.admin'
784 p = 'repository.admin'
768 o = PermOrigin.SUPER_ADMIN
785 o = PermOrigin.SUPER_ADMIN
769 self.permissions_repositories[r_k] = p, o
786 self.permissions_repositories[r_k] = p, o
770
787
771 def _calculate_repository_branch_permissions(self):
788 def _calculate_repository_branch_permissions(self):
772 # user group for repositories permissions
789 # user group for repositories permissions
773 user_repo_branch_perms_from_user_group = Permission\
790 user_repo_branch_perms_from_user_group = Permission\
774 .get_default_repo_branch_perms_from_user_group(
791 .get_default_repo_branch_perms_from_user_group(
775 self.user_id, self.scope_repo_id)
792 self.user_id, self.scope_repo_id)
776
793
777 multiple_counter = collections.defaultdict(int)
794 multiple_counter = collections.defaultdict(int)
778 for perm in user_repo_branch_perms_from_user_group:
795 for perm in user_repo_branch_perms_from_user_group:
779 r_k = perm.UserGroupRepoToPerm.repository.repo_name
796 r_k = perm.UserGroupRepoToPerm.repository.repo_name
780 p = perm.Permission.permission_name
797 p = perm.Permission.permission_name
781 pattern = perm.UserGroupToRepoBranchPermission.branch_pattern
798 pattern = perm.UserGroupToRepoBranchPermission.branch_pattern
782 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
799 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
783 .users_group.users_group_name
800 .users_group.users_group_name
784
801
785 multiple_counter[r_k] += 1
802 multiple_counter[r_k] += 1
786 if multiple_counter[r_k] > 1:
803 if multiple_counter[r_k] > 1:
787 # TODO(marcink): fix this for multi branch support, and multiple entries
804 # TODO(marcink): fix this for multi branch support, and multiple entries
788 cur_perm = self.permissions_repository_branches[r_k]
805 cur_perm = self.permissions_repository_branches[r_k]
789 p = self._choose_permission(p, cur_perm)
806 p = self._choose_permission(p, cur_perm)
790
807
791 self.permissions_repository_branches[r_k] = pattern, p, o
808 self.permissions_repository_branches[r_k] = pattern, p, o
792
809
793 # user explicit branch permissions for repositories, overrides
810 # user explicit branch permissions for repositories, overrides
794 # any specified by the group permission
811 # any specified by the group permission
795 user_repo_branch_perms = Permission.get_default_repo_branch_perms(
812 user_repo_branch_perms = Permission.get_default_repo_branch_perms(
796 self.user_id, self.scope_repo_id)
813 self.user_id, self.scope_repo_id)
797
814
798 for perm in user_repo_branch_perms:
815 for perm in user_repo_branch_perms:
799
816
800 r_k = perm.UserRepoToPerm.repository.repo_name
817 r_k = perm.UserRepoToPerm.repository.repo_name
801 p = perm.Permission.permission_name
818 p = perm.Permission.permission_name
802 pattern = perm.UserToRepoBranchPermission.branch_pattern
819 pattern = perm.UserToRepoBranchPermission.branch_pattern
803 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
820 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
804
821
805 if not self.explicit:
822 if not self.explicit:
806 # TODO(marcink): fix this for multiple entries
823 # TODO(marcink): fix this for multiple entries
807 cur_perm = self.permissions_repository_branches.get(r_k) or 'branch.none'
824 cur_perm = self.permissions_repository_branches.get(r_k) or 'branch.none'
808 p = self._choose_permission(p, cur_perm)
825 p = self._choose_permission(p, cur_perm)
809
826
810 # NOTE(marcink): register all pattern/perm instances in this
827 # NOTE(marcink): register all pattern/perm instances in this
811 # special dict that aggregates entries
828 # special dict that aggregates entries
812 self.permissions_repository_branches[r_k] = pattern, p, o
829 self.permissions_repository_branches[r_k] = pattern, p, o
813
830
814 def _calculate_repository_group_permissions(self):
831 def _calculate_repository_group_permissions(self):
815 """
832 """
816 Repository group permissions for the current user.
833 Repository group permissions for the current user.
817
834
818 Check if the user is part of user groups for repository groups and
835 Check if the user is part of user groups for repository groups and
819 fill in the permissions from it. `_choose_permission` decides of which
836 fill in the permissions from it. `_choose_permission` decides of which
820 permission should be selected based on selected method.
837 permission should be selected based on selected method.
821 """
838 """
822 # user group for repo groups permissions
839 # user group for repo groups permissions
823 user_repo_group_perms_from_user_group = Permission\
840 user_repo_group_perms_from_user_group = Permission\
824 .get_default_group_perms_from_user_group(
841 .get_default_group_perms_from_user_group(
825 self.user_id, self.scope_repo_group_id)
842 self.user_id, self.scope_repo_group_id)
826
843
827 multiple_counter = collections.defaultdict(int)
844 multiple_counter = collections.defaultdict(int)
828 for perm in user_repo_group_perms_from_user_group:
845 for perm in user_repo_group_perms_from_user_group:
829 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
846 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
830 multiple_counter[rg_k] += 1
847 multiple_counter[rg_k] += 1
831 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
848 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
832 .users_group.users_group_name
849 .users_group.users_group_name
833 p = perm.Permission.permission_name
850 p = perm.Permission.permission_name
834
851
835 if multiple_counter[rg_k] > 1:
852 if multiple_counter[rg_k] > 1:
836 cur_perm = self.permissions_repository_groups[rg_k]
853 cur_perm = self.permissions_repository_groups[rg_k]
837 p = self._choose_permission(p, cur_perm)
854 p = self._choose_permission(p, cur_perm)
838 self.permissions_repository_groups[rg_k] = p, o
855 self.permissions_repository_groups[rg_k] = p, o
839
856
840 if perm.RepoGroup.user_id == self.user_id:
857 if perm.RepoGroup.user_id == self.user_id:
841 # set admin if owner, even for member of other user group
858 # set admin if owner, even for member of other user group
842 p = 'group.admin'
859 p = 'group.admin'
843 o = PermOrigin.REPOGROUP_OWNER
860 o = PermOrigin.REPOGROUP_OWNER
844 self.permissions_repository_groups[rg_k] = p, o
861 self.permissions_repository_groups[rg_k] = p, o
845
862
846 if self.user_is_admin:
863 if self.user_is_admin:
847 p = 'group.admin'
864 p = 'group.admin'
848 o = PermOrigin.SUPER_ADMIN
865 o = PermOrigin.SUPER_ADMIN
849 self.permissions_repository_groups[rg_k] = p, o
866 self.permissions_repository_groups[rg_k] = p, o
850
867
851 # user explicit permissions for repository groups
868 # user explicit permissions for repository groups
852 user_repo_groups_perms = Permission.get_default_group_perms(
869 user_repo_groups_perms = Permission.get_default_group_perms(
853 self.user_id, self.scope_repo_group_id)
870 self.user_id, self.scope_repo_group_id)
854 for perm in user_repo_groups_perms:
871 for perm in user_repo_groups_perms:
855 rg_k = perm.UserRepoGroupToPerm.group.group_name
872 rg_k = perm.UserRepoGroupToPerm.group.group_name
856 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
873 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
857 .user.username
874 .user.username
858 p = perm.Permission.permission_name
875 p = perm.Permission.permission_name
859
876
860 if not self.explicit:
877 if not self.explicit:
861 cur_perm = self.permissions_repository_groups.get(
878 cur_perm = self.permissions_repository_groups.get(
862 rg_k, 'group.none')
879 rg_k, 'group.none')
863 p = self._choose_permission(p, cur_perm)
880 p = self._choose_permission(p, cur_perm)
864
881
865 self.permissions_repository_groups[rg_k] = p, o
882 self.permissions_repository_groups[rg_k] = p, o
866
883
867 if perm.RepoGroup.user_id == self.user_id:
884 if perm.RepoGroup.user_id == self.user_id:
868 # set admin if owner
885 # set admin if owner
869 p = 'group.admin'
886 p = 'group.admin'
870 o = PermOrigin.REPOGROUP_OWNER
887 o = PermOrigin.REPOGROUP_OWNER
871 self.permissions_repository_groups[rg_k] = p, o
888 self.permissions_repository_groups[rg_k] = p, o
872
889
873 if self.user_is_admin:
890 if self.user_is_admin:
874 p = 'group.admin'
891 p = 'group.admin'
875 o = PermOrigin.SUPER_ADMIN
892 o = PermOrigin.SUPER_ADMIN
876 self.permissions_repository_groups[rg_k] = p, o
893 self.permissions_repository_groups[rg_k] = p, o
877
894
878 def _calculate_user_group_permissions(self):
895 def _calculate_user_group_permissions(self):
879 """
896 """
880 User group permissions for the current user.
897 User group permissions for the current user.
881 """
898 """
882 # user group for user group permissions
899 # user group for user group permissions
883 user_group_from_user_group = Permission\
900 user_group_from_user_group = Permission\
884 .get_default_user_group_perms_from_user_group(
901 .get_default_user_group_perms_from_user_group(
885 self.user_id, self.scope_user_group_id)
902 self.user_id, self.scope_user_group_id)
886
903
887 multiple_counter = collections.defaultdict(int)
904 multiple_counter = collections.defaultdict(int)
888 for perm in user_group_from_user_group:
905 for perm in user_group_from_user_group:
889 ug_k = perm.UserGroupUserGroupToPerm\
906 ug_k = perm.UserGroupUserGroupToPerm\
890 .target_user_group.users_group_name
907 .target_user_group.users_group_name
891 multiple_counter[ug_k] += 1
908 multiple_counter[ug_k] += 1
892 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
909 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
893 .user_group.users_group_name
910 .user_group.users_group_name
894 p = perm.Permission.permission_name
911 p = perm.Permission.permission_name
895
912
896 if multiple_counter[ug_k] > 1:
913 if multiple_counter[ug_k] > 1:
897 cur_perm = self.permissions_user_groups[ug_k]
914 cur_perm = self.permissions_user_groups[ug_k]
898 p = self._choose_permission(p, cur_perm)
915 p = self._choose_permission(p, cur_perm)
899
916
900 self.permissions_user_groups[ug_k] = p, o
917 self.permissions_user_groups[ug_k] = p, o
901
918
902 if perm.UserGroup.user_id == self.user_id:
919 if perm.UserGroup.user_id == self.user_id:
903 # set admin if owner, even for member of other user group
920 # set admin if owner, even for member of other user group
904 p = 'usergroup.admin'
921 p = 'usergroup.admin'
905 o = PermOrigin.USERGROUP_OWNER
922 o = PermOrigin.USERGROUP_OWNER
906 self.permissions_user_groups[ug_k] = p, o
923 self.permissions_user_groups[ug_k] = p, o
907
924
908 if self.user_is_admin:
925 if self.user_is_admin:
909 p = 'usergroup.admin'
926 p = 'usergroup.admin'
910 o = PermOrigin.SUPER_ADMIN
927 o = PermOrigin.SUPER_ADMIN
911 self.permissions_user_groups[ug_k] = p, o
928 self.permissions_user_groups[ug_k] = p, o
912
929
913 # user explicit permission for user groups
930 # user explicit permission for user groups
914 user_user_groups_perms = Permission.get_default_user_group_perms(
931 user_user_groups_perms = Permission.get_default_user_group_perms(
915 self.user_id, self.scope_user_group_id)
932 self.user_id, self.scope_user_group_id)
916 for perm in user_user_groups_perms:
933 for perm in user_user_groups_perms:
917 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
934 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
918 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
935 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
919 .user.username
936 .user.username
920 p = perm.Permission.permission_name
937 p = perm.Permission.permission_name
921
938
922 if not self.explicit:
939 if not self.explicit:
923 cur_perm = self.permissions_user_groups.get(
940 cur_perm = self.permissions_user_groups.get(
924 ug_k, 'usergroup.none')
941 ug_k, 'usergroup.none')
925 p = self._choose_permission(p, cur_perm)
942 p = self._choose_permission(p, cur_perm)
926
943
927 self.permissions_user_groups[ug_k] = p, o
944 self.permissions_user_groups[ug_k] = p, o
928
945
929 if perm.UserGroup.user_id == self.user_id:
946 if perm.UserGroup.user_id == self.user_id:
930 # set admin if owner
947 # set admin if owner
931 p = 'usergroup.admin'
948 p = 'usergroup.admin'
932 o = PermOrigin.USERGROUP_OWNER
949 o = PermOrigin.USERGROUP_OWNER
933 self.permissions_user_groups[ug_k] = p, o
950 self.permissions_user_groups[ug_k] = p, o
934
951
935 if self.user_is_admin:
952 if self.user_is_admin:
936 p = 'usergroup.admin'
953 p = 'usergroup.admin'
937 o = PermOrigin.SUPER_ADMIN
954 o = PermOrigin.SUPER_ADMIN
938 self.permissions_user_groups[ug_k] = p, o
955 self.permissions_user_groups[ug_k] = p, o
939
956
940 def _choose_permission(self, new_perm, cur_perm):
957 def _choose_permission(self, new_perm, cur_perm):
941 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
958 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
942 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
959 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
943 if self.algo == 'higherwin':
960 if self.algo == 'higherwin':
944 if new_perm_val > cur_perm_val:
961 if new_perm_val > cur_perm_val:
945 return new_perm
962 return new_perm
946 return cur_perm
963 return cur_perm
947 elif self.algo == 'lowerwin':
964 elif self.algo == 'lowerwin':
948 if new_perm_val < cur_perm_val:
965 if new_perm_val < cur_perm_val:
949 return new_perm
966 return new_perm
950 return cur_perm
967 return cur_perm
951
968
952 def _permission_structure(self):
969 def _permission_structure(self):
953 return {
970 return {
954 'global': self.permissions_global,
971 'global': self.permissions_global,
955 'repositories': self.permissions_repositories,
972 'repositories': self.permissions_repositories,
956 'repository_branches': self.permissions_repository_branches,
973 'repository_branches': self.permissions_repository_branches,
957 'repositories_groups': self.permissions_repository_groups,
974 'repositories_groups': self.permissions_repository_groups,
958 'user_groups': self.permissions_user_groups,
975 'user_groups': self.permissions_user_groups,
959 }
976 }
960
977
961
978
962 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
979 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
963 """
980 """
964 Check if given controller_name is in whitelist of auth token access
981 Check if given controller_name is in whitelist of auth token access
965 """
982 """
966 if not whitelist:
983 if not whitelist:
967 from rhodecode import CONFIG
984 from rhodecode import CONFIG
968 whitelist = aslist(
985 whitelist = aslist(
969 CONFIG.get('api_access_controllers_whitelist'), sep=',')
986 CONFIG.get('api_access_controllers_whitelist'), sep=',')
970 # backward compat translation
987 # backward compat translation
971 compat = {
988 compat = {
972 # old controller, new VIEW
989 # old controller, new VIEW
973 'ChangesetController:*': 'RepoCommitsView:*',
990 'ChangesetController:*': 'RepoCommitsView:*',
974 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
991 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
975 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
992 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
976 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
993 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
977 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
994 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
978 'GistsController:*': 'GistView:*',
995 'GistsController:*': 'GistView:*',
979 }
996 }
980
997
981 log.debug(
998 log.debug(
982 'Allowed views for AUTH TOKEN access: %s', whitelist)
999 'Allowed views for AUTH TOKEN access: %s', whitelist)
983 auth_token_access_valid = False
1000 auth_token_access_valid = False
984
1001
985 for entry in whitelist:
1002 for entry in whitelist:
986 token_match = True
1003 token_match = True
987 if entry in compat:
1004 if entry in compat:
988 # translate from old Controllers to Pyramid Views
1005 # translate from old Controllers to Pyramid Views
989 entry = compat[entry]
1006 entry = compat[entry]
990
1007
991 if '@' in entry:
1008 if '@' in entry:
992 # specific AuthToken
1009 # specific AuthToken
993 entry, allowed_token = entry.split('@', 1)
1010 entry, allowed_token = entry.split('@', 1)
994 token_match = auth_token == allowed_token
1011 token_match = auth_token == allowed_token
995
1012
996 if fnmatch.fnmatch(view_name, entry) and token_match:
1013 if fnmatch.fnmatch(view_name, entry) and token_match:
997 auth_token_access_valid = True
1014 auth_token_access_valid = True
998 break
1015 break
999
1016
1000 if auth_token_access_valid:
1017 if auth_token_access_valid:
1001 log.debug('view: `%s` matches entry in whitelist: %s',
1018 log.debug('view: `%s` matches entry in whitelist: %s',
1002 view_name, whitelist)
1019 view_name, whitelist)
1003
1020
1004 else:
1021 else:
1005 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
1022 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
1006 % (view_name, whitelist))
1023 % (view_name, whitelist))
1007 if auth_token:
1024 if auth_token:
1008 # if we use auth token key and don't have access it's a warning
1025 # if we use auth token key and don't have access it's a warning
1009 log.warning(msg)
1026 log.warning(msg)
1010 else:
1027 else:
1011 log.debug(msg)
1028 log.debug(msg)
1012
1029
1013 return auth_token_access_valid
1030 return auth_token_access_valid
1014
1031
1015
1032
1016 class AuthUser(object):
1033 class AuthUser(object):
1017 """
1034 """
1018 A simple object that handles all attributes of user in RhodeCode
1035 A simple object that handles all attributes of user in RhodeCode
1019
1036
1020 It does lookup based on API key,given user, or user present in session
1037 It does lookup based on API key,given user, or user present in session
1021 Then it fills all required information for such user. It also checks if
1038 Then it fills all required information for such user. It also checks if
1022 anonymous access is enabled and if so, it returns default user as logged in
1039 anonymous access is enabled and if so, it returns default user as logged in
1023 """
1040 """
1024 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
1041 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
1025
1042
1026 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
1043 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
1027
1044
1028 self.user_id = user_id
1045 self.user_id = user_id
1029 self._api_key = api_key
1046 self._api_key = api_key
1030
1047
1031 self.api_key = None
1048 self.api_key = None
1032 self.username = username
1049 self.username = username
1033 self.ip_addr = ip_addr
1050 self.ip_addr = ip_addr
1034 self.name = ''
1051 self.name = ''
1035 self.lastname = ''
1052 self.lastname = ''
1036 self.first_name = ''
1053 self.first_name = ''
1037 self.last_name = ''
1054 self.last_name = ''
1038 self.email = ''
1055 self.email = ''
1039 self.is_authenticated = False
1056 self.is_authenticated = False
1040 self.admin = False
1057 self.admin = False
1041 self.inherit_default_permissions = False
1058 self.inherit_default_permissions = False
1042 self.password = ''
1059 self.password = ''
1043
1060
1044 self.anonymous_user = None # propagated on propagate_data
1061 self.anonymous_user = None # propagated on propagate_data
1045 self.propagate_data()
1062 self.propagate_data()
1046 self._instance = None
1063 self._instance = None
1047 self._permissions_scoped_cache = {} # used to bind scoped calculation
1064 self._permissions_scoped_cache = {} # used to bind scoped calculation
1048
1065
1049 @LazyProperty
1066 @LazyProperty
1050 def permissions(self):
1067 def permissions(self):
1051 return self.get_perms(user=self, cache=None)
1068 return self.get_perms(user=self, cache=None)
1052
1069
1053 @LazyProperty
1070 @LazyProperty
1054 def permissions_safe(self):
1071 def permissions_safe(self):
1055 """
1072 """
1056 Filtered permissions excluding not allowed repositories
1073 Filtered permissions excluding not allowed repositories
1057 """
1074 """
1058 perms = self.get_perms(user=self, cache=None)
1075 perms = self.get_perms(user=self, cache=None)
1059
1076
1060 perms['repositories'] = {
1077 perms['repositories'] = {
1061 k: v for k, v in perms['repositories'].items()
1078 k: v for k, v in perms['repositories'].items()
1062 if v != 'repository.none'}
1079 if v != 'repository.none'}
1063 perms['repositories_groups'] = {
1080 perms['repositories_groups'] = {
1064 k: v for k, v in perms['repositories_groups'].items()
1081 k: v for k, v in perms['repositories_groups'].items()
1065 if v != 'group.none'}
1082 if v != 'group.none'}
1066 perms['user_groups'] = {
1083 perms['user_groups'] = {
1067 k: v for k, v in perms['user_groups'].items()
1084 k: v for k, v in perms['user_groups'].items()
1068 if v != 'usergroup.none'}
1085 if v != 'usergroup.none'}
1069 perms['repository_branches'] = {
1086 perms['repository_branches'] = {
1070 k: v for k, v in perms['repository_branches'].iteritems()
1087 k: v for k, v in perms['repository_branches'].iteritems()
1071 if v != 'branch.none'}
1088 if v != 'branch.none'}
1072 return perms
1089 return perms
1073
1090
1074 @LazyProperty
1091 @LazyProperty
1075 def permissions_full_details(self):
1092 def permissions_full_details(self):
1076 return self.get_perms(
1093 return self.get_perms(
1077 user=self, cache=None, calculate_super_admin=True)
1094 user=self, cache=None, calculate_super_admin=True)
1078
1095
1079 def permissions_with_scope(self, scope):
1096 def permissions_with_scope(self, scope):
1080 """
1097 """
1081 Call the get_perms function with scoped data. The scope in that function
1098 Call the get_perms function with scoped data. The scope in that function
1082 narrows the SQL calls to the given ID of objects resulting in fetching
1099 narrows the SQL calls to the given ID of objects resulting in fetching
1083 Just particular permission we want to obtain. If scope is an empty dict
1100 Just particular permission we want to obtain. If scope is an empty dict
1084 then it basically narrows the scope to GLOBAL permissions only.
1101 then it basically narrows the scope to GLOBAL permissions only.
1085
1102
1086 :param scope: dict
1103 :param scope: dict
1087 """
1104 """
1088 if 'repo_name' in scope:
1105 if 'repo_name' in scope:
1089 obj = Repository.get_by_repo_name(scope['repo_name'])
1106 obj = Repository.get_by_repo_name(scope['repo_name'])
1090 if obj:
1107 if obj:
1091 scope['repo_id'] = obj.repo_id
1108 scope['repo_id'] = obj.repo_id
1092 _scope = collections.OrderedDict()
1109 _scope = collections.OrderedDict()
1093 _scope['repo_id'] = -1
1110 _scope['repo_id'] = -1
1094 _scope['user_group_id'] = -1
1111 _scope['user_group_id'] = -1
1095 _scope['repo_group_id'] = -1
1112 _scope['repo_group_id'] = -1
1096
1113
1097 for k in sorted(scope.keys()):
1114 for k in sorted(scope.keys()):
1098 _scope[k] = scope[k]
1115 _scope[k] = scope[k]
1099
1116
1100 # store in cache to mimic how the @LazyProperty works,
1117 # store in cache to mimic how the @LazyProperty works,
1101 # the difference here is that we use the unique key calculated
1118 # the difference here is that we use the unique key calculated
1102 # from params and values
1119 # from params and values
1103 return self.get_perms(user=self, cache=None, scope=_scope)
1120 return self.get_perms(user=self, cache=None, scope=_scope)
1104
1121
1105 def get_instance(self):
1122 def get_instance(self):
1106 return User.get(self.user_id)
1123 return User.get(self.user_id)
1107
1124
1108 def propagate_data(self):
1125 def propagate_data(self):
1109 """
1126 """
1110 Fills in user data and propagates values to this instance. Maps fetched
1127 Fills in user data and propagates values to this instance. Maps fetched
1111 user attributes to this class instance attributes
1128 user attributes to this class instance attributes
1112 """
1129 """
1113 log.debug('AuthUser: starting data propagation for new potential user')
1130 log.debug('AuthUser: starting data propagation for new potential user')
1114 user_model = UserModel()
1131 user_model = UserModel()
1115 anon_user = self.anonymous_user = User.get_default_user(cache=True)
1132 anon_user = self.anonymous_user = User.get_default_user(cache=True)
1116 is_user_loaded = False
1133 is_user_loaded = False
1117
1134
1118 # lookup by userid
1135 # lookup by userid
1119 if self.user_id is not None and self.user_id != anon_user.user_id:
1136 if self.user_id is not None and self.user_id != anon_user.user_id:
1120 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
1137 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
1121 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
1138 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
1122
1139
1123 # try go get user by api key
1140 # try go get user by api key
1124 elif self._api_key and self._api_key != anon_user.api_key:
1141 elif self._api_key and self._api_key != anon_user.api_key:
1125 log.debug('Trying Auth User lookup by API KEY: `%s`', self._api_key)
1142 log.debug('Trying Auth User lookup by API KEY: `%s`', self._api_key)
1126 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
1143 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
1127
1144
1128 # lookup by username
1145 # lookup by username
1129 elif self.username:
1146 elif self.username:
1130 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
1147 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
1131 is_user_loaded = user_model.fill_data(self, username=self.username)
1148 is_user_loaded = user_model.fill_data(self, username=self.username)
1132 else:
1149 else:
1133 log.debug('No data in %s that could been used to log in', self)
1150 log.debug('No data in %s that could been used to log in', self)
1134
1151
1135 if not is_user_loaded:
1152 if not is_user_loaded:
1136 log.debug(
1153 log.debug(
1137 'Failed to load user. Fallback to default user %s', anon_user)
1154 'Failed to load user. Fallback to default user %s', anon_user)
1138 # if we cannot authenticate user try anonymous
1155 # if we cannot authenticate user try anonymous
1139 if anon_user.active:
1156 if anon_user.active:
1140 log.debug('default user is active, using it as a session user')
1157 log.debug('default user is active, using it as a session user')
1141 user_model.fill_data(self, user_id=anon_user.user_id)
1158 user_model.fill_data(self, user_id=anon_user.user_id)
1142 # then we set this user is logged in
1159 # then we set this user is logged in
1143 self.is_authenticated = True
1160 self.is_authenticated = True
1144 else:
1161 else:
1145 log.debug('default user is NOT active')
1162 log.debug('default user is NOT active')
1146 # in case of disabled anonymous user we reset some of the
1163 # in case of disabled anonymous user we reset some of the
1147 # parameters so such user is "corrupted", skipping the fill_data
1164 # parameters so such user is "corrupted", skipping the fill_data
1148 for attr in ['user_id', 'username', 'admin', 'active']:
1165 for attr in ['user_id', 'username', 'admin', 'active']:
1149 setattr(self, attr, None)
1166 setattr(self, attr, None)
1150 self.is_authenticated = False
1167 self.is_authenticated = False
1151
1168
1152 if not self.username:
1169 if not self.username:
1153 self.username = 'None'
1170 self.username = 'None'
1154
1171
1155 log.debug('AuthUser: propagated user is now %s', self)
1172 log.debug('AuthUser: propagated user is now %s', self)
1156
1173
1157 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
1174 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
1158 calculate_super_admin=False, cache=None):
1175 calculate_super_admin=False, cache=None):
1159 """
1176 """
1160 Fills user permission attribute with permissions taken from database
1177 Fills user permission attribute with permissions taken from database
1161 works for permissions given for repositories, and for permissions that
1178 works for permissions given for repositories, and for permissions that
1162 are granted to groups
1179 are granted to groups
1163
1180
1164 :param user: instance of User object from database
1181 :param user: instance of User object from database
1165 :param explicit: In case there are permissions both for user and a group
1182 :param explicit: In case there are permissions both for user and a group
1166 that user is part of, explicit flag will defiine if user will
1183 that user is part of, explicit flag will defiine if user will
1167 explicitly override permissions from group, if it's False it will
1184 explicitly override permissions from group, if it's False it will
1168 make decision based on the algo
1185 make decision based on the algo
1169 :param algo: algorithm to decide what permission should be choose if
1186 :param algo: algorithm to decide what permission should be choose if
1170 it's multiple defined, eg user in two different groups. It also
1187 it's multiple defined, eg user in two different groups. It also
1171 decides if explicit flag is turned off how to specify the permission
1188 decides if explicit flag is turned off how to specify the permission
1172 for case when user is in a group + have defined separate permission
1189 for case when user is in a group + have defined separate permission
1173 :param calculate_super_admin: calculate permissions for super-admin in the
1190 :param calculate_super_admin: calculate permissions for super-admin in the
1174 same way as for regular user without speedups
1191 same way as for regular user without speedups
1175 :param cache: Use caching for calculation, None = let the cache backend decide
1192 :param cache: Use caching for calculation, None = let the cache backend decide
1176 """
1193 """
1177 user_id = user.user_id
1194 user_id = user.user_id
1178 user_is_admin = user.is_admin
1195 user_is_admin = user.is_admin
1179
1196
1180 # inheritance of global permissions like create repo/fork repo etc
1197 # inheritance of global permissions like create repo/fork repo etc
1181 user_inherit_default_permissions = user.inherit_default_permissions
1198 user_inherit_default_permissions = user.inherit_default_permissions
1182
1199
1183 cache_seconds = safe_int(
1200 cache_seconds = safe_int(
1184 rhodecode.CONFIG.get('rc_cache.cache_perms.expiration_time'))
1201 rhodecode.CONFIG.get('rc_cache.cache_perms.expiration_time'))
1185
1202
1186 if cache is None:
1203 if cache is None:
1187 # let the backend cache decide
1204 # let the backend cache decide
1188 cache_on = cache_seconds > 0
1205 cache_on = cache_seconds > 0
1189 else:
1206 else:
1190 cache_on = cache
1207 cache_on = cache
1191
1208
1192 log.debug(
1209 log.debug(
1193 'Computing PERMISSION tree for user %s scope `%s` '
1210 'Computing PERMISSION tree for user %s scope `%s` '
1194 'with caching: %s[TTL: %ss]', user, scope, cache_on, cache_seconds or 0)
1211 'with caching: %s[TTL: %ss]', user, scope, cache_on, cache_seconds or 0)
1195
1212
1196 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
1213 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
1197 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1214 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1198
1215
1199 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
1216 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
1200 condition=cache_on)
1217 condition=cache_on)
1201 def compute_perm_tree(cache_name,
1218 def compute_perm_tree(cache_name,
1202 user_id, scope, user_is_admin,user_inherit_default_permissions,
1219 user_id, scope, user_is_admin,user_inherit_default_permissions,
1203 explicit, algo, calculate_super_admin):
1220 explicit, algo, calculate_super_admin):
1204 return _cached_perms_data(
1221 return _cached_perms_data(
1205 user_id, scope, user_is_admin, user_inherit_default_permissions,
1222 user_id, scope, user_is_admin, user_inherit_default_permissions,
1206 explicit, algo, calculate_super_admin)
1223 explicit, algo, calculate_super_admin)
1207
1224
1208 start = time.time()
1225 start = time.time()
1209 result = compute_perm_tree(
1226 result = compute_perm_tree(
1210 'permissions', user_id, scope, user_is_admin,
1227 'permissions', user_id, scope, user_is_admin,
1211 user_inherit_default_permissions, explicit, algo,
1228 user_inherit_default_permissions, explicit, algo,
1212 calculate_super_admin)
1229 calculate_super_admin)
1213
1230
1214 result_repr = []
1231 result_repr = []
1215 for k in result:
1232 for k in result:
1216 result_repr.append((k, len(result[k])))
1233 result_repr.append((k, len(result[k])))
1217 total = time.time() - start
1234 total = time.time() - start
1218 log.debug('PERMISSION tree for user %s computed in %.3fs: %s',
1235 log.debug('PERMISSION tree for user %s computed in %.3fs: %s',
1219 user, total, result_repr)
1236 user, total, result_repr)
1220
1237
1221 return result
1238 return result
1222
1239
1223 @property
1240 @property
1224 def is_default(self):
1241 def is_default(self):
1225 return self.username == User.DEFAULT_USER
1242 return self.username == User.DEFAULT_USER
1226
1243
1227 @property
1244 @property
1228 def is_admin(self):
1245 def is_admin(self):
1229 return self.admin
1246 return self.admin
1230
1247
1231 @property
1248 @property
1232 def is_user_object(self):
1249 def is_user_object(self):
1233 return self.user_id is not None
1250 return self.user_id is not None
1234
1251
1235 @property
1252 @property
1236 def repositories_admin(self):
1253 def repositories_admin(self):
1237 """
1254 """
1238 Returns list of repositories you're an admin of
1255 Returns list of repositories you're an admin of
1239 """
1256 """
1240 return [
1257 return [
1241 x[0] for x in self.permissions['repositories'].items()
1258 x[0] for x in self.permissions['repositories'].items()
1242 if x[1] == 'repository.admin']
1259 if x[1] == 'repository.admin']
1243
1260
1244 @property
1261 @property
1245 def repository_groups_admin(self):
1262 def repository_groups_admin(self):
1246 """
1263 """
1247 Returns list of repository groups you're an admin of
1264 Returns list of repository groups you're an admin of
1248 """
1265 """
1249 return [
1266 return [
1250 x[0] for x in self.permissions['repositories_groups'].items()
1267 x[0] for x in self.permissions['repositories_groups'].items()
1251 if x[1] == 'group.admin']
1268 if x[1] == 'group.admin']
1252
1269
1253 @property
1270 @property
1254 def user_groups_admin(self):
1271 def user_groups_admin(self):
1255 """
1272 """
1256 Returns list of user groups you're an admin of
1273 Returns list of user groups you're an admin of
1257 """
1274 """
1258 return [
1275 return [
1259 x[0] for x in self.permissions['user_groups'].items()
1276 x[0] for x in self.permissions['user_groups'].items()
1260 if x[1] == 'usergroup.admin']
1277 if x[1] == 'usergroup.admin']
1261
1278
1262 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1279 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1263 """
1280 """
1264 Returns list of repository ids that user have access to based on given
1281 Returns list of repository ids that user have access to based on given
1265 perms. The cache flag should be only used in cases that are used for
1282 perms. The cache flag should be only used in cases that are used for
1266 display purposes, NOT IN ANY CASE for permission checks.
1283 display purposes, NOT IN ANY CASE for permission checks.
1267 """
1284 """
1268 from rhodecode.model.scm import RepoList
1285 from rhodecode.model.scm import RepoList
1269 if not perms:
1286 if not perms:
1270 perms = [
1287 perms = [
1271 'repository.read', 'repository.write', 'repository.admin']
1288 'repository.read', 'repository.write', 'repository.admin']
1272
1289
1273 def _cached_repo_acl(user_id, perm_def, _name_filter):
1290 def _cached_repo_acl(user_id, perm_def, _name_filter):
1274 qry = Repository.query()
1291 qry = Repository.query()
1275 if _name_filter:
1292 if _name_filter:
1276 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1293 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1277 qry = qry.filter(
1294 qry = qry.filter(
1278 Repository.repo_name.ilike(ilike_expression))
1295 Repository.repo_name.ilike(ilike_expression))
1279
1296
1280 return [x.repo_id for x in
1297 return [x.repo_id for x in
1281 RepoList(qry, perm_set=perm_def)]
1298 RepoList(qry, perm_set=perm_def)]
1282
1299
1283 return _cached_repo_acl(self.user_id, perms, name_filter)
1300 return _cached_repo_acl(self.user_id, perms, name_filter)
1284
1301
1285 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1302 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1286 """
1303 """
1287 Returns list of repository group ids that user have access to based on given
1304 Returns list of repository group ids that user have access to based on given
1288 perms. The cache flag should be only used in cases that are used for
1305 perms. The cache flag should be only used in cases that are used for
1289 display purposes, NOT IN ANY CASE for permission checks.
1306 display purposes, NOT IN ANY CASE for permission checks.
1290 """
1307 """
1291 from rhodecode.model.scm import RepoGroupList
1308 from rhodecode.model.scm import RepoGroupList
1292 if not perms:
1309 if not perms:
1293 perms = [
1310 perms = [
1294 'group.read', 'group.write', 'group.admin']
1311 'group.read', 'group.write', 'group.admin']
1295
1312
1296 def _cached_repo_group_acl(user_id, perm_def, _name_filter):
1313 def _cached_repo_group_acl(user_id, perm_def, _name_filter):
1297 qry = RepoGroup.query()
1314 qry = RepoGroup.query()
1298 if _name_filter:
1315 if _name_filter:
1299 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1316 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1300 qry = qry.filter(
1317 qry = qry.filter(
1301 RepoGroup.group_name.ilike(ilike_expression))
1318 RepoGroup.group_name.ilike(ilike_expression))
1302
1319
1303 return [x.group_id for x in
1320 return [x.group_id for x in
1304 RepoGroupList(qry, perm_set=perm_def)]
1321 RepoGroupList(qry, perm_set=perm_def)]
1305
1322
1306 return _cached_repo_group_acl(self.user_id, perms, name_filter)
1323 return _cached_repo_group_acl(self.user_id, perms, name_filter)
1307
1324
1308 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1325 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1309 """
1326 """
1310 Returns list of user group ids that user have access to based on given
1327 Returns list of user group ids that user have access to based on given
1311 perms. The cache flag should be only used in cases that are used for
1328 perms. The cache flag should be only used in cases that are used for
1312 display purposes, NOT IN ANY CASE for permission checks.
1329 display purposes, NOT IN ANY CASE for permission checks.
1313 """
1330 """
1314 from rhodecode.model.scm import UserGroupList
1331 from rhodecode.model.scm import UserGroupList
1315 if not perms:
1332 if not perms:
1316 perms = [
1333 perms = [
1317 'usergroup.read', 'usergroup.write', 'usergroup.admin']
1334 'usergroup.read', 'usergroup.write', 'usergroup.admin']
1318
1335
1319 def _cached_user_group_acl(user_id, perm_def, name_filter):
1336 def _cached_user_group_acl(user_id, perm_def, name_filter):
1320 qry = UserGroup.query()
1337 qry = UserGroup.query()
1321 if name_filter:
1338 if name_filter:
1322 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1339 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1323 qry = qry.filter(
1340 qry = qry.filter(
1324 UserGroup.users_group_name.ilike(ilike_expression))
1341 UserGroup.users_group_name.ilike(ilike_expression))
1325
1342
1326 return [x.users_group_id for x in
1343 return [x.users_group_id for x in
1327 UserGroupList(qry, perm_set=perm_def)]
1344 UserGroupList(qry, perm_set=perm_def)]
1328
1345
1329 return _cached_user_group_acl(self.user_id, perms, name_filter)
1346 return _cached_user_group_acl(self.user_id, perms, name_filter)
1330
1347
1331 @property
1348 @property
1332 def ip_allowed(self):
1349 def ip_allowed(self):
1333 """
1350 """
1334 Checks if ip_addr used in constructor is allowed from defined list of
1351 Checks if ip_addr used in constructor is allowed from defined list of
1335 allowed ip_addresses for user
1352 allowed ip_addresses for user
1336
1353
1337 :returns: boolean, True if ip is in allowed ip range
1354 :returns: boolean, True if ip is in allowed ip range
1338 """
1355 """
1339 # check IP
1356 # check IP
1340 inherit = self.inherit_default_permissions
1357 inherit = self.inherit_default_permissions
1341 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1358 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1342 inherit_from_default=inherit)
1359 inherit_from_default=inherit)
1343 @property
1360 @property
1344 def personal_repo_group(self):
1361 def personal_repo_group(self):
1345 return RepoGroup.get_user_personal_repo_group(self.user_id)
1362 return RepoGroup.get_user_personal_repo_group(self.user_id)
1346
1363
1347 @LazyProperty
1364 @LazyProperty
1348 def feed_token(self):
1365 def feed_token(self):
1349 return self.get_instance().feed_token
1366 return self.get_instance().feed_token
1350
1367
1351 @classmethod
1368 @classmethod
1352 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1369 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1353 allowed_ips = AuthUser.get_allowed_ips(
1370 allowed_ips = AuthUser.get_allowed_ips(
1354 user_id, cache=True, inherit_from_default=inherit_from_default)
1371 user_id, cache=True, inherit_from_default=inherit_from_default)
1355 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1372 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1356 log.debug('IP:%s for user %s is in range of %s',
1373 log.debug('IP:%s for user %s is in range of %s',
1357 ip_addr, user_id, allowed_ips)
1374 ip_addr, user_id, allowed_ips)
1358 return True
1375 return True
1359 else:
1376 else:
1360 log.info('Access for IP:%s forbidden for user %s, '
1377 log.info('Access for IP:%s forbidden for user %s, '
1361 'not in %s', ip_addr, user_id, allowed_ips)
1378 'not in %s', ip_addr, user_id, allowed_ips)
1362 return False
1379 return False
1363
1380
1364 def get_branch_permissions(self, repo_name, perms=None):
1381 def get_branch_permissions(self, repo_name, perms=None):
1365 perms = perms or self.permissions_with_scope({'repo_name': repo_name})
1382 perms = perms or self.permissions_with_scope({'repo_name': repo_name})
1366 branch_perms = perms.get('repository_branches', {})
1383 branch_perms = perms.get('repository_branches', {})
1367 if not branch_perms:
1384 if not branch_perms:
1368 return {}
1385 return {}
1369 repo_branch_perms = branch_perms.get(repo_name)
1386 repo_branch_perms = branch_perms.get(repo_name)
1370 return repo_branch_perms or {}
1387 return repo_branch_perms or {}
1371
1388
1372 def get_rule_and_branch_permission(self, repo_name, branch_name):
1389 def get_rule_and_branch_permission(self, repo_name, branch_name):
1373 """
1390 """
1374 Check if this AuthUser has defined any permissions for branches. If any of
1391 Check if this AuthUser has defined any permissions for branches. If any of
1375 the rules match in order, we return the matching permissions
1392 the rules match in order, we return the matching permissions
1376 """
1393 """
1377
1394
1378 rule = default_perm = ''
1395 rule = default_perm = ''
1379
1396
1380 repo_branch_perms = self.get_branch_permissions(repo_name=repo_name)
1397 repo_branch_perms = self.get_branch_permissions(repo_name=repo_name)
1381 if not repo_branch_perms:
1398 if not repo_branch_perms:
1382 return rule, default_perm
1399 return rule, default_perm
1383
1400
1384 # now calculate the permissions
1401 # now calculate the permissions
1385 for pattern, branch_perm in repo_branch_perms.items():
1402 for pattern, branch_perm in repo_branch_perms.items():
1386 if fnmatch.fnmatch(branch_name, pattern):
1403 if fnmatch.fnmatch(branch_name, pattern):
1387 rule = '`{}`=>{}'.format(pattern, branch_perm)
1404 rule = '`{}`=>{}'.format(pattern, branch_perm)
1388 return rule, branch_perm
1405 return rule, branch_perm
1389
1406
1390 return rule, default_perm
1407 return rule, default_perm
1391
1408
1392 def __repr__(self):
1409 def __repr__(self):
1393 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1410 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1394 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1411 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1395
1412
1396 def set_authenticated(self, authenticated=True):
1413 def set_authenticated(self, authenticated=True):
1397 if self.user_id != self.anonymous_user.user_id:
1414 if self.user_id != self.anonymous_user.user_id:
1398 self.is_authenticated = authenticated
1415 self.is_authenticated = authenticated
1399
1416
1400 def get_cookie_store(self):
1417 def get_cookie_store(self):
1401 return {
1418 return {
1402 'username': self.username,
1419 'username': self.username,
1403 'password': md5(self.password or ''),
1420 'password': md5(self.password or ''),
1404 'user_id': self.user_id,
1421 'user_id': self.user_id,
1405 'is_authenticated': self.is_authenticated
1422 'is_authenticated': self.is_authenticated
1406 }
1423 }
1407
1424
1408 @classmethod
1425 @classmethod
1409 def from_cookie_store(cls, cookie_store):
1426 def from_cookie_store(cls, cookie_store):
1410 """
1427 """
1411 Creates AuthUser from a cookie store
1428 Creates AuthUser from a cookie store
1412
1429
1413 :param cls:
1430 :param cls:
1414 :param cookie_store:
1431 :param cookie_store:
1415 """
1432 """
1416 user_id = cookie_store.get('user_id')
1433 user_id = cookie_store.get('user_id')
1417 username = cookie_store.get('username')
1434 username = cookie_store.get('username')
1418 api_key = cookie_store.get('api_key')
1435 api_key = cookie_store.get('api_key')
1419 return AuthUser(user_id, api_key, username)
1436 return AuthUser(user_id, api_key, username)
1420
1437
1421 @classmethod
1438 @classmethod
1422 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1439 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1423 _set = set()
1440 _set = set()
1424
1441
1425 if inherit_from_default:
1442 if inherit_from_default:
1426 def_user_id = User.get_default_user(cache=True).user_id
1443 def_user_id = User.get_default_user(cache=True).user_id
1427 default_ips = UserIpMap.query().filter(UserIpMap.user_id == def_user_id)
1444 default_ips = UserIpMap.query().filter(UserIpMap.user_id == def_user_id)
1428 if cache:
1445 if cache:
1429 default_ips = default_ips.options(
1446 default_ips = default_ips.options(
1430 FromCache("sql_cache_short", "get_user_ips_default"))
1447 FromCache("sql_cache_short", "get_user_ips_default"))
1431
1448
1432 # populate from default user
1449 # populate from default user
1433 for ip in default_ips:
1450 for ip in default_ips:
1434 try:
1451 try:
1435 _set.add(ip.ip_addr)
1452 _set.add(ip.ip_addr)
1436 except ObjectDeletedError:
1453 except ObjectDeletedError:
1437 # since we use heavy caching sometimes it happens that
1454 # since we use heavy caching sometimes it happens that
1438 # we get deleted objects here, we just skip them
1455 # we get deleted objects here, we just skip them
1439 pass
1456 pass
1440
1457
1441 # NOTE:(marcink) we don't want to load any rules for empty
1458 # NOTE:(marcink) we don't want to load any rules for empty
1442 # user_id which is the case of access of non logged users when anonymous
1459 # user_id which is the case of access of non logged users when anonymous
1443 # access is disabled
1460 # access is disabled
1444 user_ips = []
1461 user_ips = []
1445 if user_id:
1462 if user_id:
1446 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1463 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1447 if cache:
1464 if cache:
1448 user_ips = user_ips.options(
1465 user_ips = user_ips.options(
1449 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1466 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1450
1467
1451 for ip in user_ips:
1468 for ip in user_ips:
1452 try:
1469 try:
1453 _set.add(ip.ip_addr)
1470 _set.add(ip.ip_addr)
1454 except ObjectDeletedError:
1471 except ObjectDeletedError:
1455 # since we use heavy caching sometimes it happens that we get
1472 # since we use heavy caching sometimes it happens that we get
1456 # deleted objects here, we just skip them
1473 # deleted objects here, we just skip them
1457 pass
1474 pass
1458 return _set or {ip for ip in ['0.0.0.0/0', '::/0']}
1475 return _set or {ip for ip in ['0.0.0.0/0', '::/0']}
1459
1476
1460
1477
1461 def set_available_permissions(settings):
1478 def set_available_permissions(settings):
1462 """
1479 """
1463 This function will propagate pyramid settings with all available defined
1480 This function will propagate pyramid settings with all available defined
1464 permission given in db. We don't want to check each time from db for new
1481 permission given in db. We don't want to check each time from db for new
1465 permissions since adding a new permission also requires application restart
1482 permissions since adding a new permission also requires application restart
1466 ie. to decorate new views with the newly created permission
1483 ie. to decorate new views with the newly created permission
1467
1484
1468 :param settings: current pyramid registry.settings
1485 :param settings: current pyramid registry.settings
1469
1486
1470 """
1487 """
1471 log.debug('auth: getting information about all available permissions')
1488 log.debug('auth: getting information about all available permissions')
1472 try:
1489 try:
1473 sa = meta.Session
1490 sa = meta.Session
1474 all_perms = sa.query(Permission).all()
1491 all_perms = sa.query(Permission).all()
1475 settings.setdefault('available_permissions',
1492 settings.setdefault('available_permissions',
1476 [x.permission_name for x in all_perms])
1493 [x.permission_name for x in all_perms])
1477 log.debug('auth: set available permissions')
1494 log.debug('auth: set available permissions')
1478 except Exception:
1495 except Exception:
1479 log.exception('Failed to fetch permissions from the database.')
1496 log.exception('Failed to fetch permissions from the database.')
1480 raise
1497 raise
1481
1498
1482
1499
1483 def get_csrf_token(session, force_new=False, save_if_missing=True):
1500 def get_csrf_token(session, force_new=False, save_if_missing=True):
1484 """
1501 """
1485 Return the current authentication token, creating one if one doesn't
1502 Return the current authentication token, creating one if one doesn't
1486 already exist and the save_if_missing flag is present.
1503 already exist and the save_if_missing flag is present.
1487
1504
1488 :param session: pass in the pyramid session, else we use the global ones
1505 :param session: pass in the pyramid session, else we use the global ones
1489 :param force_new: force to re-generate the token and store it in session
1506 :param force_new: force to re-generate the token and store it in session
1490 :param save_if_missing: save the newly generated token if it's missing in
1507 :param save_if_missing: save the newly generated token if it's missing in
1491 session
1508 session
1492 """
1509 """
1493 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1510 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1494 # from pyramid.csrf import get_csrf_token
1511 # from pyramid.csrf import get_csrf_token
1495
1512
1496 if (csrf_token_key not in session and save_if_missing) or force_new:
1513 if (csrf_token_key not in session and save_if_missing) or force_new:
1497 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1514 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1498 session[csrf_token_key] = token
1515 session[csrf_token_key] = token
1499 if hasattr(session, 'save'):
1516 if hasattr(session, 'save'):
1500 session.save()
1517 session.save()
1501 return session.get(csrf_token_key)
1518 return session.get(csrf_token_key)
1502
1519
1503
1520
1504 def get_request(perm_class_instance):
1521 def get_request(perm_class_instance):
1505 from pyramid.threadlocal import get_current_request
1522 from pyramid.threadlocal import get_current_request
1506 pyramid_request = get_current_request()
1523 pyramid_request = get_current_request()
1507 return pyramid_request
1524 return pyramid_request
1508
1525
1509
1526
1510 # CHECK DECORATORS
1527 # CHECK DECORATORS
1511 class CSRFRequired(object):
1528 class CSRFRequired(object):
1512 """
1529 """
1513 Decorator for authenticating a form
1530 Decorator for authenticating a form
1514
1531
1515 This decorator uses an authorization token stored in the client's
1532 This decorator uses an authorization token stored in the client's
1516 session for prevention of certain Cross-site request forgery (CSRF)
1533 session for prevention of certain Cross-site request forgery (CSRF)
1517 attacks (See
1534 attacks (See
1518 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1535 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1519 information).
1536 information).
1520
1537
1521 For use with the ``webhelpers.secure_form`` helper functions.
1538 For use with the ``webhelpers.secure_form`` helper functions.
1522
1539
1523 """
1540 """
1524 def __init__(self, token=csrf_token_key, header='X-CSRF-Token',
1541 def __init__(self, token=csrf_token_key, header='X-CSRF-Token',
1525 except_methods=None):
1542 except_methods=None):
1526 self.token = token
1543 self.token = token
1527 self.header = header
1544 self.header = header
1528 self.except_methods = except_methods or []
1545 self.except_methods = except_methods or []
1529
1546
1530 def __call__(self, func):
1547 def __call__(self, func):
1531 return get_cython_compat_decorator(self.__wrapper, func)
1548 return get_cython_compat_decorator(self.__wrapper, func)
1532
1549
1533 def _get_csrf(self, _request):
1550 def _get_csrf(self, _request):
1534 return _request.POST.get(self.token, _request.headers.get(self.header))
1551 return _request.POST.get(self.token, _request.headers.get(self.header))
1535
1552
1536 def check_csrf(self, _request, cur_token):
1553 def check_csrf(self, _request, cur_token):
1537 supplied_token = self._get_csrf(_request)
1554 supplied_token = self._get_csrf(_request)
1538 return supplied_token and supplied_token == cur_token
1555 return supplied_token and supplied_token == cur_token
1539
1556
1540 def _get_request(self):
1557 def _get_request(self):
1541 return get_request(self)
1558 return get_request(self)
1542
1559
1543 def __wrapper(self, func, *fargs, **fkwargs):
1560 def __wrapper(self, func, *fargs, **fkwargs):
1544 request = self._get_request()
1561 request = self._get_request()
1545
1562
1546 if request.method in self.except_methods:
1563 if request.method in self.except_methods:
1547 return func(*fargs, **fkwargs)
1564 return func(*fargs, **fkwargs)
1548
1565
1549 cur_token = get_csrf_token(request.session, save_if_missing=False)
1566 cur_token = get_csrf_token(request.session, save_if_missing=False)
1550 if self.check_csrf(request, cur_token):
1567 if self.check_csrf(request, cur_token):
1551 if request.POST.get(self.token):
1568 if request.POST.get(self.token):
1552 del request.POST[self.token]
1569 del request.POST[self.token]
1553 return func(*fargs, **fkwargs)
1570 return func(*fargs, **fkwargs)
1554 else:
1571 else:
1555 reason = 'token-missing'
1572 reason = 'token-missing'
1556 supplied_token = self._get_csrf(request)
1573 supplied_token = self._get_csrf(request)
1557 if supplied_token and cur_token != supplied_token:
1574 if supplied_token and cur_token != supplied_token:
1558 reason = 'token-mismatch [%s:%s]' % (
1575 reason = 'token-mismatch [%s:%s]' % (
1559 cur_token or ''[:6], supplied_token or ''[:6])
1576 cur_token or ''[:6], supplied_token or ''[:6])
1560
1577
1561 csrf_message = \
1578 csrf_message = \
1562 ("Cross-site request forgery detected, request denied. See "
1579 ("Cross-site request forgery detected, request denied. See "
1563 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1580 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1564 "more information.")
1581 "more information.")
1565 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1582 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1566 'REMOTE_ADDR:%s, HEADERS:%s' % (
1583 'REMOTE_ADDR:%s, HEADERS:%s' % (
1567 request, reason, request.remote_addr, request.headers))
1584 request, reason, request.remote_addr, request.headers))
1568
1585
1569 raise HTTPForbidden(explanation=csrf_message)
1586 raise HTTPForbidden(explanation=csrf_message)
1570
1587
1571
1588
1572 class LoginRequired(object):
1589 class LoginRequired(object):
1573 """
1590 """
1574 Must be logged in to execute this function else
1591 Must be logged in to execute this function else
1575 redirect to login page
1592 redirect to login page
1576
1593
1577 :param api_access: if enabled this checks only for valid auth token
1594 :param api_access: if enabled this checks only for valid auth token
1578 and grants access based on valid token
1595 and grants access based on valid token
1579 """
1596 """
1580 def __init__(self, auth_token_access=None):
1597 def __init__(self, auth_token_access=None):
1581 self.auth_token_access = auth_token_access
1598 self.auth_token_access = auth_token_access
1582
1599
1583 def __call__(self, func):
1600 def __call__(self, func):
1584 return get_cython_compat_decorator(self.__wrapper, func)
1601 return get_cython_compat_decorator(self.__wrapper, func)
1585
1602
1586 def _get_request(self):
1603 def _get_request(self):
1587 return get_request(self)
1604 return get_request(self)
1588
1605
1589 def __wrapper(self, func, *fargs, **fkwargs):
1606 def __wrapper(self, func, *fargs, **fkwargs):
1590 from rhodecode.lib import helpers as h
1607 from rhodecode.lib import helpers as h
1591 cls = fargs[0]
1608 cls = fargs[0]
1592 user = cls._rhodecode_user
1609 user = cls._rhodecode_user
1593 request = self._get_request()
1610 request = self._get_request()
1594 _ = request.translate
1611 _ = request.translate
1595
1612
1596 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1613 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1597 log.debug('Starting login restriction checks for user: %s', user)
1614 log.debug('Starting login restriction checks for user: %s', user)
1598 # check if our IP is allowed
1615 # check if our IP is allowed
1599 ip_access_valid = True
1616 ip_access_valid = True
1600 if not user.ip_allowed:
1617 if not user.ip_allowed:
1601 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr,))),
1618 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr,))),
1602 category='warning')
1619 category='warning')
1603 ip_access_valid = False
1620 ip_access_valid = False
1604
1621
1605 # check if we used an APIKEY and it's a valid one
1622 # check if we used an APIKEY and it's a valid one
1606 # defined white-list of controllers which API access will be enabled
1623 # defined white-list of controllers which API access will be enabled
1607 _auth_token = request.GET.get(
1624 _auth_token = request.GET.get(
1608 'auth_token', '') or request.GET.get('api_key', '')
1625 'auth_token', '') or request.GET.get('api_key', '')
1609 auth_token_access_valid = allowed_auth_token_access(
1626 auth_token_access_valid = allowed_auth_token_access(
1610 loc, auth_token=_auth_token)
1627 loc, auth_token=_auth_token)
1611
1628
1612 # explicit controller is enabled or API is in our whitelist
1629 # explicit controller is enabled or API is in our whitelist
1613 if self.auth_token_access or auth_token_access_valid:
1630 if self.auth_token_access or auth_token_access_valid:
1614 log.debug('Checking AUTH TOKEN access for %s', cls)
1631 log.debug('Checking AUTH TOKEN access for %s', cls)
1615 db_user = user.get_instance()
1632 db_user = user.get_instance()
1616
1633
1617 if db_user:
1634 if db_user:
1618 if self.auth_token_access:
1635 if self.auth_token_access:
1619 roles = self.auth_token_access
1636 roles = self.auth_token_access
1620 else:
1637 else:
1621 roles = [UserApiKeys.ROLE_HTTP]
1638 roles = [UserApiKeys.ROLE_HTTP]
1622 token_match = db_user.authenticate_by_token(
1639 token_match = db_user.authenticate_by_token(
1623 _auth_token, roles=roles)
1640 _auth_token, roles=roles)
1624 else:
1641 else:
1625 log.debug('Unable to fetch db instance for auth user: %s', user)
1642 log.debug('Unable to fetch db instance for auth user: %s', user)
1626 token_match = False
1643 token_match = False
1627
1644
1628 if _auth_token and token_match:
1645 if _auth_token and token_match:
1629 auth_token_access_valid = True
1646 auth_token_access_valid = True
1630 log.debug('AUTH TOKEN ****%s is VALID', _auth_token[-4:])
1647 log.debug('AUTH TOKEN ****%s is VALID', _auth_token[-4:])
1631 else:
1648 else:
1632 auth_token_access_valid = False
1649 auth_token_access_valid = False
1633 if not _auth_token:
1650 if not _auth_token:
1634 log.debug("AUTH TOKEN *NOT* present in request")
1651 log.debug("AUTH TOKEN *NOT* present in request")
1635 else:
1652 else:
1636 log.warning("AUTH TOKEN ****%s *NOT* valid", _auth_token[-4:])
1653 log.warning("AUTH TOKEN ****%s *NOT* valid", _auth_token[-4:])
1637
1654
1638 log.debug('Checking if %s is authenticated @ %s', user.username, loc)
1655 log.debug('Checking if %s is authenticated @ %s', user.username, loc)
1639 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1656 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1640 else 'AUTH_TOKEN_AUTH'
1657 else 'AUTH_TOKEN_AUTH'
1641
1658
1642 if ip_access_valid and (
1659 if ip_access_valid and (
1643 user.is_authenticated or auth_token_access_valid):
1660 user.is_authenticated or auth_token_access_valid):
1644 log.info('user %s authenticating with:%s IS authenticated on func %s',
1661 log.info('user %s authenticating with:%s IS authenticated on func %s',
1645 user, reason, loc)
1662 user, reason, loc)
1646
1663
1647 return func(*fargs, **fkwargs)
1664 return func(*fargs, **fkwargs)
1648 else:
1665 else:
1649 log.warning(
1666 log.warning(
1650 'user %s authenticating with:%s NOT authenticated on '
1667 'user %s authenticating with:%s NOT authenticated on '
1651 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s',
1668 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s',
1652 user, reason, loc, ip_access_valid, auth_token_access_valid)
1669 user, reason, loc, ip_access_valid, auth_token_access_valid)
1653 # we preserve the get PARAM
1670 # we preserve the get PARAM
1654 came_from = get_came_from(request)
1671 came_from = get_came_from(request)
1655
1672
1656 log.debug('redirecting to login page with %s', came_from)
1673 log.debug('redirecting to login page with %s', came_from)
1657 raise HTTPFound(
1674 raise HTTPFound(
1658 h.route_path('login', _query={'came_from': came_from}))
1675 h.route_path('login', _query={'came_from': came_from}))
1659
1676
1660
1677
1661 class NotAnonymous(object):
1678 class NotAnonymous(object):
1662 """
1679 """
1663 Must be logged in to execute this function else
1680 Must be logged in to execute this function else
1664 redirect to login page
1681 redirect to login page
1665 """
1682 """
1666
1683
1667 def __call__(self, func):
1684 def __call__(self, func):
1668 return get_cython_compat_decorator(self.__wrapper, func)
1685 return get_cython_compat_decorator(self.__wrapper, func)
1669
1686
1670 def _get_request(self):
1687 def _get_request(self):
1671 return get_request(self)
1688 return get_request(self)
1672
1689
1673 def __wrapper(self, func, *fargs, **fkwargs):
1690 def __wrapper(self, func, *fargs, **fkwargs):
1674 import rhodecode.lib.helpers as h
1691 import rhodecode.lib.helpers as h
1675 cls = fargs[0]
1692 cls = fargs[0]
1676 self.user = cls._rhodecode_user
1693 self.user = cls._rhodecode_user
1677 request = self._get_request()
1694 request = self._get_request()
1678 _ = request.translate
1695 _ = request.translate
1679 log.debug('Checking if user is not anonymous @%s', cls)
1696 log.debug('Checking if user is not anonymous @%s', cls)
1680
1697
1681 anonymous = self.user.username == User.DEFAULT_USER
1698 anonymous = self.user.username == User.DEFAULT_USER
1682
1699
1683 if anonymous:
1700 if anonymous:
1684 came_from = get_came_from(request)
1701 came_from = get_came_from(request)
1685 h.flash(_('You need to be a registered user to '
1702 h.flash(_('You need to be a registered user to '
1686 'perform this action'),
1703 'perform this action'),
1687 category='warning')
1704 category='warning')
1688 raise HTTPFound(
1705 raise HTTPFound(
1689 h.route_path('login', _query={'came_from': came_from}))
1706 h.route_path('login', _query={'came_from': came_from}))
1690 else:
1707 else:
1691 return func(*fargs, **fkwargs)
1708 return func(*fargs, **fkwargs)
1692
1709
1693
1710
1694 class PermsDecorator(object):
1711 class PermsDecorator(object):
1695 """
1712 """
1696 Base class for controller decorators, we extract the current user from
1713 Base class for controller decorators, we extract the current user from
1697 the class itself, which has it stored in base controllers
1714 the class itself, which has it stored in base controllers
1698 """
1715 """
1699
1716
1700 def __init__(self, *required_perms):
1717 def __init__(self, *required_perms):
1701 self.required_perms = set(required_perms)
1718 self.required_perms = set(required_perms)
1702
1719
1703 def __call__(self, func):
1720 def __call__(self, func):
1704 return get_cython_compat_decorator(self.__wrapper, func)
1721 return get_cython_compat_decorator(self.__wrapper, func)
1705
1722
1706 def _get_request(self):
1723 def _get_request(self):
1707 return get_request(self)
1724 return get_request(self)
1708
1725
1709 def __wrapper(self, func, *fargs, **fkwargs):
1726 def __wrapper(self, func, *fargs, **fkwargs):
1710 import rhodecode.lib.helpers as h
1727 import rhodecode.lib.helpers as h
1711 cls = fargs[0]
1728 cls = fargs[0]
1712 _user = cls._rhodecode_user
1729 _user = cls._rhodecode_user
1713 request = self._get_request()
1730 request = self._get_request()
1714 _ = request.translate
1731 _ = request.translate
1715
1732
1716 log.debug('checking %s permissions %s for %s %s',
1733 log.debug('checking %s permissions %s for %s %s',
1717 self.__class__.__name__, self.required_perms, cls, _user)
1734 self.__class__.__name__, self.required_perms, cls, _user)
1718
1735
1719 if self.check_permissions(_user):
1736 if self.check_permissions(_user):
1720 log.debug('Permission granted for %s %s', cls, _user)
1737 log.debug('Permission granted for %s %s', cls, _user)
1721 return func(*fargs, **fkwargs)
1738 return func(*fargs, **fkwargs)
1722
1739
1723 else:
1740 else:
1724 log.debug('Permission denied for %s %s', cls, _user)
1741 log.debug('Permission denied for %s %s', cls, _user)
1725 anonymous = _user.username == User.DEFAULT_USER
1742 anonymous = _user.username == User.DEFAULT_USER
1726
1743
1727 if anonymous:
1744 if anonymous:
1728 came_from = get_came_from(self._get_request())
1745 came_from = get_came_from(self._get_request())
1729 h.flash(_('You need to be signed in to view this page'),
1746 h.flash(_('You need to be signed in to view this page'),
1730 category='warning')
1747 category='warning')
1731 raise HTTPFound(
1748 raise HTTPFound(
1732 h.route_path('login', _query={'came_from': came_from}))
1749 h.route_path('login', _query={'came_from': came_from}))
1733
1750
1734 else:
1751 else:
1735 # redirect with 404 to prevent resource discovery
1752 # redirect with 404 to prevent resource discovery
1736 raise HTTPNotFound()
1753 raise HTTPNotFound()
1737
1754
1738 def check_permissions(self, user):
1755 def check_permissions(self, user):
1739 """Dummy function for overriding"""
1756 """Dummy function for overriding"""
1740 raise NotImplementedError(
1757 raise NotImplementedError(
1741 'You have to write this function in child class')
1758 'You have to write this function in child class')
1742
1759
1743
1760
1744 class HasPermissionAllDecorator(PermsDecorator):
1761 class HasPermissionAllDecorator(PermsDecorator):
1745 """
1762 """
1746 Checks for access permission for all given predicates. All of them
1763 Checks for access permission for all given predicates. All of them
1747 have to be meet in order to fulfill the request
1764 have to be meet in order to fulfill the request
1748 """
1765 """
1749
1766
1750 def check_permissions(self, user):
1767 def check_permissions(self, user):
1751 perms = user.permissions_with_scope({})
1768 perms = user.permissions_with_scope({})
1752 if self.required_perms.issubset(perms['global']):
1769 if self.required_perms.issubset(perms['global']):
1753 return True
1770 return True
1754 return False
1771 return False
1755
1772
1756
1773
1757 class HasPermissionAnyDecorator(PermsDecorator):
1774 class HasPermissionAnyDecorator(PermsDecorator):
1758 """
1775 """
1759 Checks for access permission for any of given predicates. In order to
1776 Checks for access permission for any of given predicates. In order to
1760 fulfill the request any of predicates must be meet
1777 fulfill the request any of predicates must be meet
1761 """
1778 """
1762
1779
1763 def check_permissions(self, user):
1780 def check_permissions(self, user):
1764 perms = user.permissions_with_scope({})
1781 perms = user.permissions_with_scope({})
1765 if self.required_perms.intersection(perms['global']):
1782 if self.required_perms.intersection(perms['global']):
1766 return True
1783 return True
1767 return False
1784 return False
1768
1785
1769
1786
1770 class HasRepoPermissionAllDecorator(PermsDecorator):
1787 class HasRepoPermissionAllDecorator(PermsDecorator):
1771 """
1788 """
1772 Checks for access permission for all given predicates for specific
1789 Checks for access permission for all given predicates for specific
1773 repository. All of them have to be meet in order to fulfill the request
1790 repository. All of them have to be meet in order to fulfill the request
1774 """
1791 """
1775 def _get_repo_name(self):
1792 def _get_repo_name(self):
1776 _request = self._get_request()
1793 _request = self._get_request()
1777 return get_repo_slug(_request)
1794 return get_repo_slug(_request)
1778
1795
1779 def check_permissions(self, user):
1796 def check_permissions(self, user):
1780 perms = user.permissions
1797 perms = user.permissions
1781 repo_name = self._get_repo_name()
1798 repo_name = self._get_repo_name()
1782
1799
1783 try:
1800 try:
1784 user_perms = {perms['repositories'][repo_name]}
1801 user_perms = {perms['repositories'][repo_name]}
1785 except KeyError:
1802 except KeyError:
1786 log.debug('cannot locate repo with name: `%s` in permissions defs',
1803 log.debug('cannot locate repo with name: `%s` in permissions defs',
1787 repo_name)
1804 repo_name)
1788 return False
1805 return False
1789
1806
1790 log.debug('checking `%s` permissions for repo `%s`',
1807 log.debug('checking `%s` permissions for repo `%s`',
1791 user_perms, repo_name)
1808 user_perms, repo_name)
1792 if self.required_perms.issubset(user_perms):
1809 if self.required_perms.issubset(user_perms):
1793 return True
1810 return True
1794 return False
1811 return False
1795
1812
1796
1813
1797 class HasRepoPermissionAnyDecorator(PermsDecorator):
1814 class HasRepoPermissionAnyDecorator(PermsDecorator):
1798 """
1815 """
1799 Checks for access permission for any of given predicates for specific
1816 Checks for access permission for any of given predicates for specific
1800 repository. In order to fulfill the request any of predicates must be meet
1817 repository. In order to fulfill the request any of predicates must be meet
1801 """
1818 """
1802 def _get_repo_name(self):
1819 def _get_repo_name(self):
1803 _request = self._get_request()
1820 _request = self._get_request()
1804 return get_repo_slug(_request)
1821 return get_repo_slug(_request)
1805
1822
1806 def check_permissions(self, user):
1823 def check_permissions(self, user):
1807 perms = user.permissions
1824 perms = user.permissions
1808 repo_name = self._get_repo_name()
1825 repo_name = self._get_repo_name()
1809
1826
1810 try:
1827 try:
1811 user_perms = {perms['repositories'][repo_name]}
1828 user_perms = {perms['repositories'][repo_name]}
1812 except KeyError:
1829 except KeyError:
1813 log.debug(
1830 log.debug(
1814 'cannot locate repo with name: `%s` in permissions defs',
1831 'cannot locate repo with name: `%s` in permissions defs',
1815 repo_name)
1832 repo_name)
1816 return False
1833 return False
1817
1834
1818 log.debug('checking `%s` permissions for repo `%s`',
1835 log.debug('checking `%s` permissions for repo `%s`',
1819 user_perms, repo_name)
1836 user_perms, repo_name)
1820 if self.required_perms.intersection(user_perms):
1837 if self.required_perms.intersection(user_perms):
1821 return True
1838 return True
1822 return False
1839 return False
1823
1840
1824
1841
1825 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1842 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1826 """
1843 """
1827 Checks for access permission for all given predicates for specific
1844 Checks for access permission for all given predicates for specific
1828 repository group. All of them have to be meet in order to
1845 repository group. All of them have to be meet in order to
1829 fulfill the request
1846 fulfill the request
1830 """
1847 """
1831 def _get_repo_group_name(self):
1848 def _get_repo_group_name(self):
1832 _request = self._get_request()
1849 _request = self._get_request()
1833 return get_repo_group_slug(_request)
1850 return get_repo_group_slug(_request)
1834
1851
1835 def check_permissions(self, user):
1852 def check_permissions(self, user):
1836 perms = user.permissions
1853 perms = user.permissions
1837 group_name = self._get_repo_group_name()
1854 group_name = self._get_repo_group_name()
1838 try:
1855 try:
1839 user_perms = {perms['repositories_groups'][group_name]}
1856 user_perms = {perms['repositories_groups'][group_name]}
1840 except KeyError:
1857 except KeyError:
1841 log.debug(
1858 log.debug(
1842 'cannot locate repo group with name: `%s` in permissions defs',
1859 'cannot locate repo group with name: `%s` in permissions defs',
1843 group_name)
1860 group_name)
1844 return False
1861 return False
1845
1862
1846 log.debug('checking `%s` permissions for repo group `%s`',
1863 log.debug('checking `%s` permissions for repo group `%s`',
1847 user_perms, group_name)
1864 user_perms, group_name)
1848 if self.required_perms.issubset(user_perms):
1865 if self.required_perms.issubset(user_perms):
1849 return True
1866 return True
1850 return False
1867 return False
1851
1868
1852
1869
1853 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1870 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1854 """
1871 """
1855 Checks for access permission for any of given predicates for specific
1872 Checks for access permission for any of given predicates for specific
1856 repository group. In order to fulfill the request any
1873 repository group. In order to fulfill the request any
1857 of predicates must be met
1874 of predicates must be met
1858 """
1875 """
1859 def _get_repo_group_name(self):
1876 def _get_repo_group_name(self):
1860 _request = self._get_request()
1877 _request = self._get_request()
1861 return get_repo_group_slug(_request)
1878 return get_repo_group_slug(_request)
1862
1879
1863 def check_permissions(self, user):
1880 def check_permissions(self, user):
1864 perms = user.permissions
1881 perms = user.permissions
1865 group_name = self._get_repo_group_name()
1882 group_name = self._get_repo_group_name()
1866
1883
1867 try:
1884 try:
1868 user_perms = {perms['repositories_groups'][group_name]}
1885 user_perms = {perms['repositories_groups'][group_name]}
1869 except KeyError:
1886 except KeyError:
1870 log.debug(
1887 log.debug(
1871 'cannot locate repo group with name: `%s` in permissions defs',
1888 'cannot locate repo group with name: `%s` in permissions defs',
1872 group_name)
1889 group_name)
1873 return False
1890 return False
1874
1891
1875 log.debug('checking `%s` permissions for repo group `%s`',
1892 log.debug('checking `%s` permissions for repo group `%s`',
1876 user_perms, group_name)
1893 user_perms, group_name)
1877 if self.required_perms.intersection(user_perms):
1894 if self.required_perms.intersection(user_perms):
1878 return True
1895 return True
1879 return False
1896 return False
1880
1897
1881
1898
1882 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1899 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1883 """
1900 """
1884 Checks for access permission for all given predicates for specific
1901 Checks for access permission for all given predicates for specific
1885 user group. All of them have to be meet in order to fulfill the request
1902 user group. All of them have to be meet in order to fulfill the request
1886 """
1903 """
1887 def _get_user_group_name(self):
1904 def _get_user_group_name(self):
1888 _request = self._get_request()
1905 _request = self._get_request()
1889 return get_user_group_slug(_request)
1906 return get_user_group_slug(_request)
1890
1907
1891 def check_permissions(self, user):
1908 def check_permissions(self, user):
1892 perms = user.permissions
1909 perms = user.permissions
1893 group_name = self._get_user_group_name()
1910 group_name = self._get_user_group_name()
1894 try:
1911 try:
1895 user_perms = {perms['user_groups'][group_name]}
1912 user_perms = {perms['user_groups'][group_name]}
1896 except KeyError:
1913 except KeyError:
1897 return False
1914 return False
1898
1915
1899 if self.required_perms.issubset(user_perms):
1916 if self.required_perms.issubset(user_perms):
1900 return True
1917 return True
1901 return False
1918 return False
1902
1919
1903
1920
1904 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1921 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1905 """
1922 """
1906 Checks for access permission for any of given predicates for specific
1923 Checks for access permission for any of given predicates for specific
1907 user group. In order to fulfill the request any of predicates must be meet
1924 user group. In order to fulfill the request any of predicates must be meet
1908 """
1925 """
1909 def _get_user_group_name(self):
1926 def _get_user_group_name(self):
1910 _request = self._get_request()
1927 _request = self._get_request()
1911 return get_user_group_slug(_request)
1928 return get_user_group_slug(_request)
1912
1929
1913 def check_permissions(self, user):
1930 def check_permissions(self, user):
1914 perms = user.permissions
1931 perms = user.permissions
1915 group_name = self._get_user_group_name()
1932 group_name = self._get_user_group_name()
1916 try:
1933 try:
1917 user_perms = {perms['user_groups'][group_name]}
1934 user_perms = {perms['user_groups'][group_name]}
1918 except KeyError:
1935 except KeyError:
1919 return False
1936 return False
1920
1937
1921 if self.required_perms.intersection(user_perms):
1938 if self.required_perms.intersection(user_perms):
1922 return True
1939 return True
1923 return False
1940 return False
1924
1941
1925
1942
1926 # CHECK FUNCTIONS
1943 # CHECK FUNCTIONS
1927 class PermsFunction(object):
1944 class PermsFunction(object):
1928 """Base function for other check functions"""
1945 """Base function for other check functions"""
1929
1946
1930 def __init__(self, *perms):
1947 def __init__(self, *perms):
1931 self.required_perms = set(perms)
1948 self.required_perms = set(perms)
1932 self.repo_name = None
1949 self.repo_name = None
1933 self.repo_group_name = None
1950 self.repo_group_name = None
1934 self.user_group_name = None
1951 self.user_group_name = None
1935
1952
1936 def __bool__(self):
1953 def __bool__(self):
1937 frame = inspect.currentframe()
1954 frame = inspect.currentframe()
1938 stack_trace = traceback.format_stack(frame)
1955 stack_trace = traceback.format_stack(frame)
1939 log.error('Checking bool value on a class instance of perm '
1956 log.error('Checking bool value on a class instance of perm '
1940 'function is not allowed: %s', ''.join(stack_trace))
1957 'function is not allowed: %s', ''.join(stack_trace))
1941 # rather than throwing errors, here we always return False so if by
1958 # rather than throwing errors, here we always return False so if by
1942 # accident someone checks truth for just an instance it will always end
1959 # accident someone checks truth for just an instance it will always end
1943 # up in returning False
1960 # up in returning False
1944 return False
1961 return False
1945 __nonzero__ = __bool__
1962 __nonzero__ = __bool__
1946
1963
1947 def __call__(self, check_location='', user=None):
1964 def __call__(self, check_location='', user=None):
1948 if not user:
1965 if not user:
1949 log.debug('Using user attribute from global request')
1966 log.debug('Using user attribute from global request')
1950 request = self._get_request()
1967 request = self._get_request()
1951 user = request.user
1968 user = request.user
1952
1969
1953 # init auth user if not already given
1970 # init auth user if not already given
1954 if not isinstance(user, AuthUser):
1971 if not isinstance(user, AuthUser):
1955 log.debug('Wrapping user %s into AuthUser', user)
1972 log.debug('Wrapping user %s into AuthUser', user)
1956 user = AuthUser(user.user_id)
1973 user = AuthUser(user.user_id)
1957
1974
1958 cls_name = self.__class__.__name__
1975 cls_name = self.__class__.__name__
1959 check_scope = self._get_check_scope(cls_name)
1976 check_scope = self._get_check_scope(cls_name)
1960 check_location = check_location or 'unspecified location'
1977 check_location = check_location or 'unspecified location'
1961
1978
1962 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1979 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1963 self.required_perms, user, check_scope, check_location)
1980 self.required_perms, user, check_scope, check_location)
1964 if not user:
1981 if not user:
1965 log.warning('Empty user given for permission check')
1982 log.warning('Empty user given for permission check')
1966 return False
1983 return False
1967
1984
1968 if self.check_permissions(user):
1985 if self.check_permissions(user):
1969 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1986 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1970 check_scope, user, check_location)
1987 check_scope, user, check_location)
1971 return True
1988 return True
1972
1989
1973 else:
1990 else:
1974 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1991 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1975 check_scope, user, check_location)
1992 check_scope, user, check_location)
1976 return False
1993 return False
1977
1994
1978 def _get_request(self):
1995 def _get_request(self):
1979 return get_request(self)
1996 return get_request(self)
1980
1997
1981 def _get_check_scope(self, cls_name):
1998 def _get_check_scope(self, cls_name):
1982 return {
1999 return {
1983 'HasPermissionAll': 'GLOBAL',
2000 'HasPermissionAll': 'GLOBAL',
1984 'HasPermissionAny': 'GLOBAL',
2001 'HasPermissionAny': 'GLOBAL',
1985 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
2002 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
1986 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
2003 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
1987 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
2004 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
1988 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
2005 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
1989 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
2006 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
1990 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
2007 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
1991 }.get(cls_name, '?:%s' % cls_name)
2008 }.get(cls_name, '?:%s' % cls_name)
1992
2009
1993 def check_permissions(self, user):
2010 def check_permissions(self, user):
1994 """Dummy function for overriding"""
2011 """Dummy function for overriding"""
1995 raise Exception('You have to write this function in child class')
2012 raise Exception('You have to write this function in child class')
1996
2013
1997
2014
1998 class HasPermissionAll(PermsFunction):
2015 class HasPermissionAll(PermsFunction):
1999 def check_permissions(self, user):
2016 def check_permissions(self, user):
2000 perms = user.permissions_with_scope({})
2017 perms = user.permissions_with_scope({})
2001 if self.required_perms.issubset(perms.get('global')):
2018 if self.required_perms.issubset(perms.get('global')):
2002 return True
2019 return True
2003 return False
2020 return False
2004
2021
2005
2022
2006 class HasPermissionAny(PermsFunction):
2023 class HasPermissionAny(PermsFunction):
2007 def check_permissions(self, user):
2024 def check_permissions(self, user):
2008 perms = user.permissions_with_scope({})
2025 perms = user.permissions_with_scope({})
2009 if self.required_perms.intersection(perms.get('global')):
2026 if self.required_perms.intersection(perms.get('global')):
2010 return True
2027 return True
2011 return False
2028 return False
2012
2029
2013
2030
2014 class HasRepoPermissionAll(PermsFunction):
2031 class HasRepoPermissionAll(PermsFunction):
2015 def __call__(self, repo_name=None, check_location='', user=None):
2032 def __call__(self, repo_name=None, check_location='', user=None):
2016 self.repo_name = repo_name
2033 self.repo_name = repo_name
2017 return super(HasRepoPermissionAll, self).__call__(check_location, user)
2034 return super(HasRepoPermissionAll, self).__call__(check_location, user)
2018
2035
2019 def _get_repo_name(self):
2036 def _get_repo_name(self):
2020 if not self.repo_name:
2037 if not self.repo_name:
2021 _request = self._get_request()
2038 _request = self._get_request()
2022 self.repo_name = get_repo_slug(_request)
2039 self.repo_name = get_repo_slug(_request)
2023 return self.repo_name
2040 return self.repo_name
2024
2041
2025 def check_permissions(self, user):
2042 def check_permissions(self, user):
2026 self.repo_name = self._get_repo_name()
2043 self.repo_name = self._get_repo_name()
2027 perms = user.permissions
2044 perms = user.permissions
2028 try:
2045 try:
2029 user_perms = {perms['repositories'][self.repo_name]}
2046 user_perms = {perms['repositories'][self.repo_name]}
2030 except KeyError:
2047 except KeyError:
2031 return False
2048 return False
2032 if self.required_perms.issubset(user_perms):
2049 if self.required_perms.issubset(user_perms):
2033 return True
2050 return True
2034 return False
2051 return False
2035
2052
2036
2053
2037 class HasRepoPermissionAny(PermsFunction):
2054 class HasRepoPermissionAny(PermsFunction):
2038 def __call__(self, repo_name=None, check_location='', user=None):
2055 def __call__(self, repo_name=None, check_location='', user=None):
2039 self.repo_name = repo_name
2056 self.repo_name = repo_name
2040 return super(HasRepoPermissionAny, self).__call__(check_location, user)
2057 return super(HasRepoPermissionAny, self).__call__(check_location, user)
2041
2058
2042 def _get_repo_name(self):
2059 def _get_repo_name(self):
2043 if not self.repo_name:
2060 if not self.repo_name:
2044 _request = self._get_request()
2061 _request = self._get_request()
2045 self.repo_name = get_repo_slug(_request)
2062 self.repo_name = get_repo_slug(_request)
2046 return self.repo_name
2063 return self.repo_name
2047
2064
2048 def check_permissions(self, user):
2065 def check_permissions(self, user):
2049 self.repo_name = self._get_repo_name()
2066 self.repo_name = self._get_repo_name()
2050 perms = user.permissions
2067 perms = user.permissions
2051 try:
2068 try:
2052 user_perms = {perms['repositories'][self.repo_name]}
2069 user_perms = {perms['repositories'][self.repo_name]}
2053 except KeyError:
2070 except KeyError:
2054 return False
2071 return False
2055 if self.required_perms.intersection(user_perms):
2072 if self.required_perms.intersection(user_perms):
2056 return True
2073 return True
2057 return False
2074 return False
2058
2075
2059
2076
2060 class HasRepoGroupPermissionAny(PermsFunction):
2077 class HasRepoGroupPermissionAny(PermsFunction):
2061 def __call__(self, group_name=None, check_location='', user=None):
2078 def __call__(self, group_name=None, check_location='', user=None):
2062 self.repo_group_name = group_name
2079 self.repo_group_name = group_name
2063 return super(HasRepoGroupPermissionAny, self).__call__(
2080 return super(HasRepoGroupPermissionAny, self).__call__(
2064 check_location, user)
2081 check_location, user)
2065
2082
2066 def check_permissions(self, user):
2083 def check_permissions(self, user):
2067 perms = user.permissions
2084 perms = user.permissions
2068 try:
2085 try:
2069 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2086 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2070 except KeyError:
2087 except KeyError:
2071 return False
2088 return False
2072 if self.required_perms.intersection(user_perms):
2089 if self.required_perms.intersection(user_perms):
2073 return True
2090 return True
2074 return False
2091 return False
2075
2092
2076
2093
2077 class HasRepoGroupPermissionAll(PermsFunction):
2094 class HasRepoGroupPermissionAll(PermsFunction):
2078 def __call__(self, group_name=None, check_location='', user=None):
2095 def __call__(self, group_name=None, check_location='', user=None):
2079 self.repo_group_name = group_name
2096 self.repo_group_name = group_name
2080 return super(HasRepoGroupPermissionAll, self).__call__(
2097 return super(HasRepoGroupPermissionAll, self).__call__(
2081 check_location, user)
2098 check_location, user)
2082
2099
2083 def check_permissions(self, user):
2100 def check_permissions(self, user):
2084 perms = user.permissions
2101 perms = user.permissions
2085 try:
2102 try:
2086 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2103 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2087 except KeyError:
2104 except KeyError:
2088 return False
2105 return False
2089 if self.required_perms.issubset(user_perms):
2106 if self.required_perms.issubset(user_perms):
2090 return True
2107 return True
2091 return False
2108 return False
2092
2109
2093
2110
2094 class HasUserGroupPermissionAny(PermsFunction):
2111 class HasUserGroupPermissionAny(PermsFunction):
2095 def __call__(self, user_group_name=None, check_location='', user=None):
2112 def __call__(self, user_group_name=None, check_location='', user=None):
2096 self.user_group_name = user_group_name
2113 self.user_group_name = user_group_name
2097 return super(HasUserGroupPermissionAny, self).__call__(
2114 return super(HasUserGroupPermissionAny, self).__call__(
2098 check_location, user)
2115 check_location, user)
2099
2116
2100 def check_permissions(self, user):
2117 def check_permissions(self, user):
2101 perms = user.permissions
2118 perms = user.permissions
2102 try:
2119 try:
2103 user_perms = {perms['user_groups'][self.user_group_name]}
2120 user_perms = {perms['user_groups'][self.user_group_name]}
2104 except KeyError:
2121 except KeyError:
2105 return False
2122 return False
2106 if self.required_perms.intersection(user_perms):
2123 if self.required_perms.intersection(user_perms):
2107 return True
2124 return True
2108 return False
2125 return False
2109
2126
2110
2127
2111 class HasUserGroupPermissionAll(PermsFunction):
2128 class HasUserGroupPermissionAll(PermsFunction):
2112 def __call__(self, user_group_name=None, check_location='', user=None):
2129 def __call__(self, user_group_name=None, check_location='', user=None):
2113 self.user_group_name = user_group_name
2130 self.user_group_name = user_group_name
2114 return super(HasUserGroupPermissionAll, self).__call__(
2131 return super(HasUserGroupPermissionAll, self).__call__(
2115 check_location, user)
2132 check_location, user)
2116
2133
2117 def check_permissions(self, user):
2134 def check_permissions(self, user):
2118 perms = user.permissions
2135 perms = user.permissions
2119 try:
2136 try:
2120 user_perms = {perms['user_groups'][self.user_group_name]}
2137 user_perms = {perms['user_groups'][self.user_group_name]}
2121 except KeyError:
2138 except KeyError:
2122 return False
2139 return False
2123 if self.required_perms.issubset(user_perms):
2140 if self.required_perms.issubset(user_perms):
2124 return True
2141 return True
2125 return False
2142 return False
2126
2143
2127
2144
2128 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
2145 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
2129 class HasPermissionAnyMiddleware(object):
2146 class HasPermissionAnyMiddleware(object):
2130 def __init__(self, *perms):
2147 def __init__(self, *perms):
2131 self.required_perms = set(perms)
2148 self.required_perms = set(perms)
2132
2149
2133 def __call__(self, auth_user, repo_name):
2150 def __call__(self, auth_user, repo_name):
2134 # repo_name MUST be unicode, since we handle keys in permission
2151 # repo_name MUST be unicode, since we handle keys in permission
2135 # dict by unicode
2152 # dict by unicode
2136 repo_name = safe_unicode(repo_name)
2153 repo_name = safe_unicode(repo_name)
2137 log.debug(
2154 log.debug(
2138 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
2155 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
2139 self.required_perms, auth_user, repo_name)
2156 self.required_perms, auth_user, repo_name)
2140
2157
2141 if self.check_permissions(auth_user, repo_name):
2158 if self.check_permissions(auth_user, repo_name):
2142 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
2159 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
2143 repo_name, auth_user, 'PermissionMiddleware')
2160 repo_name, auth_user, 'PermissionMiddleware')
2144 return True
2161 return True
2145
2162
2146 else:
2163 else:
2147 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
2164 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
2148 repo_name, auth_user, 'PermissionMiddleware')
2165 repo_name, auth_user, 'PermissionMiddleware')
2149 return False
2166 return False
2150
2167
2151 def check_permissions(self, user, repo_name):
2168 def check_permissions(self, user, repo_name):
2152 perms = user.permissions_with_scope({'repo_name': repo_name})
2169 perms = user.permissions_with_scope({'repo_name': repo_name})
2153
2170
2154 try:
2171 try:
2155 user_perms = {perms['repositories'][repo_name]}
2172 user_perms = {perms['repositories'][repo_name]}
2156 except Exception:
2173 except Exception:
2157 log.exception('Error while accessing user permissions')
2174 log.exception('Error while accessing user permissions')
2158 return False
2175 return False
2159
2176
2160 if self.required_perms.intersection(user_perms):
2177 if self.required_perms.intersection(user_perms):
2161 return True
2178 return True
2162 return False
2179 return False
2163
2180
2164
2181
2165 # SPECIAL VERSION TO HANDLE API AUTH
2182 # SPECIAL VERSION TO HANDLE API AUTH
2166 class _BaseApiPerm(object):
2183 class _BaseApiPerm(object):
2167 def __init__(self, *perms):
2184 def __init__(self, *perms):
2168 self.required_perms = set(perms)
2185 self.required_perms = set(perms)
2169
2186
2170 def __call__(self, check_location=None, user=None, repo_name=None,
2187 def __call__(self, check_location=None, user=None, repo_name=None,
2171 group_name=None, user_group_name=None):
2188 group_name=None, user_group_name=None):
2172 cls_name = self.__class__.__name__
2189 cls_name = self.__class__.__name__
2173 check_scope = 'global:%s' % (self.required_perms,)
2190 check_scope = 'global:%s' % (self.required_perms,)
2174 if repo_name:
2191 if repo_name:
2175 check_scope += ', repo_name:%s' % (repo_name,)
2192 check_scope += ', repo_name:%s' % (repo_name,)
2176
2193
2177 if group_name:
2194 if group_name:
2178 check_scope += ', repo_group_name:%s' % (group_name,)
2195 check_scope += ', repo_group_name:%s' % (group_name,)
2179
2196
2180 if user_group_name:
2197 if user_group_name:
2181 check_scope += ', user_group_name:%s' % (user_group_name,)
2198 check_scope += ', user_group_name:%s' % (user_group_name,)
2182
2199
2183 log.debug('checking cls:%s %s %s @ %s',
2200 log.debug('checking cls:%s %s %s @ %s',
2184 cls_name, self.required_perms, check_scope, check_location)
2201 cls_name, self.required_perms, check_scope, check_location)
2185 if not user:
2202 if not user:
2186 log.debug('Empty User passed into arguments')
2203 log.debug('Empty User passed into arguments')
2187 return False
2204 return False
2188
2205
2189 # process user
2206 # process user
2190 if not isinstance(user, AuthUser):
2207 if not isinstance(user, AuthUser):
2191 user = AuthUser(user.user_id)
2208 user = AuthUser(user.user_id)
2192 if not check_location:
2209 if not check_location:
2193 check_location = 'unspecified'
2210 check_location = 'unspecified'
2194 if self.check_permissions(user.permissions, repo_name, group_name,
2211 if self.check_permissions(user.permissions, repo_name, group_name,
2195 user_group_name):
2212 user_group_name):
2196 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2213 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2197 check_scope, user, check_location)
2214 check_scope, user, check_location)
2198 return True
2215 return True
2199
2216
2200 else:
2217 else:
2201 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2218 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2202 check_scope, user, check_location)
2219 check_scope, user, check_location)
2203 return False
2220 return False
2204
2221
2205 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2222 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2206 user_group_name=None):
2223 user_group_name=None):
2207 """
2224 """
2208 implement in child class should return True if permissions are ok,
2225 implement in child class should return True if permissions are ok,
2209 False otherwise
2226 False otherwise
2210
2227
2211 :param perm_defs: dict with permission definitions
2228 :param perm_defs: dict with permission definitions
2212 :param repo_name: repo name
2229 :param repo_name: repo name
2213 """
2230 """
2214 raise NotImplementedError()
2231 raise NotImplementedError()
2215
2232
2216
2233
2217 class HasPermissionAllApi(_BaseApiPerm):
2234 class HasPermissionAllApi(_BaseApiPerm):
2218 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2235 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2219 user_group_name=None):
2236 user_group_name=None):
2220 if self.required_perms.issubset(perm_defs.get('global')):
2237 if self.required_perms.issubset(perm_defs.get('global')):
2221 return True
2238 return True
2222 return False
2239 return False
2223
2240
2224
2241
2225 class HasPermissionAnyApi(_BaseApiPerm):
2242 class HasPermissionAnyApi(_BaseApiPerm):
2226 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2243 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2227 user_group_name=None):
2244 user_group_name=None):
2228 if self.required_perms.intersection(perm_defs.get('global')):
2245 if self.required_perms.intersection(perm_defs.get('global')):
2229 return True
2246 return True
2230 return False
2247 return False
2231
2248
2232
2249
2233 class HasRepoPermissionAllApi(_BaseApiPerm):
2250 class HasRepoPermissionAllApi(_BaseApiPerm):
2234 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2251 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2235 user_group_name=None):
2252 user_group_name=None):
2236 try:
2253 try:
2237 _user_perms = {perm_defs['repositories'][repo_name]}
2254 _user_perms = {perm_defs['repositories'][repo_name]}
2238 except KeyError:
2255 except KeyError:
2239 log.warning(traceback.format_exc())
2256 log.warning(traceback.format_exc())
2240 return False
2257 return False
2241 if self.required_perms.issubset(_user_perms):
2258 if self.required_perms.issubset(_user_perms):
2242 return True
2259 return True
2243 return False
2260 return False
2244
2261
2245
2262
2246 class HasRepoPermissionAnyApi(_BaseApiPerm):
2263 class HasRepoPermissionAnyApi(_BaseApiPerm):
2247 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2264 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2248 user_group_name=None):
2265 user_group_name=None):
2249 try:
2266 try:
2250 _user_perms = {perm_defs['repositories'][repo_name]}
2267 _user_perms = {perm_defs['repositories'][repo_name]}
2251 except KeyError:
2268 except KeyError:
2252 log.warning(traceback.format_exc())
2269 log.warning(traceback.format_exc())
2253 return False
2270 return False
2254 if self.required_perms.intersection(_user_perms):
2271 if self.required_perms.intersection(_user_perms):
2255 return True
2272 return True
2256 return False
2273 return False
2257
2274
2258
2275
2259 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2276 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2260 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2277 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2261 user_group_name=None):
2278 user_group_name=None):
2262 try:
2279 try:
2263 _user_perms = {perm_defs['repositories_groups'][group_name]}
2280 _user_perms = {perm_defs['repositories_groups'][group_name]}
2264 except KeyError:
2281 except KeyError:
2265 log.warning(traceback.format_exc())
2282 log.warning(traceback.format_exc())
2266 return False
2283 return False
2267 if self.required_perms.intersection(_user_perms):
2284 if self.required_perms.intersection(_user_perms):
2268 return True
2285 return True
2269 return False
2286 return False
2270
2287
2271
2288
2272 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2289 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2273 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2290 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2274 user_group_name=None):
2291 user_group_name=None):
2275 try:
2292 try:
2276 _user_perms = {perm_defs['repositories_groups'][group_name]}
2293 _user_perms = {perm_defs['repositories_groups'][group_name]}
2277 except KeyError:
2294 except KeyError:
2278 log.warning(traceback.format_exc())
2295 log.warning(traceback.format_exc())
2279 return False
2296 return False
2280 if self.required_perms.issubset(_user_perms):
2297 if self.required_perms.issubset(_user_perms):
2281 return True
2298 return True
2282 return False
2299 return False
2283
2300
2284
2301
2285 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2302 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2286 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2303 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2287 user_group_name=None):
2304 user_group_name=None):
2288 try:
2305 try:
2289 _user_perms = {perm_defs['user_groups'][user_group_name]}
2306 _user_perms = {perm_defs['user_groups'][user_group_name]}
2290 except KeyError:
2307 except KeyError:
2291 log.warning(traceback.format_exc())
2308 log.warning(traceback.format_exc())
2292 return False
2309 return False
2293 if self.required_perms.intersection(_user_perms):
2310 if self.required_perms.intersection(_user_perms):
2294 return True
2311 return True
2295 return False
2312 return False
2296
2313
2297
2314
2298 def check_ip_access(source_ip, allowed_ips=None):
2315 def check_ip_access(source_ip, allowed_ips=None):
2299 """
2316 """
2300 Checks if source_ip is a subnet of any of allowed_ips.
2317 Checks if source_ip is a subnet of any of allowed_ips.
2301
2318
2302 :param source_ip:
2319 :param source_ip:
2303 :param allowed_ips: list of allowed ips together with mask
2320 :param allowed_ips: list of allowed ips together with mask
2304 """
2321 """
2305 log.debug('checking if ip:%s is subnet of %s', source_ip, allowed_ips)
2322 log.debug('checking if ip:%s is subnet of %s', source_ip, allowed_ips)
2306 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2323 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2307 if isinstance(allowed_ips, (tuple, list, set)):
2324 if isinstance(allowed_ips, (tuple, list, set)):
2308 for ip in allowed_ips:
2325 for ip in allowed_ips:
2309 ip = safe_unicode(ip)
2326 ip = safe_unicode(ip)
2310 try:
2327 try:
2311 network_address = ipaddress.ip_network(ip, strict=False)
2328 network_address = ipaddress.ip_network(ip, strict=False)
2312 if source_ip_address in network_address:
2329 if source_ip_address in network_address:
2313 log.debug('IP %s is network %s', source_ip_address, network_address)
2330 log.debug('IP %s is network %s', source_ip_address, network_address)
2314 return True
2331 return True
2315 # for any case we cannot determine the IP, don't crash just
2332 # for any case we cannot determine the IP, don't crash just
2316 # skip it and log as error, we want to say forbidden still when
2333 # skip it and log as error, we want to say forbidden still when
2317 # sending bad IP
2334 # sending bad IP
2318 except Exception:
2335 except Exception:
2319 log.error(traceback.format_exc())
2336 log.error(traceback.format_exc())
2320 continue
2337 continue
2321 return False
2338 return False
2322
2339
2323
2340
2324 def get_cython_compat_decorator(wrapper, func):
2341 def get_cython_compat_decorator(wrapper, func):
2325 """
2342 """
2326 Creates a cython compatible decorator. The previously used
2343 Creates a cython compatible decorator. The previously used
2327 decorator.decorator() function seems to be incompatible with cython.
2344 decorator.decorator() function seems to be incompatible with cython.
2328
2345
2329 :param wrapper: __wrapper method of the decorator class
2346 :param wrapper: __wrapper method of the decorator class
2330 :param func: decorated function
2347 :param func: decorated function
2331 """
2348 """
2332 @wraps(func)
2349 @wraps(func)
2333 def local_wrapper(*args, **kwds):
2350 def local_wrapper(*args, **kwds):
2334 return wrapper(func, *args, **kwds)
2351 return wrapper(func, *args, **kwds)
2335 local_wrapper.__wrapped__ = func
2352 local_wrapper.__wrapped__ = func
2336 return local_wrapper
2353 return local_wrapper
2337
2354
2338
2355
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,1053 +1,1072 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import re
22 import re
23 import shutil
23 import shutil
24 import time
24 import time
25 import logging
25 import logging
26 import traceback
26 import traceback
27 import datetime
27 import datetime
28
28
29 from pyramid.threadlocal import get_current_request
29 from pyramid.threadlocal import get_current_request
30 from zope.cachedescriptors.property import Lazy as LazyProperty
30 from zope.cachedescriptors.property import Lazy as LazyProperty
31
31
32 from rhodecode import events
32 from rhodecode import events
33 from rhodecode.lib.auth import HasUserGroupPermissionAny
33 from rhodecode.lib.auth import HasUserGroupPermissionAny
34 from rhodecode.lib.caching_query import FromCache
34 from rhodecode.lib.caching_query import FromCache
35 from rhodecode.lib.exceptions import AttachedForksError, AttachedPullRequestsError
35 from rhodecode.lib.exceptions import AttachedForksError, AttachedPullRequestsError
36 from rhodecode.lib.hooks_base import log_delete_repository
36 from rhodecode.lib.hooks_base import log_delete_repository
37 from rhodecode.lib.user_log_filter import user_log_filter
37 from rhodecode.lib.user_log_filter import user_log_filter
38 from rhodecode.lib.utils import make_db_config
38 from rhodecode.lib.utils import make_db_config
39 from rhodecode.lib.utils2 import (
39 from rhodecode.lib.utils2 import (
40 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
40 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
41 get_current_rhodecode_user, safe_int, datetime_to_time,
41 get_current_rhodecode_user, safe_int, datetime_to_time,
42 action_logger_generic)
42 action_logger_generic)
43 from rhodecode.lib.vcs.backends import get_backend
43 from rhodecode.lib.vcs.backends import get_backend
44 from rhodecode.model import BaseModel
44 from rhodecode.model import BaseModel
45 from rhodecode.model.db import (
45 from rhodecode.model.db import (
46 _hash_key, joinedload, or_, Repository, UserRepoToPerm, UserGroupRepoToPerm,
46 _hash_key, joinedload, or_, Repository, UserRepoToPerm, UserGroupRepoToPerm,
47 UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission,
47 UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission,
48 Statistics, UserGroup, RepoGroup, RepositoryField, UserLog)
48 Statistics, UserGroup, RepoGroup, RepositoryField, UserLog)
49
49
50 from rhodecode.model.settings import VcsSettingsModel
50 from rhodecode.model.settings import VcsSettingsModel
51
51
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 class RepoModel(BaseModel):
56 class RepoModel(BaseModel):
57
57
58 cls = Repository
58 cls = Repository
59
59
60 def _get_user_group(self, users_group):
60 def _get_user_group(self, users_group):
61 return self._get_instance(UserGroup, users_group,
61 return self._get_instance(UserGroup, users_group,
62 callback=UserGroup.get_by_group_name)
62 callback=UserGroup.get_by_group_name)
63
63
64 def _get_repo_group(self, repo_group):
64 def _get_repo_group(self, repo_group):
65 return self._get_instance(RepoGroup, repo_group,
65 return self._get_instance(RepoGroup, repo_group,
66 callback=RepoGroup.get_by_group_name)
66 callback=RepoGroup.get_by_group_name)
67
67
68 def _create_default_perms(self, repository, private):
68 def _create_default_perms(self, repository, private):
69 # create default permission
69 # create default permission
70 default = 'repository.read'
70 default = 'repository.read'
71 def_user = User.get_default_user()
71 def_user = User.get_default_user()
72 for p in def_user.user_perms:
72 for p in def_user.user_perms:
73 if p.permission.permission_name.startswith('repository.'):
73 if p.permission.permission_name.startswith('repository.'):
74 default = p.permission.permission_name
74 default = p.permission.permission_name
75 break
75 break
76
76
77 default_perm = 'repository.none' if private else default
77 default_perm = 'repository.none' if private else default
78
78
79 repo_to_perm = UserRepoToPerm()
79 repo_to_perm = UserRepoToPerm()
80 repo_to_perm.permission = Permission.get_by_key(default_perm)
80 repo_to_perm.permission = Permission.get_by_key(default_perm)
81
81
82 repo_to_perm.repository = repository
82 repo_to_perm.repository = repository
83 repo_to_perm.user_id = def_user.user_id
83 repo_to_perm.user_id = def_user.user_id
84
84
85 return repo_to_perm
85 return repo_to_perm
86
86
87 @LazyProperty
87 @LazyProperty
88 def repos_path(self):
88 def repos_path(self):
89 """
89 """
90 Gets the repositories root path from database
90 Gets the repositories root path from database
91 """
91 """
92 settings_model = VcsSettingsModel(sa=self.sa)
92 settings_model = VcsSettingsModel(sa=self.sa)
93 return settings_model.get_repos_location()
93 return settings_model.get_repos_location()
94
94
95 def get(self, repo_id):
95 def get(self, repo_id):
96 repo = self.sa.query(Repository) \
96 repo = self.sa.query(Repository) \
97 .filter(Repository.repo_id == repo_id)
97 .filter(Repository.repo_id == repo_id)
98
98
99 return repo.scalar()
99 return repo.scalar()
100
100
101 def get_repo(self, repository):
101 def get_repo(self, repository):
102 return self._get_repo(repository)
102 return self._get_repo(repository)
103
103
104 def get_by_repo_name(self, repo_name, cache=False):
104 def get_by_repo_name(self, repo_name, cache=False):
105 repo = self.sa.query(Repository) \
105 repo = self.sa.query(Repository) \
106 .filter(Repository.repo_name == repo_name)
106 .filter(Repository.repo_name == repo_name)
107
107
108 if cache:
108 if cache:
109 name_key = _hash_key(repo_name)
109 name_key = _hash_key(repo_name)
110 repo = repo.options(
110 repo = repo.options(
111 FromCache("sql_cache_short", "get_repo_%s" % name_key))
111 FromCache("sql_cache_short", "get_repo_%s" % name_key))
112 return repo.scalar()
112 return repo.scalar()
113
113
114 def _extract_id_from_repo_name(self, repo_name):
114 def _extract_id_from_repo_name(self, repo_name):
115 if repo_name.startswith('/'):
115 if repo_name.startswith('/'):
116 repo_name = repo_name.lstrip('/')
116 repo_name = repo_name.lstrip('/')
117 by_id_match = re.match(r'^_(\d{1,})', repo_name)
117 by_id_match = re.match(r'^_(\d{1,})', repo_name)
118 if by_id_match:
118 if by_id_match:
119 return by_id_match.groups()[0]
119 return by_id_match.groups()[0]
120
120
121 def get_repo_by_id(self, repo_name):
121 def get_repo_by_id(self, repo_name):
122 """
122 """
123 Extracts repo_name by id from special urls.
123 Extracts repo_name by id from special urls.
124 Example url is _11/repo_name
124 Example url is _11/repo_name
125
125
126 :param repo_name:
126 :param repo_name:
127 :return: repo object if matched else None
127 :return: repo object if matched else None
128 """
128 """
129
129
130 try:
130 try:
131 _repo_id = self._extract_id_from_repo_name(repo_name)
131 _repo_id = self._extract_id_from_repo_name(repo_name)
132 if _repo_id:
132 if _repo_id:
133 return self.get(_repo_id)
133 return self.get(_repo_id)
134 except Exception:
134 except Exception:
135 log.exception('Failed to extract repo_name from URL')
135 log.exception('Failed to extract repo_name from URL')
136
136
137 return None
137 return None
138
138
139 def get_repos_for_root(self, root, traverse=False):
139 def get_repos_for_root(self, root, traverse=False):
140 if traverse:
140 if traverse:
141 like_expression = u'{}%'.format(safe_unicode(root))
141 like_expression = u'{}%'.format(safe_unicode(root))
142 repos = Repository.query().filter(
142 repos = Repository.query().filter(
143 Repository.repo_name.like(like_expression)).all()
143 Repository.repo_name.like(like_expression)).all()
144 else:
144 else:
145 if root and not isinstance(root, RepoGroup):
145 if root and not isinstance(root, RepoGroup):
146 raise ValueError(
146 raise ValueError(
147 'Root must be an instance '
147 'Root must be an instance '
148 'of RepoGroup, got:{} instead'.format(type(root)))
148 'of RepoGroup, got:{} instead'.format(type(root)))
149 repos = Repository.query().filter(Repository.group == root).all()
149 repos = Repository.query().filter(Repository.group == root).all()
150 return repos
150 return repos
151
151
152 def get_url(self, repo, request=None, permalink=False):
152 def get_url(self, repo, request=None, permalink=False):
153 if not request:
153 if not request:
154 request = get_current_request()
154 request = get_current_request()
155
155
156 if not request:
156 if not request:
157 return
157 return
158
158
159 if permalink:
159 if permalink:
160 return request.route_url(
160 return request.route_url(
161 'repo_summary', repo_name='_{}'.format(safe_str(repo.repo_id)))
161 'repo_summary', repo_name='_{}'.format(safe_str(repo.repo_id)))
162 else:
162 else:
163 return request.route_url(
163 return request.route_url(
164 'repo_summary', repo_name=safe_str(repo.repo_name))
164 'repo_summary', repo_name=safe_str(repo.repo_name))
165
165
166 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
166 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
167 if not request:
167 if not request:
168 request = get_current_request()
168 request = get_current_request()
169
169
170 if not request:
170 if not request:
171 return
171 return
172
172
173 if permalink:
173 if permalink:
174 return request.route_url(
174 return request.route_url(
175 'repo_commit', repo_name=safe_str(repo.repo_id),
175 'repo_commit', repo_name=safe_str(repo.repo_id),
176 commit_id=commit_id)
176 commit_id=commit_id)
177
177
178 else:
178 else:
179 return request.route_url(
179 return request.route_url(
180 'repo_commit', repo_name=safe_str(repo.repo_name),
180 'repo_commit', repo_name=safe_str(repo.repo_name),
181 commit_id=commit_id)
181 commit_id=commit_id)
182
182
183 def get_repo_log(self, repo, filter_term):
183 def get_repo_log(self, repo, filter_term):
184 repo_log = UserLog.query()\
184 repo_log = UserLog.query()\
185 .filter(or_(UserLog.repository_id == repo.repo_id,
185 .filter(or_(UserLog.repository_id == repo.repo_id,
186 UserLog.repository_name == repo.repo_name))\
186 UserLog.repository_name == repo.repo_name))\
187 .options(joinedload(UserLog.user))\
187 .options(joinedload(UserLog.user))\
188 .options(joinedload(UserLog.repository))\
188 .options(joinedload(UserLog.repository))\
189 .order_by(UserLog.action_date.desc())
189 .order_by(UserLog.action_date.desc())
190
190
191 repo_log = user_log_filter(repo_log, filter_term)
191 repo_log = user_log_filter(repo_log, filter_term)
192 return repo_log
192 return repo_log
193
193
194 @classmethod
194 @classmethod
195 def update_repoinfo(cls, repositories=None):
195 def update_repoinfo(cls, repositories=None):
196 if not repositories:
196 if not repositories:
197 repositories = Repository.getAll()
197 repositories = Repository.getAll()
198 for repo in repositories:
198 for repo in repositories:
199 repo.update_commit_cache()
199 repo.update_commit_cache()
200
200
201 def get_repos_as_dict(self, repo_list=None, admin=False,
201 def get_repos_as_dict(self, repo_list=None, admin=False,
202 super_user_actions=False):
202 super_user_actions=False):
203 _render = get_current_request().get_partial_renderer(
203 _render = get_current_request().get_partial_renderer(
204 'rhodecode:templates/data_table/_dt_elements.mako')
204 'rhodecode:templates/data_table/_dt_elements.mako')
205 c = _render.get_call_context()
205 c = _render.get_call_context()
206
206
207 def quick_menu(repo_name):
207 def quick_menu(repo_name):
208 return _render('quick_menu', repo_name)
208 return _render('quick_menu', repo_name)
209
209
210 def repo_lnk(name, rtype, rstate, private, fork_of):
210 def repo_lnk(name, rtype, rstate, private, archived, fork_of):
211 return _render('repo_name', name, rtype, rstate, private, fork_of,
211 return _render('repo_name', name, rtype, rstate, private, archived, fork_of,
212 short_name=not admin, admin=False)
212 short_name=not admin, admin=False)
213
213
214 def last_change(last_change):
214 def last_change(last_change):
215 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
215 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
216 last_change = last_change + datetime.timedelta(seconds=
216 last_change = last_change + datetime.timedelta(seconds=
217 (datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
217 (datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
218 return _render("last_change", last_change)
218 return _render("last_change", last_change)
219
219
220 def rss_lnk(repo_name):
220 def rss_lnk(repo_name):
221 return _render("rss", repo_name)
221 return _render("rss", repo_name)
222
222
223 def atom_lnk(repo_name):
223 def atom_lnk(repo_name):
224 return _render("atom", repo_name)
224 return _render("atom", repo_name)
225
225
226 def last_rev(repo_name, cs_cache):
226 def last_rev(repo_name, cs_cache):
227 return _render('revision', repo_name, cs_cache.get('revision'),
227 return _render('revision', repo_name, cs_cache.get('revision'),
228 cs_cache.get('raw_id'), cs_cache.get('author'),
228 cs_cache.get('raw_id'), cs_cache.get('author'),
229 cs_cache.get('message'), cs_cache.get('date'))
229 cs_cache.get('message'), cs_cache.get('date'))
230
230
231 def desc(desc):
231 def desc(desc):
232 return _render('repo_desc', desc, c.visual.stylify_metatags)
232 return _render('repo_desc', desc, c.visual.stylify_metatags)
233
233
234 def state(repo_state):
234 def state(repo_state):
235 return _render("repo_state", repo_state)
235 return _render("repo_state", repo_state)
236
236
237 def repo_actions(repo_name):
237 def repo_actions(repo_name):
238 return _render('repo_actions', repo_name, super_user_actions)
238 return _render('repo_actions', repo_name, super_user_actions)
239
239
240 def user_profile(username):
240 def user_profile(username):
241 return _render('user_profile', username)
241 return _render('user_profile', username)
242
242
243 repos_data = []
243 repos_data = []
244 for repo in repo_list:
244 for repo in repo_list:
245 cs_cache = repo.changeset_cache
245 cs_cache = repo.changeset_cache
246 row = {
246 row = {
247 "menu": quick_menu(repo.repo_name),
247 "menu": quick_menu(repo.repo_name),
248
248
249 "name": repo_lnk(repo.repo_name, repo.repo_type,
249 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state,
250 repo.repo_state, repo.private, repo.fork),
250 repo.private, repo.archived, repo.fork),
251 "name_raw": repo.repo_name.lower(),
251 "name_raw": repo.repo_name.lower(),
252
252
253 "last_change": last_change(repo.last_db_change),
253 "last_change": last_change(repo.last_db_change),
254 "last_change_raw": datetime_to_time(repo.last_db_change),
254 "last_change_raw": datetime_to_time(repo.last_db_change),
255
255
256 "last_changeset": last_rev(repo.repo_name, cs_cache),
256 "last_changeset": last_rev(repo.repo_name, cs_cache),
257 "last_changeset_raw": cs_cache.get('revision'),
257 "last_changeset_raw": cs_cache.get('revision'),
258
258
259 "desc": desc(repo.description_safe),
259 "desc": desc(repo.description_safe),
260 "owner": user_profile(repo.user.username),
260 "owner": user_profile(repo.user.username),
261
261
262 "state": state(repo.repo_state),
262 "state": state(repo.repo_state),
263 "rss": rss_lnk(repo.repo_name),
263 "rss": rss_lnk(repo.repo_name),
264
264
265 "atom": atom_lnk(repo.repo_name),
265 "atom": atom_lnk(repo.repo_name),
266 }
266 }
267 if admin:
267 if admin:
268 row.update({
268 row.update({
269 "action": repo_actions(repo.repo_name),
269 "action": repo_actions(repo.repo_name),
270 })
270 })
271 repos_data.append(row)
271 repos_data.append(row)
272
272
273 return repos_data
273 return repos_data
274
274
275 def _get_defaults(self, repo_name):
275 def _get_defaults(self, repo_name):
276 """
276 """
277 Gets information about repository, and returns a dict for
277 Gets information about repository, and returns a dict for
278 usage in forms
278 usage in forms
279
279
280 :param repo_name:
280 :param repo_name:
281 """
281 """
282
282
283 repo_info = Repository.get_by_repo_name(repo_name)
283 repo_info = Repository.get_by_repo_name(repo_name)
284
284
285 if repo_info is None:
285 if repo_info is None:
286 return None
286 return None
287
287
288 defaults = repo_info.get_dict()
288 defaults = repo_info.get_dict()
289 defaults['repo_name'] = repo_info.just_name
289 defaults['repo_name'] = repo_info.just_name
290
290
291 groups = repo_info.groups_with_parents
291 groups = repo_info.groups_with_parents
292 parent_group = groups[-1] if groups else None
292 parent_group = groups[-1] if groups else None
293
293
294 # we use -1 as this is how in HTML, we mark an empty group
294 # we use -1 as this is how in HTML, we mark an empty group
295 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
295 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
296
296
297 keys_to_process = (
297 keys_to_process = (
298 {'k': 'repo_type', 'strip': False},
298 {'k': 'repo_type', 'strip': False},
299 {'k': 'repo_enable_downloads', 'strip': True},
299 {'k': 'repo_enable_downloads', 'strip': True},
300 {'k': 'repo_description', 'strip': True},
300 {'k': 'repo_description', 'strip': True},
301 {'k': 'repo_enable_locking', 'strip': True},
301 {'k': 'repo_enable_locking', 'strip': True},
302 {'k': 'repo_landing_rev', 'strip': True},
302 {'k': 'repo_landing_rev', 'strip': True},
303 {'k': 'clone_uri', 'strip': False},
303 {'k': 'clone_uri', 'strip': False},
304 {'k': 'push_uri', 'strip': False},
304 {'k': 'push_uri', 'strip': False},
305 {'k': 'repo_private', 'strip': True},
305 {'k': 'repo_private', 'strip': True},
306 {'k': 'repo_enable_statistics', 'strip': True}
306 {'k': 'repo_enable_statistics', 'strip': True}
307 )
307 )
308
308
309 for item in keys_to_process:
309 for item in keys_to_process:
310 attr = item['k']
310 attr = item['k']
311 if item['strip']:
311 if item['strip']:
312 attr = remove_prefix(item['k'], 'repo_')
312 attr = remove_prefix(item['k'], 'repo_')
313
313
314 val = defaults[attr]
314 val = defaults[attr]
315 if item['k'] == 'repo_landing_rev':
315 if item['k'] == 'repo_landing_rev':
316 val = ':'.join(defaults[attr])
316 val = ':'.join(defaults[attr])
317 defaults[item['k']] = val
317 defaults[item['k']] = val
318 if item['k'] == 'clone_uri':
318 if item['k'] == 'clone_uri':
319 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
319 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
320 if item['k'] == 'push_uri':
320 if item['k'] == 'push_uri':
321 defaults['push_uri_hidden'] = repo_info.push_uri_hidden
321 defaults['push_uri_hidden'] = repo_info.push_uri_hidden
322
322
323 # fill owner
323 # fill owner
324 if repo_info.user:
324 if repo_info.user:
325 defaults.update({'user': repo_info.user.username})
325 defaults.update({'user': repo_info.user.username})
326 else:
326 else:
327 replacement_user = User.get_first_super_admin().username
327 replacement_user = User.get_first_super_admin().username
328 defaults.update({'user': replacement_user})
328 defaults.update({'user': replacement_user})
329
329
330 return defaults
330 return defaults
331
331
332 def update(self, repo, **kwargs):
332 def update(self, repo, **kwargs):
333 try:
333 try:
334 cur_repo = self._get_repo(repo)
334 cur_repo = self._get_repo(repo)
335 source_repo_name = cur_repo.repo_name
335 source_repo_name = cur_repo.repo_name
336 if 'user' in kwargs:
336 if 'user' in kwargs:
337 cur_repo.user = User.get_by_username(kwargs['user'])
337 cur_repo.user = User.get_by_username(kwargs['user'])
338
338
339 if 'repo_group' in kwargs:
339 if 'repo_group' in kwargs:
340 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
340 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
341 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
341 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
342
342
343 update_keys = [
343 update_keys = [
344 (1, 'repo_description'),
344 (1, 'repo_description'),
345 (1, 'repo_landing_rev'),
345 (1, 'repo_landing_rev'),
346 (1, 'repo_private'),
346 (1, 'repo_private'),
347 (1, 'repo_enable_downloads'),
347 (1, 'repo_enable_downloads'),
348 (1, 'repo_enable_locking'),
348 (1, 'repo_enable_locking'),
349 (1, 'repo_enable_statistics'),
349 (1, 'repo_enable_statistics'),
350 (0, 'clone_uri'),
350 (0, 'clone_uri'),
351 (0, 'push_uri'),
351 (0, 'push_uri'),
352 (0, 'fork_id')
352 (0, 'fork_id')
353 ]
353 ]
354 for strip, k in update_keys:
354 for strip, k in update_keys:
355 if k in kwargs:
355 if k in kwargs:
356 val = kwargs[k]
356 val = kwargs[k]
357 if strip:
357 if strip:
358 k = remove_prefix(k, 'repo_')
358 k = remove_prefix(k, 'repo_')
359
359
360 setattr(cur_repo, k, val)
360 setattr(cur_repo, k, val)
361
361
362 new_name = cur_repo.get_new_name(kwargs['repo_name'])
362 new_name = cur_repo.get_new_name(kwargs['repo_name'])
363 cur_repo.repo_name = new_name
363 cur_repo.repo_name = new_name
364
364
365 # if private flag is set, reset default permission to NONE
365 # if private flag is set, reset default permission to NONE
366 if kwargs.get('repo_private'):
366 if kwargs.get('repo_private'):
367 EMPTY_PERM = 'repository.none'
367 EMPTY_PERM = 'repository.none'
368 RepoModel().grant_user_permission(
368 RepoModel().grant_user_permission(
369 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
369 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
370 )
370 )
371
371
372 # handle extra fields
372 # handle extra fields
373 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
373 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
374 kwargs):
374 kwargs):
375 k = RepositoryField.un_prefix_key(field)
375 k = RepositoryField.un_prefix_key(field)
376 ex_field = RepositoryField.get_by_key_name(
376 ex_field = RepositoryField.get_by_key_name(
377 key=k, repo=cur_repo)
377 key=k, repo=cur_repo)
378 if ex_field:
378 if ex_field:
379 ex_field.field_value = kwargs[field]
379 ex_field.field_value = kwargs[field]
380 self.sa.add(ex_field)
380 self.sa.add(ex_field)
381 cur_repo.updated_on = datetime.datetime.now()
381 cur_repo.updated_on = datetime.datetime.now()
382 self.sa.add(cur_repo)
382 self.sa.add(cur_repo)
383
383
384 if source_repo_name != new_name:
384 if source_repo_name != new_name:
385 # rename repository
385 # rename repository
386 self._rename_filesystem_repo(
386 self._rename_filesystem_repo(
387 old=source_repo_name, new=new_name)
387 old=source_repo_name, new=new_name)
388
388
389 return cur_repo
389 return cur_repo
390 except Exception:
390 except Exception:
391 log.error(traceback.format_exc())
391 log.error(traceback.format_exc())
392 raise
392 raise
393
393
394 def _create_repo(self, repo_name, repo_type, description, owner,
394 def _create_repo(self, repo_name, repo_type, description, owner,
395 private=False, clone_uri=None, repo_group=None,
395 private=False, clone_uri=None, repo_group=None,
396 landing_rev='rev:tip', fork_of=None,
396 landing_rev='rev:tip', fork_of=None,
397 copy_fork_permissions=False, enable_statistics=False,
397 copy_fork_permissions=False, enable_statistics=False,
398 enable_locking=False, enable_downloads=False,
398 enable_locking=False, enable_downloads=False,
399 copy_group_permissions=False,
399 copy_group_permissions=False,
400 state=Repository.STATE_PENDING):
400 state=Repository.STATE_PENDING):
401 """
401 """
402 Create repository inside database with PENDING state, this should be
402 Create repository inside database with PENDING state, this should be
403 only executed by create() repo. With exception of importing existing
403 only executed by create() repo. With exception of importing existing
404 repos
404 repos
405 """
405 """
406 from rhodecode.model.scm import ScmModel
406 from rhodecode.model.scm import ScmModel
407
407
408 owner = self._get_user(owner)
408 owner = self._get_user(owner)
409 fork_of = self._get_repo(fork_of)
409 fork_of = self._get_repo(fork_of)
410 repo_group = self._get_repo_group(safe_int(repo_group))
410 repo_group = self._get_repo_group(safe_int(repo_group))
411
411
412 try:
412 try:
413 repo_name = safe_unicode(repo_name)
413 repo_name = safe_unicode(repo_name)
414 description = safe_unicode(description)
414 description = safe_unicode(description)
415 # repo name is just a name of repository
415 # repo name is just a name of repository
416 # while repo_name_full is a full qualified name that is combined
416 # while repo_name_full is a full qualified name that is combined
417 # with name and path of group
417 # with name and path of group
418 repo_name_full = repo_name
418 repo_name_full = repo_name
419 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
419 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
420
420
421 new_repo = Repository()
421 new_repo = Repository()
422 new_repo.repo_state = state
422 new_repo.repo_state = state
423 new_repo.enable_statistics = False
423 new_repo.enable_statistics = False
424 new_repo.repo_name = repo_name_full
424 new_repo.repo_name = repo_name_full
425 new_repo.repo_type = repo_type
425 new_repo.repo_type = repo_type
426 new_repo.user = owner
426 new_repo.user = owner
427 new_repo.group = repo_group
427 new_repo.group = repo_group
428 new_repo.description = description or repo_name
428 new_repo.description = description or repo_name
429 new_repo.private = private
429 new_repo.private = private
430 new_repo.archived = False
430 new_repo.clone_uri = clone_uri
431 new_repo.clone_uri = clone_uri
431 new_repo.landing_rev = landing_rev
432 new_repo.landing_rev = landing_rev
432
433
433 new_repo.enable_statistics = enable_statistics
434 new_repo.enable_statistics = enable_statistics
434 new_repo.enable_locking = enable_locking
435 new_repo.enable_locking = enable_locking
435 new_repo.enable_downloads = enable_downloads
436 new_repo.enable_downloads = enable_downloads
436
437
437 if repo_group:
438 if repo_group:
438 new_repo.enable_locking = repo_group.enable_locking
439 new_repo.enable_locking = repo_group.enable_locking
439
440
440 if fork_of:
441 if fork_of:
441 parent_repo = fork_of
442 parent_repo = fork_of
442 new_repo.fork = parent_repo
443 new_repo.fork = parent_repo
443
444
444 events.trigger(events.RepoPreCreateEvent(new_repo))
445 events.trigger(events.RepoPreCreateEvent(new_repo))
445
446
446 self.sa.add(new_repo)
447 self.sa.add(new_repo)
447
448
448 EMPTY_PERM = 'repository.none'
449 EMPTY_PERM = 'repository.none'
449 if fork_of and copy_fork_permissions:
450 if fork_of and copy_fork_permissions:
450 repo = fork_of
451 repo = fork_of
451 user_perms = UserRepoToPerm.query() \
452 user_perms = UserRepoToPerm.query() \
452 .filter(UserRepoToPerm.repository == repo).all()
453 .filter(UserRepoToPerm.repository == repo).all()
453 group_perms = UserGroupRepoToPerm.query() \
454 group_perms = UserGroupRepoToPerm.query() \
454 .filter(UserGroupRepoToPerm.repository == repo).all()
455 .filter(UserGroupRepoToPerm.repository == repo).all()
455
456
456 for perm in user_perms:
457 for perm in user_perms:
457 UserRepoToPerm.create(
458 UserRepoToPerm.create(
458 perm.user, new_repo, perm.permission)
459 perm.user, new_repo, perm.permission)
459
460
460 for perm in group_perms:
461 for perm in group_perms:
461 UserGroupRepoToPerm.create(
462 UserGroupRepoToPerm.create(
462 perm.users_group, new_repo, perm.permission)
463 perm.users_group, new_repo, perm.permission)
463 # in case we copy permissions and also set this repo to private
464 # in case we copy permissions and also set this repo to private
464 # override the default user permission to make it a private
465 # override the default user permission to make it a private
465 # repo
466 # repo
466 if private:
467 if private:
467 RepoModel(self.sa).grant_user_permission(
468 RepoModel(self.sa).grant_user_permission(
468 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
469 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
469
470
470 elif repo_group and copy_group_permissions:
471 elif repo_group and copy_group_permissions:
471 user_perms = UserRepoGroupToPerm.query() \
472 user_perms = UserRepoGroupToPerm.query() \
472 .filter(UserRepoGroupToPerm.group == repo_group).all()
473 .filter(UserRepoGroupToPerm.group == repo_group).all()
473
474
474 group_perms = UserGroupRepoGroupToPerm.query() \
475 group_perms = UserGroupRepoGroupToPerm.query() \
475 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
476 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
476
477
477 for perm in user_perms:
478 for perm in user_perms:
478 perm_name = perm.permission.permission_name.replace(
479 perm_name = perm.permission.permission_name.replace(
479 'group.', 'repository.')
480 'group.', 'repository.')
480 perm_obj = Permission.get_by_key(perm_name)
481 perm_obj = Permission.get_by_key(perm_name)
481 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
482 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
482
483
483 for perm in group_perms:
484 for perm in group_perms:
484 perm_name = perm.permission.permission_name.replace(
485 perm_name = perm.permission.permission_name.replace(
485 'group.', 'repository.')
486 'group.', 'repository.')
486 perm_obj = Permission.get_by_key(perm_name)
487 perm_obj = Permission.get_by_key(perm_name)
487 UserGroupRepoToPerm.create(
488 UserGroupRepoToPerm.create(
488 perm.users_group, new_repo, perm_obj)
489 perm.users_group, new_repo, perm_obj)
489
490
490 if private:
491 if private:
491 RepoModel(self.sa).grant_user_permission(
492 RepoModel(self.sa).grant_user_permission(
492 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
493 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
493
494
494 else:
495 else:
495 perm_obj = self._create_default_perms(new_repo, private)
496 perm_obj = self._create_default_perms(new_repo, private)
496 self.sa.add(perm_obj)
497 self.sa.add(perm_obj)
497
498
498 # now automatically start following this repository as owner
499 # now automatically start following this repository as owner
499 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
500 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
500 owner.user_id)
501 owner.user_id)
501
502
502 # we need to flush here, in order to check if database won't
503 # we need to flush here, in order to check if database won't
503 # throw any exceptions, create filesystem dirs at the very end
504 # throw any exceptions, create filesystem dirs at the very end
504 self.sa.flush()
505 self.sa.flush()
505 events.trigger(events.RepoCreateEvent(new_repo))
506 events.trigger(events.RepoCreateEvent(new_repo))
506 return new_repo
507 return new_repo
507
508
508 except Exception:
509 except Exception:
509 log.error(traceback.format_exc())
510 log.error(traceback.format_exc())
510 raise
511 raise
511
512
512 def create(self, form_data, cur_user):
513 def create(self, form_data, cur_user):
513 """
514 """
514 Create repository using celery tasks
515 Create repository using celery tasks
515
516
516 :param form_data:
517 :param form_data:
517 :param cur_user:
518 :param cur_user:
518 """
519 """
519 from rhodecode.lib.celerylib import tasks, run_task
520 from rhodecode.lib.celerylib import tasks, run_task
520 return run_task(tasks.create_repo, form_data, cur_user)
521 return run_task(tasks.create_repo, form_data, cur_user)
521
522
522 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
523 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
523 perm_deletions=None, check_perms=True,
524 perm_deletions=None, check_perms=True,
524 cur_user=None):
525 cur_user=None):
525 if not perm_additions:
526 if not perm_additions:
526 perm_additions = []
527 perm_additions = []
527 if not perm_updates:
528 if not perm_updates:
528 perm_updates = []
529 perm_updates = []
529 if not perm_deletions:
530 if not perm_deletions:
530 perm_deletions = []
531 perm_deletions = []
531
532
532 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
533 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
533
534
534 changes = {
535 changes = {
535 'added': [],
536 'added': [],
536 'updated': [],
537 'updated': [],
537 'deleted': []
538 'deleted': []
538 }
539 }
539 # update permissions
540 # update permissions
540 for member_id, perm, member_type in perm_updates:
541 for member_id, perm, member_type in perm_updates:
541 member_id = int(member_id)
542 member_id = int(member_id)
542 if member_type == 'user':
543 if member_type == 'user':
543 member_name = User.get(member_id).username
544 member_name = User.get(member_id).username
544 # this updates also current one if found
545 # this updates also current one if found
545 self.grant_user_permission(
546 self.grant_user_permission(
546 repo=repo, user=member_id, perm=perm)
547 repo=repo, user=member_id, perm=perm)
547 elif member_type == 'user_group':
548 elif member_type == 'user_group':
548 # check if we have permissions to alter this usergroup
549 # check if we have permissions to alter this usergroup
549 member_name = UserGroup.get(member_id).users_group_name
550 member_name = UserGroup.get(member_id).users_group_name
550 if not check_perms or HasUserGroupPermissionAny(
551 if not check_perms or HasUserGroupPermissionAny(
551 *req_perms)(member_name, user=cur_user):
552 *req_perms)(member_name, user=cur_user):
552 self.grant_user_group_permission(
553 self.grant_user_group_permission(
553 repo=repo, group_name=member_id, perm=perm)
554 repo=repo, group_name=member_id, perm=perm)
554 else:
555 else:
555 raise ValueError("member_type must be 'user' or 'user_group' "
556 raise ValueError("member_type must be 'user' or 'user_group' "
556 "got {} instead".format(member_type))
557 "got {} instead".format(member_type))
557 changes['updated'].append({'type': member_type, 'id': member_id,
558 changes['updated'].append({'type': member_type, 'id': member_id,
558 'name': member_name, 'new_perm': perm})
559 'name': member_name, 'new_perm': perm})
559
560
560 # set new permissions
561 # set new permissions
561 for member_id, perm, member_type in perm_additions:
562 for member_id, perm, member_type in perm_additions:
562 member_id = int(member_id)
563 member_id = int(member_id)
563 if member_type == 'user':
564 if member_type == 'user':
564 member_name = User.get(member_id).username
565 member_name = User.get(member_id).username
565 self.grant_user_permission(
566 self.grant_user_permission(
566 repo=repo, user=member_id, perm=perm)
567 repo=repo, user=member_id, perm=perm)
567 elif member_type == 'user_group':
568 elif member_type == 'user_group':
568 # check if we have permissions to alter this usergroup
569 # check if we have permissions to alter this usergroup
569 member_name = UserGroup.get(member_id).users_group_name
570 member_name = UserGroup.get(member_id).users_group_name
570 if not check_perms or HasUserGroupPermissionAny(
571 if not check_perms or HasUserGroupPermissionAny(
571 *req_perms)(member_name, user=cur_user):
572 *req_perms)(member_name, user=cur_user):
572 self.grant_user_group_permission(
573 self.grant_user_group_permission(
573 repo=repo, group_name=member_id, perm=perm)
574 repo=repo, group_name=member_id, perm=perm)
574 else:
575 else:
575 raise ValueError("member_type must be 'user' or 'user_group' "
576 raise ValueError("member_type must be 'user' or 'user_group' "
576 "got {} instead".format(member_type))
577 "got {} instead".format(member_type))
577
578
578 changes['added'].append({'type': member_type, 'id': member_id,
579 changes['added'].append({'type': member_type, 'id': member_id,
579 'name': member_name, 'new_perm': perm})
580 'name': member_name, 'new_perm': perm})
580 # delete permissions
581 # delete permissions
581 for member_id, perm, member_type in perm_deletions:
582 for member_id, perm, member_type in perm_deletions:
582 member_id = int(member_id)
583 member_id = int(member_id)
583 if member_type == 'user':
584 if member_type == 'user':
584 member_name = User.get(member_id).username
585 member_name = User.get(member_id).username
585 self.revoke_user_permission(repo=repo, user=member_id)
586 self.revoke_user_permission(repo=repo, user=member_id)
586 elif member_type == 'user_group':
587 elif member_type == 'user_group':
587 # check if we have permissions to alter this usergroup
588 # check if we have permissions to alter this usergroup
588 member_name = UserGroup.get(member_id).users_group_name
589 member_name = UserGroup.get(member_id).users_group_name
589 if not check_perms or HasUserGroupPermissionAny(
590 if not check_perms or HasUserGroupPermissionAny(
590 *req_perms)(member_name, user=cur_user):
591 *req_perms)(member_name, user=cur_user):
591 self.revoke_user_group_permission(
592 self.revoke_user_group_permission(
592 repo=repo, group_name=member_id)
593 repo=repo, group_name=member_id)
593 else:
594 else:
594 raise ValueError("member_type must be 'user' or 'user_group' "
595 raise ValueError("member_type must be 'user' or 'user_group' "
595 "got {} instead".format(member_type))
596 "got {} instead".format(member_type))
596
597
597 changes['deleted'].append({'type': member_type, 'id': member_id,
598 changes['deleted'].append({'type': member_type, 'id': member_id,
598 'name': member_name, 'new_perm': perm})
599 'name': member_name, 'new_perm': perm})
599 return changes
600 return changes
600
601
601 def create_fork(self, form_data, cur_user):
602 def create_fork(self, form_data, cur_user):
602 """
603 """
603 Simple wrapper into executing celery task for fork creation
604 Simple wrapper into executing celery task for fork creation
604
605
605 :param form_data:
606 :param form_data:
606 :param cur_user:
607 :param cur_user:
607 """
608 """
608 from rhodecode.lib.celerylib import tasks, run_task
609 from rhodecode.lib.celerylib import tasks, run_task
609 return run_task(tasks.create_repo_fork, form_data, cur_user)
610 return run_task(tasks.create_repo_fork, form_data, cur_user)
610
611
612 def archive(self, repo):
613 """
614 Archive given repository. Set archive flag.
615
616 :param repo:
617 """
618 repo = self._get_repo(repo)
619 if repo:
620
621 try:
622 repo.archived = True
623 self.sa.add(repo)
624 self.sa.commit()
625 except Exception:
626 log.error(traceback.format_exc())
627 raise
628
611 def delete(self, repo, forks=None, pull_requests=None, fs_remove=True, cur_user=None):
629 def delete(self, repo, forks=None, pull_requests=None, fs_remove=True, cur_user=None):
612 """
630 """
613 Delete given repository, forks parameter defines what do do with
631 Delete given repository, forks parameter defines what do do with
614 attached forks. Throws AttachedForksError if deleted repo has attached
632 attached forks. Throws AttachedForksError if deleted repo has attached
615 forks
633 forks
616
634
617 :param repo:
635 :param repo:
618 :param forks: str 'delete' or 'detach'
636 :param forks: str 'delete' or 'detach'
637 :param pull_requests: str 'delete' or None
619 :param fs_remove: remove(archive) repo from filesystem
638 :param fs_remove: remove(archive) repo from filesystem
620 """
639 """
621 if not cur_user:
640 if not cur_user:
622 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
641 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
623 repo = self._get_repo(repo)
642 repo = self._get_repo(repo)
624 if repo:
643 if repo:
625 if forks == 'detach':
644 if forks == 'detach':
626 for r in repo.forks:
645 for r in repo.forks:
627 r.fork = None
646 r.fork = None
628 self.sa.add(r)
647 self.sa.add(r)
629 elif forks == 'delete':
648 elif forks == 'delete':
630 for r in repo.forks:
649 for r in repo.forks:
631 self.delete(r, forks='delete')
650 self.delete(r, forks='delete')
632 elif [f for f in repo.forks]:
651 elif [f for f in repo.forks]:
633 raise AttachedForksError()
652 raise AttachedForksError()
634
653
635 # check for pull requests
654 # check for pull requests
636 pr_sources = repo.pull_requests_source
655 pr_sources = repo.pull_requests_source
637 pr_targets = repo.pull_requests_target
656 pr_targets = repo.pull_requests_target
638 if pull_requests != 'delete' and (pr_sources or pr_targets):
657 if pull_requests != 'delete' and (pr_sources or pr_targets):
639 raise AttachedPullRequestsError()
658 raise AttachedPullRequestsError()
640
659
641 old_repo_dict = repo.get_dict()
660 old_repo_dict = repo.get_dict()
642 events.trigger(events.RepoPreDeleteEvent(repo))
661 events.trigger(events.RepoPreDeleteEvent(repo))
643 try:
662 try:
644 self.sa.delete(repo)
663 self.sa.delete(repo)
645 if fs_remove:
664 if fs_remove:
646 self._delete_filesystem_repo(repo)
665 self._delete_filesystem_repo(repo)
647 else:
666 else:
648 log.debug('skipping removal from filesystem')
667 log.debug('skipping removal from filesystem')
649 old_repo_dict.update({
668 old_repo_dict.update({
650 'deleted_by': cur_user,
669 'deleted_by': cur_user,
651 'deleted_on': time.time(),
670 'deleted_on': time.time(),
652 })
671 })
653 log_delete_repository(**old_repo_dict)
672 log_delete_repository(**old_repo_dict)
654 events.trigger(events.RepoDeleteEvent(repo))
673 events.trigger(events.RepoDeleteEvent(repo))
655 except Exception:
674 except Exception:
656 log.error(traceback.format_exc())
675 log.error(traceback.format_exc())
657 raise
676 raise
658
677
659 def grant_user_permission(self, repo, user, perm):
678 def grant_user_permission(self, repo, user, perm):
660 """
679 """
661 Grant permission for user on given repository, or update existing one
680 Grant permission for user on given repository, or update existing one
662 if found
681 if found
663
682
664 :param repo: Instance of Repository, repository_id, or repository name
683 :param repo: Instance of Repository, repository_id, or repository name
665 :param user: Instance of User, user_id or username
684 :param user: Instance of User, user_id or username
666 :param perm: Instance of Permission, or permission_name
685 :param perm: Instance of Permission, or permission_name
667 """
686 """
668 user = self._get_user(user)
687 user = self._get_user(user)
669 repo = self._get_repo(repo)
688 repo = self._get_repo(repo)
670 permission = self._get_perm(perm)
689 permission = self._get_perm(perm)
671
690
672 # check if we have that permission already
691 # check if we have that permission already
673 obj = self.sa.query(UserRepoToPerm) \
692 obj = self.sa.query(UserRepoToPerm) \
674 .filter(UserRepoToPerm.user == user) \
693 .filter(UserRepoToPerm.user == user) \
675 .filter(UserRepoToPerm.repository == repo) \
694 .filter(UserRepoToPerm.repository == repo) \
676 .scalar()
695 .scalar()
677 if obj is None:
696 if obj is None:
678 # create new !
697 # create new !
679 obj = UserRepoToPerm()
698 obj = UserRepoToPerm()
680 obj.repository = repo
699 obj.repository = repo
681 obj.user = user
700 obj.user = user
682 obj.permission = permission
701 obj.permission = permission
683 self.sa.add(obj)
702 self.sa.add(obj)
684 log.debug('Granted perm %s to %s on %s', perm, user, repo)
703 log.debug('Granted perm %s to %s on %s', perm, user, repo)
685 action_logger_generic(
704 action_logger_generic(
686 'granted permission: {} to user: {} on repo: {}'.format(
705 'granted permission: {} to user: {} on repo: {}'.format(
687 perm, user, repo), namespace='security.repo')
706 perm, user, repo), namespace='security.repo')
688 return obj
707 return obj
689
708
690 def revoke_user_permission(self, repo, user):
709 def revoke_user_permission(self, repo, user):
691 """
710 """
692 Revoke permission for user on given repository
711 Revoke permission for user on given repository
693
712
694 :param repo: Instance of Repository, repository_id, or repository name
713 :param repo: Instance of Repository, repository_id, or repository name
695 :param user: Instance of User, user_id or username
714 :param user: Instance of User, user_id or username
696 """
715 """
697
716
698 user = self._get_user(user)
717 user = self._get_user(user)
699 repo = self._get_repo(repo)
718 repo = self._get_repo(repo)
700
719
701 obj = self.sa.query(UserRepoToPerm) \
720 obj = self.sa.query(UserRepoToPerm) \
702 .filter(UserRepoToPerm.repository == repo) \
721 .filter(UserRepoToPerm.repository == repo) \
703 .filter(UserRepoToPerm.user == user) \
722 .filter(UserRepoToPerm.user == user) \
704 .scalar()
723 .scalar()
705 if obj:
724 if obj:
706 self.sa.delete(obj)
725 self.sa.delete(obj)
707 log.debug('Revoked perm on %s on %s', repo, user)
726 log.debug('Revoked perm on %s on %s', repo, user)
708 action_logger_generic(
727 action_logger_generic(
709 'revoked permission from user: {} on repo: {}'.format(
728 'revoked permission from user: {} on repo: {}'.format(
710 user, repo), namespace='security.repo')
729 user, repo), namespace='security.repo')
711
730
712 def grant_user_group_permission(self, repo, group_name, perm):
731 def grant_user_group_permission(self, repo, group_name, perm):
713 """
732 """
714 Grant permission for user group on given repository, or update
733 Grant permission for user group on given repository, or update
715 existing one if found
734 existing one if found
716
735
717 :param repo: Instance of Repository, repository_id, or repository name
736 :param repo: Instance of Repository, repository_id, or repository name
718 :param group_name: Instance of UserGroup, users_group_id,
737 :param group_name: Instance of UserGroup, users_group_id,
719 or user group name
738 or user group name
720 :param perm: Instance of Permission, or permission_name
739 :param perm: Instance of Permission, or permission_name
721 """
740 """
722 repo = self._get_repo(repo)
741 repo = self._get_repo(repo)
723 group_name = self._get_user_group(group_name)
742 group_name = self._get_user_group(group_name)
724 permission = self._get_perm(perm)
743 permission = self._get_perm(perm)
725
744
726 # check if we have that permission already
745 # check if we have that permission already
727 obj = self.sa.query(UserGroupRepoToPerm) \
746 obj = self.sa.query(UserGroupRepoToPerm) \
728 .filter(UserGroupRepoToPerm.users_group == group_name) \
747 .filter(UserGroupRepoToPerm.users_group == group_name) \
729 .filter(UserGroupRepoToPerm.repository == repo) \
748 .filter(UserGroupRepoToPerm.repository == repo) \
730 .scalar()
749 .scalar()
731
750
732 if obj is None:
751 if obj is None:
733 # create new
752 # create new
734 obj = UserGroupRepoToPerm()
753 obj = UserGroupRepoToPerm()
735
754
736 obj.repository = repo
755 obj.repository = repo
737 obj.users_group = group_name
756 obj.users_group = group_name
738 obj.permission = permission
757 obj.permission = permission
739 self.sa.add(obj)
758 self.sa.add(obj)
740 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
759 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
741 action_logger_generic(
760 action_logger_generic(
742 'granted permission: {} to usergroup: {} on repo: {}'.format(
761 'granted permission: {} to usergroup: {} on repo: {}'.format(
743 perm, group_name, repo), namespace='security.repo')
762 perm, group_name, repo), namespace='security.repo')
744
763
745 return obj
764 return obj
746
765
747 def revoke_user_group_permission(self, repo, group_name):
766 def revoke_user_group_permission(self, repo, group_name):
748 """
767 """
749 Revoke permission for user group on given repository
768 Revoke permission for user group on given repository
750
769
751 :param repo: Instance of Repository, repository_id, or repository name
770 :param repo: Instance of Repository, repository_id, or repository name
752 :param group_name: Instance of UserGroup, users_group_id,
771 :param group_name: Instance of UserGroup, users_group_id,
753 or user group name
772 or user group name
754 """
773 """
755 repo = self._get_repo(repo)
774 repo = self._get_repo(repo)
756 group_name = self._get_user_group(group_name)
775 group_name = self._get_user_group(group_name)
757
776
758 obj = self.sa.query(UserGroupRepoToPerm) \
777 obj = self.sa.query(UserGroupRepoToPerm) \
759 .filter(UserGroupRepoToPerm.repository == repo) \
778 .filter(UserGroupRepoToPerm.repository == repo) \
760 .filter(UserGroupRepoToPerm.users_group == group_name) \
779 .filter(UserGroupRepoToPerm.users_group == group_name) \
761 .scalar()
780 .scalar()
762 if obj:
781 if obj:
763 self.sa.delete(obj)
782 self.sa.delete(obj)
764 log.debug('Revoked perm to %s on %s', repo, group_name)
783 log.debug('Revoked perm to %s on %s', repo, group_name)
765 action_logger_generic(
784 action_logger_generic(
766 'revoked permission from usergroup: {} on repo: {}'.format(
785 'revoked permission from usergroup: {} on repo: {}'.format(
767 group_name, repo), namespace='security.repo')
786 group_name, repo), namespace='security.repo')
768
787
769 def delete_stats(self, repo_name):
788 def delete_stats(self, repo_name):
770 """
789 """
771 removes stats for given repo
790 removes stats for given repo
772
791
773 :param repo_name:
792 :param repo_name:
774 """
793 """
775 repo = self._get_repo(repo_name)
794 repo = self._get_repo(repo_name)
776 try:
795 try:
777 obj = self.sa.query(Statistics) \
796 obj = self.sa.query(Statistics) \
778 .filter(Statistics.repository == repo).scalar()
797 .filter(Statistics.repository == repo).scalar()
779 if obj:
798 if obj:
780 self.sa.delete(obj)
799 self.sa.delete(obj)
781 except Exception:
800 except Exception:
782 log.error(traceback.format_exc())
801 log.error(traceback.format_exc())
783 raise
802 raise
784
803
785 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
804 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
786 field_type='str', field_desc=''):
805 field_type='str', field_desc=''):
787
806
788 repo = self._get_repo(repo_name)
807 repo = self._get_repo(repo_name)
789
808
790 new_field = RepositoryField()
809 new_field = RepositoryField()
791 new_field.repository = repo
810 new_field.repository = repo
792 new_field.field_key = field_key
811 new_field.field_key = field_key
793 new_field.field_type = field_type # python type
812 new_field.field_type = field_type # python type
794 new_field.field_value = field_value
813 new_field.field_value = field_value
795 new_field.field_desc = field_desc
814 new_field.field_desc = field_desc
796 new_field.field_label = field_label
815 new_field.field_label = field_label
797 self.sa.add(new_field)
816 self.sa.add(new_field)
798 return new_field
817 return new_field
799
818
800 def delete_repo_field(self, repo_name, field_key):
819 def delete_repo_field(self, repo_name, field_key):
801 repo = self._get_repo(repo_name)
820 repo = self._get_repo(repo_name)
802 field = RepositoryField.get_by_key_name(field_key, repo)
821 field = RepositoryField.get_by_key_name(field_key, repo)
803 if field:
822 if field:
804 self.sa.delete(field)
823 self.sa.delete(field)
805
824
806 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
825 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
807 clone_uri=None, repo_store_location=None,
826 clone_uri=None, repo_store_location=None,
808 use_global_config=False):
827 use_global_config=False):
809 """
828 """
810 makes repository on filesystem. It's group aware means it'll create
829 makes repository on filesystem. It's group aware means it'll create
811 a repository within a group, and alter the paths accordingly of
830 a repository within a group, and alter the paths accordingly of
812 group location
831 group location
813
832
814 :param repo_name:
833 :param repo_name:
815 :param alias:
834 :param alias:
816 :param parent:
835 :param parent:
817 :param clone_uri:
836 :param clone_uri:
818 :param repo_store_location:
837 :param repo_store_location:
819 """
838 """
820 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
839 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
821 from rhodecode.model.scm import ScmModel
840 from rhodecode.model.scm import ScmModel
822
841
823 if Repository.NAME_SEP in repo_name:
842 if Repository.NAME_SEP in repo_name:
824 raise ValueError(
843 raise ValueError(
825 'repo_name must not contain groups got `%s`' % repo_name)
844 'repo_name must not contain groups got `%s`' % repo_name)
826
845
827 if isinstance(repo_group, RepoGroup):
846 if isinstance(repo_group, RepoGroup):
828 new_parent_path = os.sep.join(repo_group.full_path_splitted)
847 new_parent_path = os.sep.join(repo_group.full_path_splitted)
829 else:
848 else:
830 new_parent_path = repo_group or ''
849 new_parent_path = repo_group or ''
831
850
832 if repo_store_location:
851 if repo_store_location:
833 _paths = [repo_store_location]
852 _paths = [repo_store_location]
834 else:
853 else:
835 _paths = [self.repos_path, new_parent_path, repo_name]
854 _paths = [self.repos_path, new_parent_path, repo_name]
836 # we need to make it str for mercurial
855 # we need to make it str for mercurial
837 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
856 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
838
857
839 # check if this path is not a repository
858 # check if this path is not a repository
840 if is_valid_repo(repo_path, self.repos_path):
859 if is_valid_repo(repo_path, self.repos_path):
841 raise Exception('This path %s is a valid repository' % repo_path)
860 raise Exception('This path %s is a valid repository' % repo_path)
842
861
843 # check if this path is a group
862 # check if this path is a group
844 if is_valid_repo_group(repo_path, self.repos_path):
863 if is_valid_repo_group(repo_path, self.repos_path):
845 raise Exception('This path %s is a valid group' % repo_path)
864 raise Exception('This path %s is a valid group' % repo_path)
846
865
847 log.info('creating repo %s in %s from url: `%s`',
866 log.info('creating repo %s in %s from url: `%s`',
848 repo_name, safe_unicode(repo_path),
867 repo_name, safe_unicode(repo_path),
849 obfuscate_url_pw(clone_uri))
868 obfuscate_url_pw(clone_uri))
850
869
851 backend = get_backend(repo_type)
870 backend = get_backend(repo_type)
852
871
853 config_repo = None if use_global_config else repo_name
872 config_repo = None if use_global_config else repo_name
854 if config_repo and new_parent_path:
873 if config_repo and new_parent_path:
855 config_repo = Repository.NAME_SEP.join(
874 config_repo = Repository.NAME_SEP.join(
856 (new_parent_path, config_repo))
875 (new_parent_path, config_repo))
857 config = make_db_config(clear_session=False, repo=config_repo)
876 config = make_db_config(clear_session=False, repo=config_repo)
858 config.set('extensions', 'largefiles', '')
877 config.set('extensions', 'largefiles', '')
859
878
860 # patch and reset hooks section of UI config to not run any
879 # patch and reset hooks section of UI config to not run any
861 # hooks on creating remote repo
880 # hooks on creating remote repo
862 config.clear_section('hooks')
881 config.clear_section('hooks')
863
882
864 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
883 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
865 if repo_type == 'git':
884 if repo_type == 'git':
866 repo = backend(
885 repo = backend(
867 repo_path, config=config, create=True, src_url=clone_uri,
886 repo_path, config=config, create=True, src_url=clone_uri,
868 bare=True)
887 bare=True)
869 else:
888 else:
870 repo = backend(
889 repo = backend(
871 repo_path, config=config, create=True, src_url=clone_uri)
890 repo_path, config=config, create=True, src_url=clone_uri)
872
891
873 repo.install_hooks()
892 repo.install_hooks()
874
893
875 log.debug('Created repo %s with %s backend',
894 log.debug('Created repo %s with %s backend',
876 safe_unicode(repo_name), safe_unicode(repo_type))
895 safe_unicode(repo_name), safe_unicode(repo_type))
877 return repo
896 return repo
878
897
879 def _rename_filesystem_repo(self, old, new):
898 def _rename_filesystem_repo(self, old, new):
880 """
899 """
881 renames repository on filesystem
900 renames repository on filesystem
882
901
883 :param old: old name
902 :param old: old name
884 :param new: new name
903 :param new: new name
885 """
904 """
886 log.info('renaming repo from %s to %s', old, new)
905 log.info('renaming repo from %s to %s', old, new)
887
906
888 old_path = os.path.join(self.repos_path, old)
907 old_path = os.path.join(self.repos_path, old)
889 new_path = os.path.join(self.repos_path, new)
908 new_path = os.path.join(self.repos_path, new)
890 if os.path.isdir(new_path):
909 if os.path.isdir(new_path):
891 raise Exception(
910 raise Exception(
892 'Was trying to rename to already existing dir %s' % new_path
911 'Was trying to rename to already existing dir %s' % new_path
893 )
912 )
894 shutil.move(old_path, new_path)
913 shutil.move(old_path, new_path)
895
914
896 def _delete_filesystem_repo(self, repo):
915 def _delete_filesystem_repo(self, repo):
897 """
916 """
898 removes repo from filesystem, the removal is acctually made by
917 removes repo from filesystem, the removal is acctually made by
899 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
918 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
900 repository is no longer valid for rhodecode, can be undeleted later on
919 repository is no longer valid for rhodecode, can be undeleted later on
901 by reverting the renames on this repository
920 by reverting the renames on this repository
902
921
903 :param repo: repo object
922 :param repo: repo object
904 """
923 """
905 rm_path = os.path.join(self.repos_path, repo.repo_name)
924 rm_path = os.path.join(self.repos_path, repo.repo_name)
906 repo_group = repo.group
925 repo_group = repo.group
907 log.info("Removing repository %s", rm_path)
926 log.info("Removing repository %s", rm_path)
908 # disable hg/git internal that it doesn't get detected as repo
927 # disable hg/git internal that it doesn't get detected as repo
909 alias = repo.repo_type
928 alias = repo.repo_type
910
929
911 config = make_db_config(clear_session=False)
930 config = make_db_config(clear_session=False)
912 config.set('extensions', 'largefiles', '')
931 config.set('extensions', 'largefiles', '')
913 bare = getattr(repo.scm_instance(config=config), 'bare', False)
932 bare = getattr(repo.scm_instance(config=config), 'bare', False)
914
933
915 # skip this for bare git repos
934 # skip this for bare git repos
916 if not bare:
935 if not bare:
917 # disable VCS repo
936 # disable VCS repo
918 vcs_path = os.path.join(rm_path, '.%s' % alias)
937 vcs_path = os.path.join(rm_path, '.%s' % alias)
919 if os.path.exists(vcs_path):
938 if os.path.exists(vcs_path):
920 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
939 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
921
940
922 _now = datetime.datetime.now()
941 _now = datetime.datetime.now()
923 _ms = str(_now.microsecond).rjust(6, '0')
942 _ms = str(_now.microsecond).rjust(6, '0')
924 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
943 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
925 repo.just_name)
944 repo.just_name)
926 if repo_group:
945 if repo_group:
927 # if repository is in group, prefix the removal path with the group
946 # if repository is in group, prefix the removal path with the group
928 args = repo_group.full_path_splitted + [_d]
947 args = repo_group.full_path_splitted + [_d]
929 _d = os.path.join(*args)
948 _d = os.path.join(*args)
930
949
931 if os.path.isdir(rm_path):
950 if os.path.isdir(rm_path):
932 shutil.move(rm_path, os.path.join(self.repos_path, _d))
951 shutil.move(rm_path, os.path.join(self.repos_path, _d))
933
952
934 # finally cleanup diff-cache if it exists
953 # finally cleanup diff-cache if it exists
935 cached_diffs_dir = repo.cached_diffs_dir
954 cached_diffs_dir = repo.cached_diffs_dir
936 if os.path.isdir(cached_diffs_dir):
955 if os.path.isdir(cached_diffs_dir):
937 shutil.rmtree(cached_diffs_dir)
956 shutil.rmtree(cached_diffs_dir)
938
957
939
958
940 class ReadmeFinder:
959 class ReadmeFinder:
941 """
960 """
942 Utility which knows how to find a readme for a specific commit.
961 Utility which knows how to find a readme for a specific commit.
943
962
944 The main idea is that this is a configurable algorithm. When creating an
963 The main idea is that this is a configurable algorithm. When creating an
945 instance you can define parameters, currently only the `default_renderer`.
964 instance you can define parameters, currently only the `default_renderer`.
946 Based on this configuration the method :meth:`search` behaves slightly
965 Based on this configuration the method :meth:`search` behaves slightly
947 different.
966 different.
948 """
967 """
949
968
950 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
969 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
951 path_re = re.compile(r'^docs?', re.IGNORECASE)
970 path_re = re.compile(r'^docs?', re.IGNORECASE)
952
971
953 default_priorities = {
972 default_priorities = {
954 None: 0,
973 None: 0,
955 '.text': 2,
974 '.text': 2,
956 '.txt': 3,
975 '.txt': 3,
957 '.rst': 1,
976 '.rst': 1,
958 '.rest': 2,
977 '.rest': 2,
959 '.md': 1,
978 '.md': 1,
960 '.mkdn': 2,
979 '.mkdn': 2,
961 '.mdown': 3,
980 '.mdown': 3,
962 '.markdown': 4,
981 '.markdown': 4,
963 }
982 }
964
983
965 path_priority = {
984 path_priority = {
966 'doc': 0,
985 'doc': 0,
967 'docs': 1,
986 'docs': 1,
968 }
987 }
969
988
970 FALLBACK_PRIORITY = 99
989 FALLBACK_PRIORITY = 99
971
990
972 RENDERER_TO_EXTENSION = {
991 RENDERER_TO_EXTENSION = {
973 'rst': ['.rst', '.rest'],
992 'rst': ['.rst', '.rest'],
974 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
993 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
975 }
994 }
976
995
977 def __init__(self, default_renderer=None):
996 def __init__(self, default_renderer=None):
978 self._default_renderer = default_renderer
997 self._default_renderer = default_renderer
979 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
998 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
980 default_renderer, [])
999 default_renderer, [])
981
1000
982 def search(self, commit, path='/'):
1001 def search(self, commit, path='/'):
983 """
1002 """
984 Find a readme in the given `commit`.
1003 Find a readme in the given `commit`.
985 """
1004 """
986 nodes = commit.get_nodes(path)
1005 nodes = commit.get_nodes(path)
987 matches = self._match_readmes(nodes)
1006 matches = self._match_readmes(nodes)
988 matches = self._sort_according_to_priority(matches)
1007 matches = self._sort_according_to_priority(matches)
989 if matches:
1008 if matches:
990 return matches[0].node
1009 return matches[0].node
991
1010
992 paths = self._match_paths(nodes)
1011 paths = self._match_paths(nodes)
993 paths = self._sort_paths_according_to_priority(paths)
1012 paths = self._sort_paths_according_to_priority(paths)
994 for path in paths:
1013 for path in paths:
995 match = self.search(commit, path=path)
1014 match = self.search(commit, path=path)
996 if match:
1015 if match:
997 return match
1016 return match
998
1017
999 return None
1018 return None
1000
1019
1001 def _match_readmes(self, nodes):
1020 def _match_readmes(self, nodes):
1002 for node in nodes:
1021 for node in nodes:
1003 if not node.is_file():
1022 if not node.is_file():
1004 continue
1023 continue
1005 path = node.path.rsplit('/', 1)[-1]
1024 path = node.path.rsplit('/', 1)[-1]
1006 match = self.readme_re.match(path)
1025 match = self.readme_re.match(path)
1007 if match:
1026 if match:
1008 extension = match.group(1)
1027 extension = match.group(1)
1009 yield ReadmeMatch(node, match, self._priority(extension))
1028 yield ReadmeMatch(node, match, self._priority(extension))
1010
1029
1011 def _match_paths(self, nodes):
1030 def _match_paths(self, nodes):
1012 for node in nodes:
1031 for node in nodes:
1013 if not node.is_dir():
1032 if not node.is_dir():
1014 continue
1033 continue
1015 match = self.path_re.match(node.path)
1034 match = self.path_re.match(node.path)
1016 if match:
1035 if match:
1017 yield node.path
1036 yield node.path
1018
1037
1019 def _priority(self, extension):
1038 def _priority(self, extension):
1020 renderer_priority = (
1039 renderer_priority = (
1021 0 if extension in self._renderer_extensions else 1)
1040 0 if extension in self._renderer_extensions else 1)
1022 extension_priority = self.default_priorities.get(
1041 extension_priority = self.default_priorities.get(
1023 extension, self.FALLBACK_PRIORITY)
1042 extension, self.FALLBACK_PRIORITY)
1024 return (renderer_priority, extension_priority)
1043 return (renderer_priority, extension_priority)
1025
1044
1026 def _sort_according_to_priority(self, matches):
1045 def _sort_according_to_priority(self, matches):
1027
1046
1028 def priority_and_path(match):
1047 def priority_and_path(match):
1029 return (match.priority, match.path)
1048 return (match.priority, match.path)
1030
1049
1031 return sorted(matches, key=priority_and_path)
1050 return sorted(matches, key=priority_and_path)
1032
1051
1033 def _sort_paths_according_to_priority(self, paths):
1052 def _sort_paths_according_to_priority(self, paths):
1034
1053
1035 def priority_and_path(path):
1054 def priority_and_path(path):
1036 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1055 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1037
1056
1038 return sorted(paths, key=priority_and_path)
1057 return sorted(paths, key=priority_and_path)
1039
1058
1040
1059
1041 class ReadmeMatch:
1060 class ReadmeMatch:
1042
1061
1043 def __init__(self, node, match, priority):
1062 def __init__(self, node, match, priority):
1044 self.node = node
1063 self.node = node
1045 self._match = match
1064 self._match = match
1046 self.priority = priority
1065 self.priority = priority
1047
1066
1048 @property
1067 @property
1049 def path(self):
1068 def path(self):
1050 return self.node.path
1069 return self.node.path
1051
1070
1052 def __repr__(self):
1071 def __repr__(self):
1053 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
1072 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
@@ -1,335 +1,336 b''
1
1
2 /******************************************************************************
2 /******************************************************************************
3 * *
3 * *
4 * DO NOT CHANGE THIS FILE MANUALLY *
4 * DO NOT CHANGE THIS FILE MANUALLY *
5 * *
5 * *
6 * *
6 * *
7 * This file is automatically generated when the app starts up with *
7 * This file is automatically generated when the app starts up with *
8 * generate_js_files = true *
8 * generate_js_files = true *
9 * *
9 * *
10 * To add a route here pass jsroute=True to the route definition in the app *
10 * To add a route here pass jsroute=True to the route definition in the app *
11 * *
11 * *
12 ******************************************************************************/
12 ******************************************************************************/
13 function registerRCRoutes() {
13 function registerRCRoutes() {
14 // routes registration
14 // routes registration
15 pyroutes.register('favicon', '/favicon.ico', []);
15 pyroutes.register('favicon', '/favicon.ico', []);
16 pyroutes.register('robots', '/robots.txt', []);
16 pyroutes.register('robots', '/robots.txt', []);
17 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
17 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
18 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
18 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
19 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
19 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
20 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
20 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
21 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
21 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
22 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
22 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
23 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
23 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
24 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
24 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
25 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
25 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
26 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
26 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
27 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
27 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
28 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
28 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
29 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
29 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
30 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
30 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
31 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
31 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
32 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
32 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
33 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
33 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
34 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
34 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
35 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
35 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
36 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
36 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
37 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
37 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
38 pyroutes.register('admin_home', '/_admin', []);
38 pyroutes.register('admin_home', '/_admin', []);
39 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
39 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
40 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
40 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
41 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
41 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
42 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
42 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
43 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
43 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
44 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
44 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
45 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
45 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
46 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
46 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
47 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
47 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
48 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
48 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
49 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions/delete', []);
49 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions/delete', []);
50 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
50 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
51 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
51 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
52 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
52 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
53 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
53 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
54 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
54 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
55 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
55 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
56 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
56 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
57 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
57 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
58 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
58 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
59 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
59 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
60 pyroutes.register('admin_settings', '/_admin/settings', []);
60 pyroutes.register('admin_settings', '/_admin/settings', []);
61 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
61 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
62 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
62 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
63 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
63 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
64 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
64 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
65 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
65 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
66 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
66 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
67 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
67 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
68 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
68 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
69 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
69 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
70 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
70 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
71 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
71 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
72 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
72 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
73 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
73 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
74 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
74 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
75 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
75 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
76 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
76 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
77 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
77 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
78 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
78 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
79 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
79 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
80 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
80 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
81 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
81 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
82 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
82 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
83 pyroutes.register('admin_settings_automation', '/_admin/_admin/settings/automation', []);
83 pyroutes.register('admin_settings_automation', '/_admin/_admin/settings/automation', []);
84 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
84 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
85 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
85 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
86 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
86 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
87 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
87 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
88 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
88 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
89 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
89 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
90 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
90 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
91 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
91 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
92 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
92 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
93 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
93 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
94 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
94 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
95 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
95 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
96 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
96 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
97 pyroutes.register('users', '/_admin/users', []);
97 pyroutes.register('users', '/_admin/users', []);
98 pyroutes.register('users_data', '/_admin/users_data', []);
98 pyroutes.register('users_data', '/_admin/users_data', []);
99 pyroutes.register('users_create', '/_admin/users/create', []);
99 pyroutes.register('users_create', '/_admin/users/create', []);
100 pyroutes.register('users_new', '/_admin/users/new', []);
100 pyroutes.register('users_new', '/_admin/users/new', []);
101 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
101 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
102 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
102 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
103 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
103 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
104 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
104 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
105 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
105 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
106 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
106 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
107 pyroutes.register('user_force_password_reset', '/_admin/users/%(user_id)s/password_reset', ['user_id']);
107 pyroutes.register('user_force_password_reset', '/_admin/users/%(user_id)s/password_reset', ['user_id']);
108 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
108 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
109 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
109 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
110 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
110 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
111 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
111 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
112 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
112 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
113 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
113 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
114 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
114 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
115 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
115 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
116 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
116 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
117 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
117 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
118 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
118 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
119 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
119 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
120 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
120 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
121 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
121 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
122 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
122 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
123 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
123 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
124 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
124 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
125 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
125 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
126 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
126 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
127 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
127 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
128 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
128 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
129 pyroutes.register('user_groups', '/_admin/user_groups', []);
129 pyroutes.register('user_groups', '/_admin/user_groups', []);
130 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
130 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
131 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
131 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
132 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
132 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
133 pyroutes.register('repos', '/_admin/repos', []);
133 pyroutes.register('repos', '/_admin/repos', []);
134 pyroutes.register('repo_new', '/_admin/repos/new', []);
134 pyroutes.register('repo_new', '/_admin/repos/new', []);
135 pyroutes.register('repo_create', '/_admin/repos/create', []);
135 pyroutes.register('repo_create', '/_admin/repos/create', []);
136 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
136 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
137 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
137 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
138 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
138 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
139 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
139 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
140 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
140 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
141 pyroutes.register('channelstream_proxy', '/_channelstream', []);
141 pyroutes.register('channelstream_proxy', '/_channelstream', []);
142 pyroutes.register('login', '/_admin/login', []);
142 pyroutes.register('login', '/_admin/login', []);
143 pyroutes.register('logout', '/_admin/logout', []);
143 pyroutes.register('logout', '/_admin/logout', []);
144 pyroutes.register('register', '/_admin/register', []);
144 pyroutes.register('register', '/_admin/register', []);
145 pyroutes.register('reset_password', '/_admin/password_reset', []);
145 pyroutes.register('reset_password', '/_admin/password_reset', []);
146 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
146 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
147 pyroutes.register('home', '/', []);
147 pyroutes.register('home', '/', []);
148 pyroutes.register('user_autocomplete_data', '/_users', []);
148 pyroutes.register('user_autocomplete_data', '/_users', []);
149 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
149 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
150 pyroutes.register('repo_list_data', '/_repos', []);
150 pyroutes.register('repo_list_data', '/_repos', []);
151 pyroutes.register('goto_switcher_data', '/_goto_data', []);
151 pyroutes.register('goto_switcher_data', '/_goto_data', []);
152 pyroutes.register('markup_preview', '/_markup_preview', []);
152 pyroutes.register('markup_preview', '/_markup_preview', []);
153 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
153 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
154 pyroutes.register('journal', '/_admin/journal', []);
154 pyroutes.register('journal', '/_admin/journal', []);
155 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
155 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
156 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
156 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
157 pyroutes.register('journal_public', '/_admin/public_journal', []);
157 pyroutes.register('journal_public', '/_admin/public_journal', []);
158 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
158 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
159 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
159 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
160 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
160 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
161 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
161 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
162 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
162 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
163 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
163 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
164 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
164 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
165 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
165 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
166 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
166 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
167 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
167 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
168 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
168 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
169 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
169 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
170 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
170 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
171 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
171 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
172 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
172 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
173 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
173 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
174 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
174 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
175 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
175 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
176 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
176 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
177 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
177 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
178 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
178 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
179 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
179 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
180 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
180 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
181 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
181 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
182 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
182 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
183 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
183 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
184 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
184 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
185 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
185 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
186 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
186 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
187 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
187 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
188 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
188 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
189 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
189 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
190 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
190 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
191 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
191 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
192 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
192 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
193 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
193 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
194 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
194 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
195 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
195 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
196 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
196 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
197 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
197 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
198 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
198 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
199 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
199 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
200 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
200 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
201 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
201 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
202 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
202 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
203 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
203 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
204 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
204 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
205 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
205 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
206 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
206 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
207 pyroutes.register('repo_changelog_elements_file', '/%(repo_name)s/changelog_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
207 pyroutes.register('repo_changelog_elements_file', '/%(repo_name)s/changelog_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
208 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
208 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
209 pyroutes.register('repo_compare', '/%(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']);
209 pyroutes.register('repo_compare', '/%(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']);
210 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
210 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
211 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
211 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
212 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
212 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
213 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
213 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
214 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
214 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
215 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
215 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
216 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
216 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
217 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
217 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
218 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
218 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
219 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
219 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
220 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
220 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
221 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
221 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
222 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
222 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
223 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
223 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
224 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
224 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
225 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
225 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
226 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
226 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
227 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
227 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
228 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
228 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
229 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
229 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
230 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
230 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
231 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
231 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
232 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
232 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
233 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
233 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
234 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
234 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
235 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
235 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
236 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
236 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
237 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
237 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
238 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
238 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
239 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
239 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
240 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
240 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
241 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
241 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
242 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
242 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
243 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
243 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
244 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
244 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
245 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
245 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
246 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
246 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
247 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
247 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
248 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
248 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
249 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
249 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
250 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
250 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
251 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
251 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
252 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
252 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
253 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
253 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
254 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
254 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
255 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
255 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
256 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
256 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
257 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
257 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
258 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
258 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
259 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
259 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
260 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
260 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
261 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
261 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
262 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
262 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
263 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
263 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
264 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
264 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
265 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
265 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
266 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
266 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
267 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
267 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
268 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
268 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
269 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
269 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
270 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
270 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
271 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
271 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
272 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
272 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
273 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
273 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
274 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
274 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
275 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
275 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
276 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
276 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
277 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
277 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
278 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
278 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
279 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
279 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
280 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
280 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
281 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
281 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
282 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
282 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
283 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
283 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
284 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
284 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
285 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
285 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
286 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
286 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
287 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
287 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
288 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
288 pyroutes.register('search', '/_admin/search', []);
289 pyroutes.register('search', '/_admin/search', []);
289 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
290 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
290 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
291 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
291 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
292 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
292 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
293 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
293 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
294 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
294 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
295 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
295 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
296 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
296 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
297 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
297 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
298 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
298 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
299 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
299 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
300 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
300 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
301 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
301 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
302 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
302 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
303 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
303 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
304 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
304 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
305 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
305 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
306 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
306 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
307 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
307 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
308 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
308 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
309 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
309 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
310 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
310 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
311 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
311 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
312 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
312 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
313 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
313 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
314 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
314 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
315 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
315 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
316 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
316 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
317 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
317 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
318 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
318 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
319 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
319 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
320 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
320 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
321 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
321 pyroutes.register('gists_show', '/_admin/gists', []);
322 pyroutes.register('gists_show', '/_admin/gists', []);
322 pyroutes.register('gists_new', '/_admin/gists/new', []);
323 pyroutes.register('gists_new', '/_admin/gists/new', []);
323 pyroutes.register('gists_create', '/_admin/gists/create', []);
324 pyroutes.register('gists_create', '/_admin/gists/create', []);
324 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
325 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
325 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
326 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
326 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
327 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
327 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
328 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
328 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
329 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
329 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
330 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
330 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
331 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
331 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
332 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
332 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
333 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
333 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
334 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
334 pyroutes.register('apiv2', '/_admin/api', []);
335 pyroutes.register('apiv2', '/_admin/api', []);
335 }
336 }
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now