##// END OF EJS Templates
routing: improve bad character detection on repo names
super-admin -
r4843:05c94e8b default
parent child Browse files
Show More
@@ -1,829 +1,839 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2020 RhodeCode GmbH
3 # Copyright (C) 2016-2020 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 import compat
25 from pyramid import compat
26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
27
27
28 from rhodecode.lib import helpers as h, diffs, rc_cache
28 from rhodecode.lib import helpers as h, diffs, rc_cache
29 from rhodecode.lib.utils import repo_name_slug
29 from rhodecode.lib.utils import repo_name_slug
30 from rhodecode.lib.utils2 import (
30 from rhodecode.lib.utils2 import (
31 StrictAttributeDict, str2bool, safe_int, datetime_to_time, safe_unicode)
31 StrictAttributeDict, str2bool, safe_int, datetime_to_time, safe_unicode)
32 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
32 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
33 from rhodecode.lib.vcs.backends.base import EmptyCommit
33 from rhodecode.lib.vcs.backends.base import EmptyCommit
34 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
34 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
35 from rhodecode.model import repo
35 from rhodecode.model import repo
36 from rhodecode.model import repo_group
36 from rhodecode.model import repo_group
37 from rhodecode.model import user_group
37 from rhodecode.model import user_group
38 from rhodecode.model import user
38 from rhodecode.model import user
39 from rhodecode.model.db import User
39 from rhodecode.model.db import User
40 from rhodecode.model.scm import ScmModel
40 from rhodecode.model.scm import ScmModel
41 from rhodecode.model.settings import VcsSettingsModel, IssueTrackerSettingsModel
41 from rhodecode.model.settings import VcsSettingsModel, IssueTrackerSettingsModel
42 from rhodecode.model.repo import ReadmeFinder
42 from rhodecode.model.repo import ReadmeFinder
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46
46
47 ADMIN_PREFIX = '/_admin'
47 ADMIN_PREFIX = '/_admin'
48 STATIC_FILE_PREFIX = '/_static'
48 STATIC_FILE_PREFIX = '/_static'
49
49
50 URL_NAME_REQUIREMENTS = {
50 URL_NAME_REQUIREMENTS = {
51 # group name can have a slash in them, but they must not end with a slash
51 # group name can have a slash in them, but they must not end with a slash
52 'group_name': r'.*?[^/]',
52 'group_name': r'.*?[^/]',
53 'repo_group_name': r'.*?[^/]',
53 'repo_group_name': r'.*?[^/]',
54 # repo names can have a slash in them, but they must not end with a slash
54 # repo names can have a slash in them, but they must not end with a slash
55 'repo_name': r'.*?[^/]',
55 'repo_name': r'.*?[^/]',
56 # file path eats up everything at the end
56 # file path eats up everything at the end
57 'f_path': r'.*',
57 'f_path': r'.*',
58 # reference types
58 # reference types
59 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
59 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
60 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
60 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
61 }
61 }
62
62
63
63
64 def add_route_with_slash(config,name, pattern, **kw):
64 def add_route_with_slash(config,name, pattern, **kw):
65 config.add_route(name, pattern, **kw)
65 config.add_route(name, pattern, **kw)
66 if not pattern.endswith('/'):
66 if not pattern.endswith('/'):
67 config.add_route(name + '_slash', pattern + '/', **kw)
67 config.add_route(name + '_slash', pattern + '/', **kw)
68
68
69
69
70 def add_route_requirements(route_path, requirements=None):
70 def add_route_requirements(route_path, requirements=None):
71 """
71 """
72 Adds regex requirements to pyramid routes using a mapping dict
72 Adds regex requirements to pyramid routes using a mapping dict
73 e.g::
73 e.g::
74 add_route_requirements('{repo_name}/settings')
74 add_route_requirements('{repo_name}/settings')
75 """
75 """
76 requirements = requirements or URL_NAME_REQUIREMENTS
76 requirements = requirements or URL_NAME_REQUIREMENTS
77 for key, regex in requirements.items():
77 for key, regex in requirements.items():
78 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
78 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
79 return route_path
79 return route_path
80
80
81
81
82 def get_format_ref_id(repo):
82 def get_format_ref_id(repo):
83 """Returns a `repo` specific reference formatter function"""
83 """Returns a `repo` specific reference formatter function"""
84 if h.is_svn(repo):
84 if h.is_svn(repo):
85 return _format_ref_id_svn
85 return _format_ref_id_svn
86 else:
86 else:
87 return _format_ref_id
87 return _format_ref_id
88
88
89
89
90 def _format_ref_id(name, raw_id):
90 def _format_ref_id(name, raw_id):
91 """Default formatting of a given reference `name`"""
91 """Default formatting of a given reference `name`"""
92 return name
92 return name
93
93
94
94
95 def _format_ref_id_svn(name, raw_id):
95 def _format_ref_id_svn(name, raw_id):
96 """Special way of formatting a reference for Subversion including path"""
96 """Special way of formatting a reference for Subversion including path"""
97 return '%s@%s' % (name, raw_id)
97 return '%s@%s' % (name, raw_id)
98
98
99
99
100 class TemplateArgs(StrictAttributeDict):
100 class TemplateArgs(StrictAttributeDict):
101 pass
101 pass
102
102
103
103
104 class BaseAppView(object):
104 class BaseAppView(object):
105
105
106 def __init__(self, context, request):
106 def __init__(self, context, request):
107 self.request = request
107 self.request = request
108 self.context = context
108 self.context = context
109 self.session = request.session
109 self.session = request.session
110 if not hasattr(request, 'user'):
110 if not hasattr(request, 'user'):
111 # NOTE(marcink): edge case, we ended up in matched route
111 # NOTE(marcink): edge case, we ended up in matched route
112 # but probably of web-app context, e.g API CALL/VCS CALL
112 # but probably of web-app context, e.g API CALL/VCS CALL
113 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
113 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
114 log.warning('Unable to process request `%s` in this scope', request)
114 log.warning('Unable to process request `%s` in this scope', request)
115 raise HTTPBadRequest()
115 raise HTTPBadRequest()
116
116
117 self._rhodecode_user = request.user # auth user
117 self._rhodecode_user = request.user # auth user
118 self._rhodecode_db_user = self._rhodecode_user.get_instance()
118 self._rhodecode_db_user = self._rhodecode_user.get_instance()
119 self._maybe_needs_password_change(
119 self._maybe_needs_password_change(
120 request.matched_route.name, self._rhodecode_db_user)
120 request.matched_route.name, self._rhodecode_db_user)
121
121
122 def _maybe_needs_password_change(self, view_name, user_obj):
122 def _maybe_needs_password_change(self, view_name, user_obj):
123
123
124 dont_check_views = [
124 dont_check_views = [
125 'channelstream_connect'
125 'channelstream_connect'
126 ]
126 ]
127 if view_name in dont_check_views:
127 if view_name in dont_check_views:
128 return
128 return
129
129
130 log.debug('Checking if user %s needs password change on view %s',
130 log.debug('Checking if user %s needs password change on view %s',
131 user_obj, view_name)
131 user_obj, view_name)
132
132
133 skip_user_views = [
133 skip_user_views = [
134 'logout', 'login',
134 'logout', 'login',
135 'my_account_password', 'my_account_password_update'
135 'my_account_password', 'my_account_password_update'
136 ]
136 ]
137
137
138 if not user_obj:
138 if not user_obj:
139 return
139 return
140
140
141 if user_obj.username == User.DEFAULT_USER:
141 if user_obj.username == User.DEFAULT_USER:
142 return
142 return
143
143
144 now = time.time()
144 now = time.time()
145 should_change = user_obj.user_data.get('force_password_change')
145 should_change = user_obj.user_data.get('force_password_change')
146 change_after = safe_int(should_change) or 0
146 change_after = safe_int(should_change) or 0
147 if should_change and now > change_after:
147 if should_change and now > change_after:
148 log.debug('User %s requires password change', user_obj)
148 log.debug('User %s requires password change', user_obj)
149 h.flash('You are required to change your password', 'warning',
149 h.flash('You are required to change your password', 'warning',
150 ignore_duplicate=True)
150 ignore_duplicate=True)
151
151
152 if view_name not in skip_user_views:
152 if view_name not in skip_user_views:
153 raise HTTPFound(
153 raise HTTPFound(
154 self.request.route_path('my_account_password'))
154 self.request.route_path('my_account_password'))
155
155
156 def _log_creation_exception(self, e, repo_name):
156 def _log_creation_exception(self, e, repo_name):
157 _ = self.request.translate
157 _ = self.request.translate
158 reason = None
158 reason = None
159 if len(e.args) == 2:
159 if len(e.args) == 2:
160 reason = e.args[1]
160 reason = e.args[1]
161
161
162 if reason == 'INVALID_CERTIFICATE':
162 if reason == 'INVALID_CERTIFICATE':
163 log.exception(
163 log.exception(
164 'Exception creating a repository: invalid certificate')
164 'Exception creating a repository: invalid certificate')
165 msg = (_('Error creating repository %s: invalid certificate')
165 msg = (_('Error creating repository %s: invalid certificate')
166 % repo_name)
166 % repo_name)
167 else:
167 else:
168 log.exception("Exception creating a repository")
168 log.exception("Exception creating a repository")
169 msg = (_('Error creating repository %s')
169 msg = (_('Error creating repository %s')
170 % repo_name)
170 % repo_name)
171 return msg
171 return msg
172
172
173 def _get_local_tmpl_context(self, include_app_defaults=True):
173 def _get_local_tmpl_context(self, include_app_defaults=True):
174 c = TemplateArgs()
174 c = TemplateArgs()
175 c.auth_user = self.request.user
175 c.auth_user = self.request.user
176 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
176 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
177 c.rhodecode_user = self.request.user
177 c.rhodecode_user = self.request.user
178
178
179 if include_app_defaults:
179 if include_app_defaults:
180 from rhodecode.lib.base import attach_context_attributes
180 from rhodecode.lib.base import attach_context_attributes
181 attach_context_attributes(c, self.request, self.request.user.user_id)
181 attach_context_attributes(c, self.request, self.request.user.user_id)
182
182
183 c.is_super_admin = c.auth_user.is_admin
183 c.is_super_admin = c.auth_user.is_admin
184
184
185 c.can_create_repo = c.is_super_admin
185 c.can_create_repo = c.is_super_admin
186 c.can_create_repo_group = c.is_super_admin
186 c.can_create_repo_group = c.is_super_admin
187 c.can_create_user_group = c.is_super_admin
187 c.can_create_user_group = c.is_super_admin
188
188
189 c.is_delegated_admin = False
189 c.is_delegated_admin = False
190
190
191 if not c.auth_user.is_default and not c.is_super_admin:
191 if not c.auth_user.is_default and not c.is_super_admin:
192 c.can_create_repo = h.HasPermissionAny('hg.create.repository')(
192 c.can_create_repo = h.HasPermissionAny('hg.create.repository')(
193 user=self.request.user)
193 user=self.request.user)
194 repositories = c.auth_user.repositories_admin or c.can_create_repo
194 repositories = c.auth_user.repositories_admin or c.can_create_repo
195
195
196 c.can_create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')(
196 c.can_create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')(
197 user=self.request.user)
197 user=self.request.user)
198 repository_groups = c.auth_user.repository_groups_admin or c.can_create_repo_group
198 repository_groups = c.auth_user.repository_groups_admin or c.can_create_repo_group
199
199
200 c.can_create_user_group = h.HasPermissionAny('hg.usergroup.create.true')(
200 c.can_create_user_group = h.HasPermissionAny('hg.usergroup.create.true')(
201 user=self.request.user)
201 user=self.request.user)
202 user_groups = c.auth_user.user_groups_admin or c.can_create_user_group
202 user_groups = c.auth_user.user_groups_admin or c.can_create_user_group
203 # delegated admin can create, or manage some objects
203 # delegated admin can create, or manage some objects
204 c.is_delegated_admin = repositories or repository_groups or user_groups
204 c.is_delegated_admin = repositories or repository_groups or user_groups
205 return c
205 return c
206
206
207 def _get_template_context(self, tmpl_args, **kwargs):
207 def _get_template_context(self, tmpl_args, **kwargs):
208
208
209 local_tmpl_args = {
209 local_tmpl_args = {
210 'defaults': {},
210 'defaults': {},
211 'errors': {},
211 'errors': {},
212 'c': tmpl_args
212 'c': tmpl_args
213 }
213 }
214 local_tmpl_args.update(kwargs)
214 local_tmpl_args.update(kwargs)
215 return local_tmpl_args
215 return local_tmpl_args
216
216
217 def load_default_context(self):
217 def load_default_context(self):
218 """
218 """
219 example:
219 example:
220
220
221 def load_default_context(self):
221 def load_default_context(self):
222 c = self._get_local_tmpl_context()
222 c = self._get_local_tmpl_context()
223 c.custom_var = 'foobar'
223 c.custom_var = 'foobar'
224
224
225 return c
225 return c
226 """
226 """
227 raise NotImplementedError('Needs implementation in view class')
227 raise NotImplementedError('Needs implementation in view class')
228
228
229
229
230 class RepoAppView(BaseAppView):
230 class RepoAppView(BaseAppView):
231
231
232 def __init__(self, context, request):
232 def __init__(self, context, request):
233 super(RepoAppView, self).__init__(context, request)
233 super(RepoAppView, self).__init__(context, request)
234 self.db_repo = request.db_repo
234 self.db_repo = request.db_repo
235 self.db_repo_name = self.db_repo.repo_name
235 self.db_repo_name = self.db_repo.repo_name
236 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
236 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
237 self.db_repo_artifacts = ScmModel().get_artifacts(self.db_repo)
237 self.db_repo_artifacts = ScmModel().get_artifacts(self.db_repo)
238 self.db_repo_patterns = IssueTrackerSettingsModel(repo=self.db_repo)
238 self.db_repo_patterns = IssueTrackerSettingsModel(repo=self.db_repo)
239
239
240 def _handle_missing_requirements(self, error):
240 def _handle_missing_requirements(self, error):
241 log.error(
241 log.error(
242 'Requirements are missing for repository %s: %s',
242 'Requirements are missing for repository %s: %s',
243 self.db_repo_name, safe_unicode(error))
243 self.db_repo_name, safe_unicode(error))
244
244
245 def _get_local_tmpl_context(self, include_app_defaults=True):
245 def _get_local_tmpl_context(self, include_app_defaults=True):
246 _ = self.request.translate
246 _ = self.request.translate
247 c = super(RepoAppView, self)._get_local_tmpl_context(
247 c = super(RepoAppView, self)._get_local_tmpl_context(
248 include_app_defaults=include_app_defaults)
248 include_app_defaults=include_app_defaults)
249
249
250 # register common vars for this type of view
250 # register common vars for this type of view
251 c.rhodecode_db_repo = self.db_repo
251 c.rhodecode_db_repo = self.db_repo
252 c.repo_name = self.db_repo_name
252 c.repo_name = self.db_repo_name
253 c.repository_pull_requests = self.db_repo_pull_requests
253 c.repository_pull_requests = self.db_repo_pull_requests
254 c.repository_artifacts = self.db_repo_artifacts
254 c.repository_artifacts = self.db_repo_artifacts
255 c.repository_is_user_following = ScmModel().is_following_repo(
255 c.repository_is_user_following = ScmModel().is_following_repo(
256 self.db_repo_name, self._rhodecode_user.user_id)
256 self.db_repo_name, self._rhodecode_user.user_id)
257 self.path_filter = PathFilter(None)
257 self.path_filter = PathFilter(None)
258
258
259 c.repository_requirements_missing = {}
259 c.repository_requirements_missing = {}
260 try:
260 try:
261 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
261 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
262 # NOTE(marcink):
262 # NOTE(marcink):
263 # comparison to None since if it's an object __bool__ is expensive to
263 # comparison to None since if it's an object __bool__ is expensive to
264 # calculate
264 # calculate
265 if self.rhodecode_vcs_repo is not None:
265 if self.rhodecode_vcs_repo is not None:
266 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
266 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
267 c.auth_user.username)
267 c.auth_user.username)
268 self.path_filter = PathFilter(path_perms)
268 self.path_filter = PathFilter(path_perms)
269 except RepositoryRequirementError as e:
269 except RepositoryRequirementError as e:
270 c.repository_requirements_missing = {'error': str(e)}
270 c.repository_requirements_missing = {'error': str(e)}
271 self._handle_missing_requirements(e)
271 self._handle_missing_requirements(e)
272 self.rhodecode_vcs_repo = None
272 self.rhodecode_vcs_repo = None
273
273
274 c.path_filter = self.path_filter # used by atom_feed_entry.mako
274 c.path_filter = self.path_filter # used by atom_feed_entry.mako
275
275
276 if self.rhodecode_vcs_repo is None:
276 if self.rhodecode_vcs_repo is None:
277 # unable to fetch this repo as vcs instance, report back to user
277 # unable to fetch this repo as vcs instance, report back to user
278 h.flash(_(
278 h.flash(_(
279 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
279 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
280 "Please check if it exist, or is not damaged.") %
280 "Please check if it exist, or is not damaged.") %
281 {'repo_name': c.repo_name},
281 {'repo_name': c.repo_name},
282 category='error', ignore_duplicate=True)
282 category='error', ignore_duplicate=True)
283 if c.repository_requirements_missing:
283 if c.repository_requirements_missing:
284 route = self.request.matched_route.name
284 route = self.request.matched_route.name
285 if route.startswith(('edit_repo', 'repo_summary')):
285 if route.startswith(('edit_repo', 'repo_summary')):
286 # allow summary and edit repo on missing requirements
286 # allow summary and edit repo on missing requirements
287 return c
287 return c
288
288
289 raise HTTPFound(
289 raise HTTPFound(
290 h.route_path('repo_summary', repo_name=self.db_repo_name))
290 h.route_path('repo_summary', repo_name=self.db_repo_name))
291
291
292 else: # redirect if we don't show missing requirements
292 else: # redirect if we don't show missing requirements
293 raise HTTPFound(h.route_path('home'))
293 raise HTTPFound(h.route_path('home'))
294
294
295 c.has_origin_repo_read_perm = False
295 c.has_origin_repo_read_perm = False
296 if self.db_repo.fork:
296 if self.db_repo.fork:
297 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
297 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
298 'repository.write', 'repository.read', 'repository.admin')(
298 'repository.write', 'repository.read', 'repository.admin')(
299 self.db_repo.fork.repo_name, 'summary fork link')
299 self.db_repo.fork.repo_name, 'summary fork link')
300
300
301 return c
301 return c
302
302
303 def _get_f_path_unchecked(self, matchdict, default=None):
303 def _get_f_path_unchecked(self, matchdict, default=None):
304 """
304 """
305 Should only be used by redirects, everything else should call _get_f_path
305 Should only be used by redirects, everything else should call _get_f_path
306 """
306 """
307 f_path = matchdict.get('f_path')
307 f_path = matchdict.get('f_path')
308 if f_path:
308 if f_path:
309 # fix for multiple initial slashes that causes errors for GIT
309 # fix for multiple initial slashes that causes errors for GIT
310 return f_path.lstrip('/')
310 return f_path.lstrip('/')
311
311
312 return default
312 return default
313
313
314 def _get_f_path(self, matchdict, default=None):
314 def _get_f_path(self, matchdict, default=None):
315 f_path_match = self._get_f_path_unchecked(matchdict, default)
315 f_path_match = self._get_f_path_unchecked(matchdict, default)
316 return self.path_filter.assert_path_permissions(f_path_match)
316 return self.path_filter.assert_path_permissions(f_path_match)
317
317
318 def _get_general_setting(self, target_repo, settings_key, default=False):
318 def _get_general_setting(self, target_repo, settings_key, default=False):
319 settings_model = VcsSettingsModel(repo=target_repo)
319 settings_model = VcsSettingsModel(repo=target_repo)
320 settings = settings_model.get_general_settings()
320 settings = settings_model.get_general_settings()
321 return settings.get(settings_key, default)
321 return settings.get(settings_key, default)
322
322
323 def _get_repo_setting(self, target_repo, settings_key, default=False):
323 def _get_repo_setting(self, target_repo, settings_key, default=False):
324 settings_model = VcsSettingsModel(repo=target_repo)
324 settings_model = VcsSettingsModel(repo=target_repo)
325 settings = settings_model.get_repo_settings_inherited()
325 settings = settings_model.get_repo_settings_inherited()
326 return settings.get(settings_key, default)
326 return settings.get(settings_key, default)
327
327
328 def _get_readme_data(self, db_repo, renderer_type, commit_id=None, path='/'):
328 def _get_readme_data(self, db_repo, renderer_type, commit_id=None, path='/'):
329 log.debug('Looking for README file at path %s', path)
329 log.debug('Looking for README file at path %s', path)
330 if commit_id:
330 if commit_id:
331 landing_commit_id = commit_id
331 landing_commit_id = commit_id
332 else:
332 else:
333 landing_commit = db_repo.get_landing_commit()
333 landing_commit = db_repo.get_landing_commit()
334 if isinstance(landing_commit, EmptyCommit):
334 if isinstance(landing_commit, EmptyCommit):
335 return None, None
335 return None, None
336 landing_commit_id = landing_commit.raw_id
336 landing_commit_id = landing_commit.raw_id
337
337
338 cache_namespace_uid = 'cache_repo.{}'.format(db_repo.repo_id)
338 cache_namespace_uid = 'cache_repo.{}'.format(db_repo.repo_id)
339 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
339 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
340 start = time.time()
340 start = time.time()
341
341
342 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
342 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
343 def generate_repo_readme(repo_id, _commit_id, _repo_name, _readme_search_path, _renderer_type):
343 def generate_repo_readme(repo_id, _commit_id, _repo_name, _readme_search_path, _renderer_type):
344 readme_data = None
344 readme_data = None
345 readme_filename = None
345 readme_filename = None
346
346
347 commit = db_repo.get_commit(_commit_id)
347 commit = db_repo.get_commit(_commit_id)
348 log.debug("Searching for a README file at commit %s.", _commit_id)
348 log.debug("Searching for a README file at commit %s.", _commit_id)
349 readme_node = ReadmeFinder(_renderer_type).search(commit, path=_readme_search_path)
349 readme_node = ReadmeFinder(_renderer_type).search(commit, path=_readme_search_path)
350
350
351 if readme_node:
351 if readme_node:
352 log.debug('Found README node: %s', readme_node)
352 log.debug('Found README node: %s', readme_node)
353 relative_urls = {
353 relative_urls = {
354 'raw': h.route_path(
354 'raw': h.route_path(
355 'repo_file_raw', repo_name=_repo_name,
355 'repo_file_raw', repo_name=_repo_name,
356 commit_id=commit.raw_id, f_path=readme_node.path),
356 commit_id=commit.raw_id, f_path=readme_node.path),
357 'standard': h.route_path(
357 'standard': h.route_path(
358 'repo_files', repo_name=_repo_name,
358 'repo_files', repo_name=_repo_name,
359 commit_id=commit.raw_id, f_path=readme_node.path),
359 commit_id=commit.raw_id, f_path=readme_node.path),
360 }
360 }
361 readme_data = self._render_readme_or_none(commit, readme_node, relative_urls)
361 readme_data = self._render_readme_or_none(commit, readme_node, relative_urls)
362 readme_filename = readme_node.unicode_path
362 readme_filename = readme_node.unicode_path
363
363
364 return readme_data, readme_filename
364 return readme_data, readme_filename
365
365
366 readme_data, readme_filename = generate_repo_readme(
366 readme_data, readme_filename = generate_repo_readme(
367 db_repo.repo_id, landing_commit_id, db_repo.repo_name, path, renderer_type,)
367 db_repo.repo_id, landing_commit_id, db_repo.repo_name, path, renderer_type,)
368 compute_time = time.time() - start
368 compute_time = time.time() - start
369 log.debug('Repo README for path %s generated and computed in %.4fs',
369 log.debug('Repo README for path %s generated and computed in %.4fs',
370 path, compute_time)
370 path, compute_time)
371 return readme_data, readme_filename
371 return readme_data, readme_filename
372
372
373 def _render_readme_or_none(self, commit, readme_node, relative_urls):
373 def _render_readme_or_none(self, commit, readme_node, relative_urls):
374 log.debug('Found README file `%s` rendering...', readme_node.path)
374 log.debug('Found README file `%s` rendering...', readme_node.path)
375 renderer = MarkupRenderer()
375 renderer = MarkupRenderer()
376 try:
376 try:
377 html_source = renderer.render(
377 html_source = renderer.render(
378 readme_node.content, filename=readme_node.path)
378 readme_node.content, filename=readme_node.path)
379 if relative_urls:
379 if relative_urls:
380 return relative_links(html_source, relative_urls)
380 return relative_links(html_source, relative_urls)
381 return html_source
381 return html_source
382 except Exception:
382 except Exception:
383 log.exception(
383 log.exception(
384 "Exception while trying to render the README")
384 "Exception while trying to render the README")
385
385
386 def get_recache_flag(self):
386 def get_recache_flag(self):
387 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
387 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
388 flag_val = self.request.GET.get(flag_name)
388 flag_val = self.request.GET.get(flag_name)
389 if str2bool(flag_val):
389 if str2bool(flag_val):
390 return True
390 return True
391 return False
391 return False
392
392
393 def get_commit_preload_attrs(cls):
393 def get_commit_preload_attrs(cls):
394 pre_load = ['author', 'branch', 'date', 'message', 'parents',
394 pre_load = ['author', 'branch', 'date', 'message', 'parents',
395 'obsolete', 'phase', 'hidden']
395 'obsolete', 'phase', 'hidden']
396 return pre_load
396 return pre_load
397
397
398
398
399 class PathFilter(object):
399 class PathFilter(object):
400
400
401 # Expects and instance of BasePathPermissionChecker or None
401 # Expects and instance of BasePathPermissionChecker or None
402 def __init__(self, permission_checker):
402 def __init__(self, permission_checker):
403 self.permission_checker = permission_checker
403 self.permission_checker = permission_checker
404
404
405 def assert_path_permissions(self, path):
405 def assert_path_permissions(self, path):
406 if self.path_access_allowed(path):
406 if self.path_access_allowed(path):
407 return path
407 return path
408 raise HTTPForbidden()
408 raise HTTPForbidden()
409
409
410 def path_access_allowed(self, path):
410 def path_access_allowed(self, path):
411 log.debug('Checking ACL permissions for PathFilter for `%s`', path)
411 log.debug('Checking ACL permissions for PathFilter for `%s`', path)
412 if self.permission_checker:
412 if self.permission_checker:
413 has_access = path and self.permission_checker.has_access(path)
413 has_access = path and self.permission_checker.has_access(path)
414 log.debug('ACL Permissions checker enabled, ACL Check has_access: %s', has_access)
414 log.debug('ACL Permissions checker enabled, ACL Check has_access: %s', has_access)
415 return has_access
415 return has_access
416
416
417 log.debug('ACL permissions checker not enabled, skipping...')
417 log.debug('ACL permissions checker not enabled, skipping...')
418 return True
418 return True
419
419
420 def filter_patchset(self, patchset):
420 def filter_patchset(self, patchset):
421 if not self.permission_checker or not patchset:
421 if not self.permission_checker or not patchset:
422 return patchset, False
422 return patchset, False
423 had_filtered = False
423 had_filtered = False
424 filtered_patchset = []
424 filtered_patchset = []
425 for patch in patchset:
425 for patch in patchset:
426 filename = patch.get('filename', None)
426 filename = patch.get('filename', None)
427 if not filename or self.permission_checker.has_access(filename):
427 if not filename or self.permission_checker.has_access(filename):
428 filtered_patchset.append(patch)
428 filtered_patchset.append(patch)
429 else:
429 else:
430 had_filtered = True
430 had_filtered = True
431 if had_filtered:
431 if had_filtered:
432 if isinstance(patchset, diffs.LimitedDiffContainer):
432 if isinstance(patchset, diffs.LimitedDiffContainer):
433 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
433 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
434 return filtered_patchset, True
434 return filtered_patchset, True
435 else:
435 else:
436 return patchset, False
436 return patchset, False
437
437
438 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
438 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
439 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
439 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
440 result = diffset.render_patchset(
440 result = diffset.render_patchset(
441 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
441 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
442 result.has_hidden_changes = has_hidden_changes
442 result.has_hidden_changes = has_hidden_changes
443 return result
443 return result
444
444
445 def get_raw_patch(self, diff_processor):
445 def get_raw_patch(self, diff_processor):
446 if self.permission_checker is None:
446 if self.permission_checker is None:
447 return diff_processor.as_raw()
447 return diff_processor.as_raw()
448 elif self.permission_checker.has_full_access:
448 elif self.permission_checker.has_full_access:
449 return diff_processor.as_raw()
449 return diff_processor.as_raw()
450 else:
450 else:
451 return '# Repository has user-specific filters, raw patch generation is disabled.'
451 return '# Repository has user-specific filters, raw patch generation is disabled.'
452
452
453 @property
453 @property
454 def is_enabled(self):
454 def is_enabled(self):
455 return self.permission_checker is not None
455 return self.permission_checker is not None
456
456
457
457
458 class RepoGroupAppView(BaseAppView):
458 class RepoGroupAppView(BaseAppView):
459 def __init__(self, context, request):
459 def __init__(self, context, request):
460 super(RepoGroupAppView, self).__init__(context, request)
460 super(RepoGroupAppView, self).__init__(context, request)
461 self.db_repo_group = request.db_repo_group
461 self.db_repo_group = request.db_repo_group
462 self.db_repo_group_name = self.db_repo_group.group_name
462 self.db_repo_group_name = self.db_repo_group.group_name
463
463
464 def _get_local_tmpl_context(self, include_app_defaults=True):
464 def _get_local_tmpl_context(self, include_app_defaults=True):
465 _ = self.request.translate
465 _ = self.request.translate
466 c = super(RepoGroupAppView, self)._get_local_tmpl_context(
466 c = super(RepoGroupAppView, self)._get_local_tmpl_context(
467 include_app_defaults=include_app_defaults)
467 include_app_defaults=include_app_defaults)
468 c.repo_group = self.db_repo_group
468 c.repo_group = self.db_repo_group
469 return c
469 return c
470
470
471 def _revoke_perms_on_yourself(self, form_result):
471 def _revoke_perms_on_yourself(self, form_result):
472 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
472 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
473 form_result['perm_updates'])
473 form_result['perm_updates'])
474 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
474 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
475 form_result['perm_additions'])
475 form_result['perm_additions'])
476 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
476 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
477 form_result['perm_deletions'])
477 form_result['perm_deletions'])
478 admin_perm = 'group.admin'
478 admin_perm = 'group.admin'
479 if _updates and _updates[0][1] != admin_perm or \
479 if _updates and _updates[0][1] != admin_perm or \
480 _additions and _additions[0][1] != admin_perm or \
480 _additions and _additions[0][1] != admin_perm or \
481 _deletions and _deletions[0][1] != admin_perm:
481 _deletions and _deletions[0][1] != admin_perm:
482 return True
482 return True
483 return False
483 return False
484
484
485
485
486 class UserGroupAppView(BaseAppView):
486 class UserGroupAppView(BaseAppView):
487 def __init__(self, context, request):
487 def __init__(self, context, request):
488 super(UserGroupAppView, self).__init__(context, request)
488 super(UserGroupAppView, self).__init__(context, request)
489 self.db_user_group = request.db_user_group
489 self.db_user_group = request.db_user_group
490 self.db_user_group_name = self.db_user_group.users_group_name
490 self.db_user_group_name = self.db_user_group.users_group_name
491
491
492
492
493 class UserAppView(BaseAppView):
493 class UserAppView(BaseAppView):
494 def __init__(self, context, request):
494 def __init__(self, context, request):
495 super(UserAppView, self).__init__(context, request)
495 super(UserAppView, self).__init__(context, request)
496 self.db_user = request.db_user
496 self.db_user = request.db_user
497 self.db_user_id = self.db_user.user_id
497 self.db_user_id = self.db_user.user_id
498
498
499 _ = self.request.translate
499 _ = self.request.translate
500 if not request.db_user_supports_default:
500 if not request.db_user_supports_default:
501 if self.db_user.username == User.DEFAULT_USER:
501 if self.db_user.username == User.DEFAULT_USER:
502 h.flash(_("Editing user `{}` is disabled.".format(
502 h.flash(_("Editing user `{}` is disabled.".format(
503 User.DEFAULT_USER)), category='warning')
503 User.DEFAULT_USER)), category='warning')
504 raise HTTPFound(h.route_path('users'))
504 raise HTTPFound(h.route_path('users'))
505
505
506
506
507 class DataGridAppView(object):
507 class DataGridAppView(object):
508 """
508 """
509 Common class to have re-usable grid rendering components
509 Common class to have re-usable grid rendering components
510 """
510 """
511
511
512 def _extract_ordering(self, request, column_map=None):
512 def _extract_ordering(self, request, column_map=None):
513 column_map = column_map or {}
513 column_map = column_map or {}
514 column_index = safe_int(request.GET.get('order[0][column]'))
514 column_index = safe_int(request.GET.get('order[0][column]'))
515 order_dir = request.GET.get(
515 order_dir = request.GET.get(
516 'order[0][dir]', 'desc')
516 'order[0][dir]', 'desc')
517 order_by = request.GET.get(
517 order_by = request.GET.get(
518 'columns[%s][data][sort]' % column_index, 'name_raw')
518 'columns[%s][data][sort]' % column_index, 'name_raw')
519
519
520 # translate datatable to DB columns
520 # translate datatable to DB columns
521 order_by = column_map.get(order_by) or order_by
521 order_by = column_map.get(order_by) or order_by
522
522
523 search_q = request.GET.get('search[value]')
523 search_q = request.GET.get('search[value]')
524 return search_q, order_by, order_dir
524 return search_q, order_by, order_dir
525
525
526 def _extract_chunk(self, request):
526 def _extract_chunk(self, request):
527 start = safe_int(request.GET.get('start'), 0)
527 start = safe_int(request.GET.get('start'), 0)
528 length = safe_int(request.GET.get('length'), 25)
528 length = safe_int(request.GET.get('length'), 25)
529 draw = safe_int(request.GET.get('draw'))
529 draw = safe_int(request.GET.get('draw'))
530 return draw, start, length
530 return draw, start, length
531
531
532 def _get_order_col(self, order_by, model):
532 def _get_order_col(self, order_by, model):
533 if isinstance(order_by, compat.string_types):
533 if isinstance(order_by, compat.string_types):
534 try:
534 try:
535 return operator.attrgetter(order_by)(model)
535 return operator.attrgetter(order_by)(model)
536 except AttributeError:
536 except AttributeError:
537 return None
537 return None
538 else:
538 else:
539 return order_by
539 return order_by
540
540
541
541
542 class BaseReferencesView(RepoAppView):
542 class BaseReferencesView(RepoAppView):
543 """
543 """
544 Base for reference view for branches, tags and bookmarks.
544 Base for reference view for branches, tags and bookmarks.
545 """
545 """
546 def load_default_context(self):
546 def load_default_context(self):
547 c = self._get_local_tmpl_context()
547 c = self._get_local_tmpl_context()
548 return c
548 return c
549
549
550 def load_refs_context(self, ref_items, partials_template):
550 def load_refs_context(self, ref_items, partials_template):
551 _render = self.request.get_partial_renderer(partials_template)
551 _render = self.request.get_partial_renderer(partials_template)
552 pre_load = ["author", "date", "message", "parents"]
552 pre_load = ["author", "date", "message", "parents"]
553
553
554 is_svn = h.is_svn(self.rhodecode_vcs_repo)
554 is_svn = h.is_svn(self.rhodecode_vcs_repo)
555 is_hg = h.is_hg(self.rhodecode_vcs_repo)
555 is_hg = h.is_hg(self.rhodecode_vcs_repo)
556
556
557 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
557 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
558
558
559 closed_refs = {}
559 closed_refs = {}
560 if is_hg:
560 if is_hg:
561 closed_refs = self.rhodecode_vcs_repo.branches_closed
561 closed_refs = self.rhodecode_vcs_repo.branches_closed
562
562
563 data = []
563 data = []
564 for ref_name, commit_id in ref_items:
564 for ref_name, commit_id in ref_items:
565 commit = self.rhodecode_vcs_repo.get_commit(
565 commit = self.rhodecode_vcs_repo.get_commit(
566 commit_id=commit_id, pre_load=pre_load)
566 commit_id=commit_id, pre_load=pre_load)
567 closed = ref_name in closed_refs
567 closed = ref_name in closed_refs
568
568
569 # TODO: johbo: Unify generation of reference links
569 # TODO: johbo: Unify generation of reference links
570 use_commit_id = '/' in ref_name or is_svn
570 use_commit_id = '/' in ref_name or is_svn
571
571
572 if use_commit_id:
572 if use_commit_id:
573 files_url = h.route_path(
573 files_url = h.route_path(
574 'repo_files',
574 'repo_files',
575 repo_name=self.db_repo_name,
575 repo_name=self.db_repo_name,
576 f_path=ref_name if is_svn else '',
576 f_path=ref_name if is_svn else '',
577 commit_id=commit_id,
577 commit_id=commit_id,
578 _query=dict(at=ref_name)
578 _query=dict(at=ref_name)
579 )
579 )
580
580
581 else:
581 else:
582 files_url = h.route_path(
582 files_url = h.route_path(
583 'repo_files',
583 'repo_files',
584 repo_name=self.db_repo_name,
584 repo_name=self.db_repo_name,
585 f_path=ref_name if is_svn else '',
585 f_path=ref_name if is_svn else '',
586 commit_id=ref_name,
586 commit_id=ref_name,
587 _query=dict(at=ref_name)
587 _query=dict(at=ref_name)
588 )
588 )
589
589
590 data.append({
590 data.append({
591 "name": _render('name', ref_name, files_url, closed),
591 "name": _render('name', ref_name, files_url, closed),
592 "name_raw": ref_name,
592 "name_raw": ref_name,
593 "date": _render('date', commit.date),
593 "date": _render('date', commit.date),
594 "date_raw": datetime_to_time(commit.date),
594 "date_raw": datetime_to_time(commit.date),
595 "author": _render('author', commit.author),
595 "author": _render('author', commit.author),
596 "commit": _render(
596 "commit": _render(
597 'commit', commit.message, commit.raw_id, commit.idx),
597 'commit', commit.message, commit.raw_id, commit.idx),
598 "commit_raw": commit.idx,
598 "commit_raw": commit.idx,
599 "compare": _render(
599 "compare": _render(
600 'compare', format_ref_id(ref_name, commit.raw_id)),
600 'compare', format_ref_id(ref_name, commit.raw_id)),
601 })
601 })
602
602
603 return data
603 return data
604
604
605
605
606 class RepoRoutePredicate(object):
606 class RepoRoutePredicate(object):
607 def __init__(self, val, config):
607 def __init__(self, val, config):
608 self.val = val
608 self.val = val
609
609
610 def text(self):
610 def text(self):
611 return 'repo_route = %s' % self.val
611 return 'repo_route = %s' % self.val
612
612
613 phash = text
613 phash = text
614
614
615 def __call__(self, info, request):
615 def __call__(self, info, request):
616 if hasattr(request, 'vcs_call'):
616 if hasattr(request, 'vcs_call'):
617 # skip vcs calls
617 # skip vcs calls
618 return
618 return
619
619
620 repo_name = info['match']['repo_name']
620 repo_name = info['match']['repo_name']
621 if repo_name != repo_name_slug(repo_name):
621
622 repo_name_parts = repo_name.split('/')
623 repo_slugs = [x for x in map(lambda x: repo_name_slug(x), repo_name_parts)]
624
625 if repo_name_parts != repo_slugs:
622 # short-skip if the repo-name doesn't follow slug rule
626 # short-skip if the repo-name doesn't follow slug rule
627 log.warning('repo_name: %s is different than slug %s', repo_name_parts, repo_slugs)
623 return False
628 return False
624
629
625 repo_model = repo.RepoModel()
630 repo_model = repo.RepoModel()
626
631
627 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
632 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
628
633
629 def redirect_if_creating(route_info, db_repo):
634 def redirect_if_creating(route_info, db_repo):
630 skip_views = ['edit_repo_advanced_delete']
635 skip_views = ['edit_repo_advanced_delete']
631 route = route_info['route']
636 route = route_info['route']
632 # we should skip delete view so we can actually "remove" repositories
637 # we should skip delete view so we can actually "remove" repositories
633 # if they get stuck in creating state.
638 # if they get stuck in creating state.
634 if route.name in skip_views:
639 if route.name in skip_views:
635 return
640 return
636
641
637 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
642 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
638 repo_creating_url = request.route_path(
643 repo_creating_url = request.route_path(
639 'repo_creating', repo_name=db_repo.repo_name)
644 'repo_creating', repo_name=db_repo.repo_name)
640 raise HTTPFound(repo_creating_url)
645 raise HTTPFound(repo_creating_url)
641
646
642 if by_name_match:
647 if by_name_match:
643 # register this as request object we can re-use later
648 # register this as request object we can re-use later
644 request.db_repo = by_name_match
649 request.db_repo = by_name_match
645 redirect_if_creating(info, by_name_match)
650 redirect_if_creating(info, by_name_match)
646 return True
651 return True
647
652
648 by_id_match = repo_model.get_repo_by_id(repo_name)
653 by_id_match = repo_model.get_repo_by_id(repo_name)
649 if by_id_match:
654 if by_id_match:
650 request.db_repo = by_id_match
655 request.db_repo = by_id_match
651 redirect_if_creating(info, by_id_match)
656 redirect_if_creating(info, by_id_match)
652 return True
657 return True
653
658
654 return False
659 return False
655
660
656
661
657 class RepoForbidArchivedRoutePredicate(object):
662 class RepoForbidArchivedRoutePredicate(object):
658 def __init__(self, val, config):
663 def __init__(self, val, config):
659 self.val = val
664 self.val = val
660
665
661 def text(self):
666 def text(self):
662 return 'repo_forbid_archived = %s' % self.val
667 return 'repo_forbid_archived = %s' % self.val
663
668
664 phash = text
669 phash = text
665
670
666 def __call__(self, info, request):
671 def __call__(self, info, request):
667 _ = request.translate
672 _ = request.translate
668 rhodecode_db_repo = request.db_repo
673 rhodecode_db_repo = request.db_repo
669
674
670 log.debug(
675 log.debug(
671 '%s checking if archived flag for repo for %s',
676 '%s checking if archived flag for repo for %s',
672 self.__class__.__name__, rhodecode_db_repo.repo_name)
677 self.__class__.__name__, rhodecode_db_repo.repo_name)
673
678
674 if rhodecode_db_repo.archived:
679 if rhodecode_db_repo.archived:
675 log.warning('Current view is not supported for archived repo:%s',
680 log.warning('Current view is not supported for archived repo:%s',
676 rhodecode_db_repo.repo_name)
681 rhodecode_db_repo.repo_name)
677
682
678 h.flash(
683 h.flash(
679 h.literal(_('Action not supported for archived repository.')),
684 h.literal(_('Action not supported for archived repository.')),
680 category='warning')
685 category='warning')
681 summary_url = request.route_path(
686 summary_url = request.route_path(
682 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
687 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
683 raise HTTPFound(summary_url)
688 raise HTTPFound(summary_url)
684 return True
689 return True
685
690
686
691
687 class RepoTypeRoutePredicate(object):
692 class RepoTypeRoutePredicate(object):
688 def __init__(self, val, config):
693 def __init__(self, val, config):
689 self.val = val or ['hg', 'git', 'svn']
694 self.val = val or ['hg', 'git', 'svn']
690
695
691 def text(self):
696 def text(self):
692 return 'repo_accepted_type = %s' % self.val
697 return 'repo_accepted_type = %s' % self.val
693
698
694 phash = text
699 phash = text
695
700
696 def __call__(self, info, request):
701 def __call__(self, info, request):
697 if hasattr(request, 'vcs_call'):
702 if hasattr(request, 'vcs_call'):
698 # skip vcs calls
703 # skip vcs calls
699 return
704 return
700
705
701 rhodecode_db_repo = request.db_repo
706 rhodecode_db_repo = request.db_repo
702
707
703 log.debug(
708 log.debug(
704 '%s checking repo type for %s in %s',
709 '%s checking repo type for %s in %s',
705 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
710 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
706
711
707 if rhodecode_db_repo.repo_type in self.val:
712 if rhodecode_db_repo.repo_type in self.val:
708 return True
713 return True
709 else:
714 else:
710 log.warning('Current view is not supported for repo type:%s',
715 log.warning('Current view is not supported for repo type:%s',
711 rhodecode_db_repo.repo_type)
716 rhodecode_db_repo.repo_type)
712 return False
717 return False
713
718
714
719
715 class RepoGroupRoutePredicate(object):
720 class RepoGroupRoutePredicate(object):
716 def __init__(self, val, config):
721 def __init__(self, val, config):
717 self.val = val
722 self.val = val
718
723
719 def text(self):
724 def text(self):
720 return 'repo_group_route = %s' % self.val
725 return 'repo_group_route = %s' % self.val
721
726
722 phash = text
727 phash = text
723
728
724 def __call__(self, info, request):
729 def __call__(self, info, request):
725 if hasattr(request, 'vcs_call'):
730 if hasattr(request, 'vcs_call'):
726 # skip vcs calls
731 # skip vcs calls
727 return
732 return
728
733
729 repo_group_name = info['match']['repo_group_name']
734 repo_group_name = info['match']['repo_group_name']
730 if repo_group_name != repo_name_slug(repo_group_name):
735
736 repo_group_name_parts = repo_group_name.split('/')
737 repo_group_slugs = [x for x in map(lambda x: repo_name_slug(x), repo_group_name_parts)]
738 if repo_group_name_parts != repo_group_slugs:
739 # short-skip if the repo-name doesn't follow slug rule
740 log.warning('repo_group_name: %s is different than slug %s', repo_group_name_parts, repo_group_slugs)
731 return False
741 return False
732
742
733 repo_group_model = repo_group.RepoGroupModel()
743 repo_group_model = repo_group.RepoGroupModel()
734 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
744 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
735
745
736 if by_name_match:
746 if by_name_match:
737 # register this as request object we can re-use later
747 # register this as request object we can re-use later
738 request.db_repo_group = by_name_match
748 request.db_repo_group = by_name_match
739 return True
749 return True
740
750
741 return False
751 return False
742
752
743
753
744 class UserGroupRoutePredicate(object):
754 class UserGroupRoutePredicate(object):
745 def __init__(self, val, config):
755 def __init__(self, val, config):
746 self.val = val
756 self.val = val
747
757
748 def text(self):
758 def text(self):
749 return 'user_group_route = %s' % self.val
759 return 'user_group_route = %s' % self.val
750
760
751 phash = text
761 phash = text
752
762
753 def __call__(self, info, request):
763 def __call__(self, info, request):
754 if hasattr(request, 'vcs_call'):
764 if hasattr(request, 'vcs_call'):
755 # skip vcs calls
765 # skip vcs calls
756 return
766 return
757
767
758 user_group_id = info['match']['user_group_id']
768 user_group_id = info['match']['user_group_id']
759 user_group_model = user_group.UserGroup()
769 user_group_model = user_group.UserGroup()
760 by_id_match = user_group_model.get(user_group_id, cache=False)
770 by_id_match = user_group_model.get(user_group_id, cache=False)
761
771
762 if by_id_match:
772 if by_id_match:
763 # register this as request object we can re-use later
773 # register this as request object we can re-use later
764 request.db_user_group = by_id_match
774 request.db_user_group = by_id_match
765 return True
775 return True
766
776
767 return False
777 return False
768
778
769
779
770 class UserRoutePredicateBase(object):
780 class UserRoutePredicateBase(object):
771 supports_default = None
781 supports_default = None
772
782
773 def __init__(self, val, config):
783 def __init__(self, val, config):
774 self.val = val
784 self.val = val
775
785
776 def text(self):
786 def text(self):
777 raise NotImplementedError()
787 raise NotImplementedError()
778
788
779 def __call__(self, info, request):
789 def __call__(self, info, request):
780 if hasattr(request, 'vcs_call'):
790 if hasattr(request, 'vcs_call'):
781 # skip vcs calls
791 # skip vcs calls
782 return
792 return
783
793
784 user_id = info['match']['user_id']
794 user_id = info['match']['user_id']
785 user_model = user.User()
795 user_model = user.User()
786 by_id_match = user_model.get(user_id, cache=False)
796 by_id_match = user_model.get(user_id, cache=False)
787
797
788 if by_id_match:
798 if by_id_match:
789 # register this as request object we can re-use later
799 # register this as request object we can re-use later
790 request.db_user = by_id_match
800 request.db_user = by_id_match
791 request.db_user_supports_default = self.supports_default
801 request.db_user_supports_default = self.supports_default
792 return True
802 return True
793
803
794 return False
804 return False
795
805
796
806
797 class UserRoutePredicate(UserRoutePredicateBase):
807 class UserRoutePredicate(UserRoutePredicateBase):
798 supports_default = False
808 supports_default = False
799
809
800 def text(self):
810 def text(self):
801 return 'user_route = %s' % self.val
811 return 'user_route = %s' % self.val
802
812
803 phash = text
813 phash = text
804
814
805
815
806 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
816 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
807 supports_default = True
817 supports_default = True
808
818
809 def text(self):
819 def text(self):
810 return 'user_with_default_route = %s' % self.val
820 return 'user_with_default_route = %s' % self.val
811
821
812 phash = text
822 phash = text
813
823
814
824
815 def includeme(config):
825 def includeme(config):
816 config.add_route_predicate(
826 config.add_route_predicate(
817 'repo_route', RepoRoutePredicate)
827 'repo_route', RepoRoutePredicate)
818 config.add_route_predicate(
828 config.add_route_predicate(
819 'repo_accepted_types', RepoTypeRoutePredicate)
829 'repo_accepted_types', RepoTypeRoutePredicate)
820 config.add_route_predicate(
830 config.add_route_predicate(
821 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
831 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
822 config.add_route_predicate(
832 config.add_route_predicate(
823 'repo_group_route', RepoGroupRoutePredicate)
833 'repo_group_route', RepoGroupRoutePredicate)
824 config.add_route_predicate(
834 config.add_route_predicate(
825 'user_group_route', UserGroupRoutePredicate)
835 'user_group_route', UserGroupRoutePredicate)
826 config.add_route_predicate(
836 config.add_route_predicate(
827 'user_route_with_default', UserRouteWithDefaultPredicate)
837 'user_route_with_default', UserRouteWithDefaultPredicate)
828 config.add_route_predicate(
838 config.add_route_predicate(
829 'user_route', UserRoutePredicate)
839 'user_route', UserRoutePredicate)
@@ -1,800 +1,800 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 Utilities library for RhodeCode
22 Utilities library for RhodeCode
23 """
23 """
24
24
25 import datetime
25 import datetime
26 import decorator
26 import decorator
27 import json
27 import json
28 import logging
28 import logging
29 import os
29 import os
30 import re
30 import re
31 import sys
31 import sys
32 import shutil
32 import shutil
33 import socket
33 import socket
34 import tempfile
34 import tempfile
35 import traceback
35 import traceback
36 import tarfile
36 import tarfile
37 import warnings
37 import warnings
38 import hashlib
38 import hashlib
39 from os.path import join as jn
39 from os.path import join as jn
40
40
41 import paste
41 import paste
42 import pkg_resources
42 import pkg_resources
43 from webhelpers2.text import collapse, remove_formatting
43 from webhelpers2.text import collapse, remove_formatting
44 from mako import exceptions
44 from mako import exceptions
45 from pyramid.threadlocal import get_current_registry
45 from pyramid.threadlocal import get_current_registry
46 from rhodecode.lib.request import Request
46 from rhodecode.lib.request import Request
47
47
48 from rhodecode.lib.vcs.backends.base import Config
48 from rhodecode.lib.vcs.backends.base import Config
49 from rhodecode.lib.vcs.exceptions import VCSError
49 from rhodecode.lib.vcs.exceptions import VCSError
50 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
50 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
51 from rhodecode.lib.utils2 import (
51 from rhodecode.lib.utils2 import (
52 safe_str, safe_unicode, get_current_rhodecode_user, md5, sha1)
52 safe_str, safe_unicode, get_current_rhodecode_user, md5, sha1)
53 from rhodecode.model import meta
53 from rhodecode.model import meta
54 from rhodecode.model.db import (
54 from rhodecode.model.db import (
55 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
55 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
56 from rhodecode.model.meta import Session
56 from rhodecode.model.meta import Session
57
57
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
61 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
62
62
63 # String which contains characters that are not allowed in slug names for
63 # String which contains characters that are not allowed in slug names for
64 # repositories or repository groups. It is properly escaped to use it in
64 # repositories or repository groups. It is properly escaped to use it in
65 # regular expressions.
65 # regular expressions.
66 SLUG_BAD_CHARS = re.escape('`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
66 SLUG_BAD_CHARS = re.escape('`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
67
67
68 # Regex that matches forbidden characters in repo/group slugs.
68 # Regex that matches forbidden characters in repo/group slugs.
69 SLUG_BAD_CHAR_RE = re.compile('[{}]'.format(SLUG_BAD_CHARS))
69 SLUG_BAD_CHAR_RE = re.compile('[{}\x00-\x08\x0b-\x0c\x0e-\x1f]'.format(SLUG_BAD_CHARS))
70
70
71 # Regex that matches allowed characters in repo/group slugs.
71 # Regex that matches allowed characters in repo/group slugs.
72 SLUG_GOOD_CHAR_RE = re.compile('[^{}]'.format(SLUG_BAD_CHARS))
72 SLUG_GOOD_CHAR_RE = re.compile('[^{}]'.format(SLUG_BAD_CHARS))
73
73
74 # Regex that matches whole repo/group slugs.
74 # Regex that matches whole repo/group slugs.
75 SLUG_RE = re.compile('[^{}]+'.format(SLUG_BAD_CHARS))
75 SLUG_RE = re.compile('[^{}]+'.format(SLUG_BAD_CHARS))
76
76
77 _license_cache = None
77 _license_cache = None
78
78
79
79
80 def repo_name_slug(value):
80 def repo_name_slug(value):
81 """
81 """
82 Return slug of name of repository
82 Return slug of name of repository
83 This function is called on each creation/modification
83 This function is called on each creation/modification
84 of repository to prevent bad names in repo
84 of repository to prevent bad names in repo
85 """
85 """
86 replacement_char = '-'
86 replacement_char = '-'
87
87
88 slug = remove_formatting(value)
88 slug = remove_formatting(value)
89 slug = SLUG_BAD_CHAR_RE.sub('', slug)
89 slug = SLUG_BAD_CHAR_RE.sub('', slug)
90 slug = re.sub('[\s]+', '-', slug)
90 slug = re.sub('[\s]+', '-', slug)
91 slug = collapse(slug, replacement_char)
91 slug = collapse(slug, replacement_char)
92 return slug
92 return slug
93
93
94
94
95 #==============================================================================
95 #==============================================================================
96 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
96 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
97 #==============================================================================
97 #==============================================================================
98 def get_repo_slug(request):
98 def get_repo_slug(request):
99 _repo = ''
99 _repo = ''
100
100
101 if hasattr(request, 'db_repo'):
101 if hasattr(request, 'db_repo'):
102 # if our requests has set db reference use it for name, this
102 # if our requests has set db reference use it for name, this
103 # translates the example.com/_<id> into proper repo names
103 # translates the example.com/_<id> into proper repo names
104 _repo = request.db_repo.repo_name
104 _repo = request.db_repo.repo_name
105 elif getattr(request, 'matchdict', None):
105 elif getattr(request, 'matchdict', None):
106 # pyramid
106 # pyramid
107 _repo = request.matchdict.get('repo_name')
107 _repo = request.matchdict.get('repo_name')
108
108
109 if _repo:
109 if _repo:
110 _repo = _repo.rstrip('/')
110 _repo = _repo.rstrip('/')
111 return _repo
111 return _repo
112
112
113
113
114 def get_repo_group_slug(request):
114 def get_repo_group_slug(request):
115 _group = ''
115 _group = ''
116 if hasattr(request, 'db_repo_group'):
116 if hasattr(request, 'db_repo_group'):
117 # if our requests has set db reference use it for name, this
117 # if our requests has set db reference use it for name, this
118 # translates the example.com/_<id> into proper repo group names
118 # translates the example.com/_<id> into proper repo group names
119 _group = request.db_repo_group.group_name
119 _group = request.db_repo_group.group_name
120 elif getattr(request, 'matchdict', None):
120 elif getattr(request, 'matchdict', None):
121 # pyramid
121 # pyramid
122 _group = request.matchdict.get('repo_group_name')
122 _group = request.matchdict.get('repo_group_name')
123
123
124 if _group:
124 if _group:
125 _group = _group.rstrip('/')
125 _group = _group.rstrip('/')
126 return _group
126 return _group
127
127
128
128
129 def get_user_group_slug(request):
129 def get_user_group_slug(request):
130 _user_group = ''
130 _user_group = ''
131
131
132 if hasattr(request, 'db_user_group'):
132 if hasattr(request, 'db_user_group'):
133 _user_group = request.db_user_group.users_group_name
133 _user_group = request.db_user_group.users_group_name
134 elif getattr(request, 'matchdict', None):
134 elif getattr(request, 'matchdict', None):
135 # pyramid
135 # pyramid
136 _user_group = request.matchdict.get('user_group_id')
136 _user_group = request.matchdict.get('user_group_id')
137 _user_group_name = request.matchdict.get('user_group_name')
137 _user_group_name = request.matchdict.get('user_group_name')
138 try:
138 try:
139 if _user_group:
139 if _user_group:
140 _user_group = UserGroup.get(_user_group)
140 _user_group = UserGroup.get(_user_group)
141 elif _user_group_name:
141 elif _user_group_name:
142 _user_group = UserGroup.get_by_group_name(_user_group_name)
142 _user_group = UserGroup.get_by_group_name(_user_group_name)
143
143
144 if _user_group:
144 if _user_group:
145 _user_group = _user_group.users_group_name
145 _user_group = _user_group.users_group_name
146 except Exception:
146 except Exception:
147 log.exception('Failed to get user group by id and name')
147 log.exception('Failed to get user group by id and name')
148 # catch all failures here
148 # catch all failures here
149 return None
149 return None
150
150
151 return _user_group
151 return _user_group
152
152
153
153
154 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
154 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
155 """
155 """
156 Scans given path for repos and return (name,(type,path)) tuple
156 Scans given path for repos and return (name,(type,path)) tuple
157
157
158 :param path: path to scan for repositories
158 :param path: path to scan for repositories
159 :param recursive: recursive search and return names with subdirs in front
159 :param recursive: recursive search and return names with subdirs in front
160 """
160 """
161
161
162 # remove ending slash for better results
162 # remove ending slash for better results
163 path = path.rstrip(os.sep)
163 path = path.rstrip(os.sep)
164 log.debug('now scanning in %s location recursive:%s...', path, recursive)
164 log.debug('now scanning in %s location recursive:%s...', path, recursive)
165
165
166 def _get_repos(p):
166 def _get_repos(p):
167 dirpaths = _get_dirpaths(p)
167 dirpaths = _get_dirpaths(p)
168 if not _is_dir_writable(p):
168 if not _is_dir_writable(p):
169 log.warning('repo path without write access: %s', p)
169 log.warning('repo path without write access: %s', p)
170
170
171 for dirpath in dirpaths:
171 for dirpath in dirpaths:
172 if os.path.isfile(os.path.join(p, dirpath)):
172 if os.path.isfile(os.path.join(p, dirpath)):
173 continue
173 continue
174 cur_path = os.path.join(p, dirpath)
174 cur_path = os.path.join(p, dirpath)
175
175
176 # skip removed repos
176 # skip removed repos
177 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
177 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
178 continue
178 continue
179
179
180 #skip .<somethin> dirs
180 #skip .<somethin> dirs
181 if dirpath.startswith('.'):
181 if dirpath.startswith('.'):
182 continue
182 continue
183
183
184 try:
184 try:
185 scm_info = get_scm(cur_path)
185 scm_info = get_scm(cur_path)
186 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
186 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
187 except VCSError:
187 except VCSError:
188 if not recursive:
188 if not recursive:
189 continue
189 continue
190 #check if this dir containts other repos for recursive scan
190 #check if this dir containts other repos for recursive scan
191 rec_path = os.path.join(p, dirpath)
191 rec_path = os.path.join(p, dirpath)
192 if os.path.isdir(rec_path):
192 if os.path.isdir(rec_path):
193 for inner_scm in _get_repos(rec_path):
193 for inner_scm in _get_repos(rec_path):
194 yield inner_scm
194 yield inner_scm
195
195
196 return _get_repos(path)
196 return _get_repos(path)
197
197
198
198
199 def _get_dirpaths(p):
199 def _get_dirpaths(p):
200 try:
200 try:
201 # OS-independable way of checking if we have at least read-only
201 # OS-independable way of checking if we have at least read-only
202 # access or not.
202 # access or not.
203 dirpaths = os.listdir(p)
203 dirpaths = os.listdir(p)
204 except OSError:
204 except OSError:
205 log.warning('ignoring repo path without read access: %s', p)
205 log.warning('ignoring repo path without read access: %s', p)
206 return []
206 return []
207
207
208 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
208 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
209 # decode paths and suddenly returns unicode objects itself. The items it
209 # decode paths and suddenly returns unicode objects itself. The items it
210 # cannot decode are returned as strings and cause issues.
210 # cannot decode are returned as strings and cause issues.
211 #
211 #
212 # Those paths are ignored here until a solid solution for path handling has
212 # Those paths are ignored here until a solid solution for path handling has
213 # been built.
213 # been built.
214 expected_type = type(p)
214 expected_type = type(p)
215
215
216 def _has_correct_type(item):
216 def _has_correct_type(item):
217 if type(item) is not expected_type:
217 if type(item) is not expected_type:
218 log.error(
218 log.error(
219 u"Ignoring path %s since it cannot be decoded into unicode.",
219 u"Ignoring path %s since it cannot be decoded into unicode.",
220 # Using "repr" to make sure that we see the byte value in case
220 # Using "repr" to make sure that we see the byte value in case
221 # of support.
221 # of support.
222 repr(item))
222 repr(item))
223 return False
223 return False
224 return True
224 return True
225
225
226 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
226 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
227
227
228 return dirpaths
228 return dirpaths
229
229
230
230
231 def _is_dir_writable(path):
231 def _is_dir_writable(path):
232 """
232 """
233 Probe if `path` is writable.
233 Probe if `path` is writable.
234
234
235 Due to trouble on Cygwin / Windows, this is actually probing if it is
235 Due to trouble on Cygwin / Windows, this is actually probing if it is
236 possible to create a file inside of `path`, stat does not produce reliable
236 possible to create a file inside of `path`, stat does not produce reliable
237 results in this case.
237 results in this case.
238 """
238 """
239 try:
239 try:
240 with tempfile.TemporaryFile(dir=path):
240 with tempfile.TemporaryFile(dir=path):
241 pass
241 pass
242 except OSError:
242 except OSError:
243 return False
243 return False
244 return True
244 return True
245
245
246
246
247 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None, config=None):
247 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None, config=None):
248 """
248 """
249 Returns True if given path is a valid repository False otherwise.
249 Returns True if given path is a valid repository False otherwise.
250 If expect_scm param is given also, compare if given scm is the same
250 If expect_scm param is given also, compare if given scm is the same
251 as expected from scm parameter. If explicit_scm is given don't try to
251 as expected from scm parameter. If explicit_scm is given don't try to
252 detect the scm, just use the given one to check if repo is valid
252 detect the scm, just use the given one to check if repo is valid
253
253
254 :param repo_name:
254 :param repo_name:
255 :param base_path:
255 :param base_path:
256 :param expect_scm:
256 :param expect_scm:
257 :param explicit_scm:
257 :param explicit_scm:
258 :param config:
258 :param config:
259
259
260 :return True: if given path is a valid repository
260 :return True: if given path is a valid repository
261 """
261 """
262 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
262 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
263 log.debug('Checking if `%s` is a valid path for repository. '
263 log.debug('Checking if `%s` is a valid path for repository. '
264 'Explicit type: %s', repo_name, explicit_scm)
264 'Explicit type: %s', repo_name, explicit_scm)
265
265
266 try:
266 try:
267 if explicit_scm:
267 if explicit_scm:
268 detected_scms = [get_scm_backend(explicit_scm)(
268 detected_scms = [get_scm_backend(explicit_scm)(
269 full_path, config=config).alias]
269 full_path, config=config).alias]
270 else:
270 else:
271 detected_scms = get_scm(full_path)
271 detected_scms = get_scm(full_path)
272
272
273 if expect_scm:
273 if expect_scm:
274 return detected_scms[0] == expect_scm
274 return detected_scms[0] == expect_scm
275 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
275 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
276 return True
276 return True
277 except VCSError:
277 except VCSError:
278 log.debug('path: %s is not a valid repo !', full_path)
278 log.debug('path: %s is not a valid repo !', full_path)
279 return False
279 return False
280
280
281
281
282 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
282 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
283 """
283 """
284 Returns True if given path is a repository group, False otherwise
284 Returns True if given path is a repository group, False otherwise
285
285
286 :param repo_name:
286 :param repo_name:
287 :param base_path:
287 :param base_path:
288 """
288 """
289 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
289 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
290 log.debug('Checking if `%s` is a valid path for repository group',
290 log.debug('Checking if `%s` is a valid path for repository group',
291 repo_group_name)
291 repo_group_name)
292
292
293 # check if it's not a repo
293 # check if it's not a repo
294 if is_valid_repo(repo_group_name, base_path):
294 if is_valid_repo(repo_group_name, base_path):
295 log.debug('Repo called %s exist, it is not a valid repo group', repo_group_name)
295 log.debug('Repo called %s exist, it is not a valid repo group', repo_group_name)
296 return False
296 return False
297
297
298 try:
298 try:
299 # we need to check bare git repos at higher level
299 # we need to check bare git repos at higher level
300 # since we might match branches/hooks/info/objects or possible
300 # since we might match branches/hooks/info/objects or possible
301 # other things inside bare git repo
301 # other things inside bare git repo
302 maybe_repo = os.path.dirname(full_path)
302 maybe_repo = os.path.dirname(full_path)
303 if maybe_repo == base_path:
303 if maybe_repo == base_path:
304 # skip root level repo check, we know root location CANNOT BE a repo group
304 # skip root level repo check, we know root location CANNOT BE a repo group
305 return False
305 return False
306
306
307 scm_ = get_scm(maybe_repo)
307 scm_ = get_scm(maybe_repo)
308 log.debug('path: %s is a vcs object:%s, not valid repo group', full_path, scm_)
308 log.debug('path: %s is a vcs object:%s, not valid repo group', full_path, scm_)
309 return False
309 return False
310 except VCSError:
310 except VCSError:
311 pass
311 pass
312
312
313 # check if it's a valid path
313 # check if it's a valid path
314 if skip_path_check or os.path.isdir(full_path):
314 if skip_path_check or os.path.isdir(full_path):
315 log.debug('path: %s is a valid repo group !', full_path)
315 log.debug('path: %s is a valid repo group !', full_path)
316 return True
316 return True
317
317
318 log.debug('path: %s is not a valid repo group !', full_path)
318 log.debug('path: %s is not a valid repo group !', full_path)
319 return False
319 return False
320
320
321
321
322 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
322 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
323 while True:
323 while True:
324 ok = raw_input(prompt)
324 ok = raw_input(prompt)
325 if ok.lower() in ('y', 'ye', 'yes'):
325 if ok.lower() in ('y', 'ye', 'yes'):
326 return True
326 return True
327 if ok.lower() in ('n', 'no', 'nop', 'nope'):
327 if ok.lower() in ('n', 'no', 'nop', 'nope'):
328 return False
328 return False
329 retries = retries - 1
329 retries = retries - 1
330 if retries < 0:
330 if retries < 0:
331 raise IOError
331 raise IOError
332 print(complaint)
332 print(complaint)
333
333
334 # propagated from mercurial documentation
334 # propagated from mercurial documentation
335 ui_sections = [
335 ui_sections = [
336 'alias', 'auth',
336 'alias', 'auth',
337 'decode/encode', 'defaults',
337 'decode/encode', 'defaults',
338 'diff', 'email',
338 'diff', 'email',
339 'extensions', 'format',
339 'extensions', 'format',
340 'merge-patterns', 'merge-tools',
340 'merge-patterns', 'merge-tools',
341 'hooks', 'http_proxy',
341 'hooks', 'http_proxy',
342 'smtp', 'patch',
342 'smtp', 'patch',
343 'paths', 'profiling',
343 'paths', 'profiling',
344 'server', 'trusted',
344 'server', 'trusted',
345 'ui', 'web', ]
345 'ui', 'web', ]
346
346
347
347
348 def config_data_from_db(clear_session=True, repo=None):
348 def config_data_from_db(clear_session=True, repo=None):
349 """
349 """
350 Read the configuration data from the database and return configuration
350 Read the configuration data from the database and return configuration
351 tuples.
351 tuples.
352 """
352 """
353 from rhodecode.model.settings import VcsSettingsModel
353 from rhodecode.model.settings import VcsSettingsModel
354
354
355 config = []
355 config = []
356
356
357 sa = meta.Session()
357 sa = meta.Session()
358 settings_model = VcsSettingsModel(repo=repo, sa=sa)
358 settings_model = VcsSettingsModel(repo=repo, sa=sa)
359
359
360 ui_settings = settings_model.get_ui_settings()
360 ui_settings = settings_model.get_ui_settings()
361
361
362 ui_data = []
362 ui_data = []
363 for setting in ui_settings:
363 for setting in ui_settings:
364 if setting.active:
364 if setting.active:
365 ui_data.append((setting.section, setting.key, setting.value))
365 ui_data.append((setting.section, setting.key, setting.value))
366 config.append((
366 config.append((
367 safe_str(setting.section), safe_str(setting.key),
367 safe_str(setting.section), safe_str(setting.key),
368 safe_str(setting.value)))
368 safe_str(setting.value)))
369 if setting.key == 'push_ssl':
369 if setting.key == 'push_ssl':
370 # force set push_ssl requirement to False, rhodecode
370 # force set push_ssl requirement to False, rhodecode
371 # handles that
371 # handles that
372 config.append((
372 config.append((
373 safe_str(setting.section), safe_str(setting.key), False))
373 safe_str(setting.section), safe_str(setting.key), False))
374 log.debug(
374 log.debug(
375 'settings ui from db@repo[%s]: %s',
375 'settings ui from db@repo[%s]: %s',
376 repo,
376 repo,
377 ','.join(map(lambda s: '[{}] {}={}'.format(*s), ui_data)))
377 ','.join(map(lambda s: '[{}] {}={}'.format(*s), ui_data)))
378 if clear_session:
378 if clear_session:
379 meta.Session.remove()
379 meta.Session.remove()
380
380
381 # TODO: mikhail: probably it makes no sense to re-read hooks information.
381 # TODO: mikhail: probably it makes no sense to re-read hooks information.
382 # It's already there and activated/deactivated
382 # It's already there and activated/deactivated
383 skip_entries = []
383 skip_entries = []
384 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
384 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
385 if 'pull' not in enabled_hook_classes:
385 if 'pull' not in enabled_hook_classes:
386 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
386 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
387 if 'push' not in enabled_hook_classes:
387 if 'push' not in enabled_hook_classes:
388 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
388 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
389 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
389 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
390 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PUSH_KEY))
390 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PUSH_KEY))
391
391
392 config = [entry for entry in config if entry[:2] not in skip_entries]
392 config = [entry for entry in config if entry[:2] not in skip_entries]
393
393
394 return config
394 return config
395
395
396
396
397 def make_db_config(clear_session=True, repo=None):
397 def make_db_config(clear_session=True, repo=None):
398 """
398 """
399 Create a :class:`Config` instance based on the values in the database.
399 Create a :class:`Config` instance based on the values in the database.
400 """
400 """
401 config = Config()
401 config = Config()
402 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
402 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
403 for section, option, value in config_data:
403 for section, option, value in config_data:
404 config.set(section, option, value)
404 config.set(section, option, value)
405 return config
405 return config
406
406
407
407
408 def get_enabled_hook_classes(ui_settings):
408 def get_enabled_hook_classes(ui_settings):
409 """
409 """
410 Return the enabled hook classes.
410 Return the enabled hook classes.
411
411
412 :param ui_settings: List of ui_settings as returned
412 :param ui_settings: List of ui_settings as returned
413 by :meth:`VcsSettingsModel.get_ui_settings`
413 by :meth:`VcsSettingsModel.get_ui_settings`
414
414
415 :return: a list with the enabled hook classes. The order is not guaranteed.
415 :return: a list with the enabled hook classes. The order is not guaranteed.
416 :rtype: list
416 :rtype: list
417 """
417 """
418 enabled_hooks = []
418 enabled_hooks = []
419 active_hook_keys = [
419 active_hook_keys = [
420 key for section, key, value, active in ui_settings
420 key for section, key, value, active in ui_settings
421 if section == 'hooks' and active]
421 if section == 'hooks' and active]
422
422
423 hook_names = {
423 hook_names = {
424 RhodeCodeUi.HOOK_PUSH: 'push',
424 RhodeCodeUi.HOOK_PUSH: 'push',
425 RhodeCodeUi.HOOK_PULL: 'pull',
425 RhodeCodeUi.HOOK_PULL: 'pull',
426 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
426 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
427 }
427 }
428
428
429 for key in active_hook_keys:
429 for key in active_hook_keys:
430 hook = hook_names.get(key)
430 hook = hook_names.get(key)
431 if hook:
431 if hook:
432 enabled_hooks.append(hook)
432 enabled_hooks.append(hook)
433
433
434 return enabled_hooks
434 return enabled_hooks
435
435
436
436
437 def set_rhodecode_config(config):
437 def set_rhodecode_config(config):
438 """
438 """
439 Updates pyramid config with new settings from database
439 Updates pyramid config with new settings from database
440
440
441 :param config:
441 :param config:
442 """
442 """
443 from rhodecode.model.settings import SettingsModel
443 from rhodecode.model.settings import SettingsModel
444 app_settings = SettingsModel().get_all_settings()
444 app_settings = SettingsModel().get_all_settings()
445
445
446 for k, v in app_settings.items():
446 for k, v in app_settings.items():
447 config[k] = v
447 config[k] = v
448
448
449
449
450 def get_rhodecode_realm():
450 def get_rhodecode_realm():
451 """
451 """
452 Return the rhodecode realm from database.
452 Return the rhodecode realm from database.
453 """
453 """
454 from rhodecode.model.settings import SettingsModel
454 from rhodecode.model.settings import SettingsModel
455 realm = SettingsModel().get_setting_by_name('realm')
455 realm = SettingsModel().get_setting_by_name('realm')
456 return safe_str(realm.app_settings_value)
456 return safe_str(realm.app_settings_value)
457
457
458
458
459 def get_rhodecode_base_path():
459 def get_rhodecode_base_path():
460 """
460 """
461 Returns the base path. The base path is the filesystem path which points
461 Returns the base path. The base path is the filesystem path which points
462 to the repository store.
462 to the repository store.
463 """
463 """
464 from rhodecode.model.settings import SettingsModel
464 from rhodecode.model.settings import SettingsModel
465 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
465 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
466 return safe_str(paths_ui.ui_value)
466 return safe_str(paths_ui.ui_value)
467
467
468
468
469 def map_groups(path):
469 def map_groups(path):
470 """
470 """
471 Given a full path to a repository, create all nested groups that this
471 Given a full path to a repository, create all nested groups that this
472 repo is inside. This function creates parent-child relationships between
472 repo is inside. This function creates parent-child relationships between
473 groups and creates default perms for all new groups.
473 groups and creates default perms for all new groups.
474
474
475 :param paths: full path to repository
475 :param paths: full path to repository
476 """
476 """
477 from rhodecode.model.repo_group import RepoGroupModel
477 from rhodecode.model.repo_group import RepoGroupModel
478 sa = meta.Session()
478 sa = meta.Session()
479 groups = path.split(Repository.NAME_SEP)
479 groups = path.split(Repository.NAME_SEP)
480 parent = None
480 parent = None
481 group = None
481 group = None
482
482
483 # last element is repo in nested groups structure
483 # last element is repo in nested groups structure
484 groups = groups[:-1]
484 groups = groups[:-1]
485 rgm = RepoGroupModel(sa)
485 rgm = RepoGroupModel(sa)
486 owner = User.get_first_super_admin()
486 owner = User.get_first_super_admin()
487 for lvl, group_name in enumerate(groups):
487 for lvl, group_name in enumerate(groups):
488 group_name = '/'.join(groups[:lvl] + [group_name])
488 group_name = '/'.join(groups[:lvl] + [group_name])
489 group = RepoGroup.get_by_group_name(group_name)
489 group = RepoGroup.get_by_group_name(group_name)
490 desc = '%s group' % group_name
490 desc = '%s group' % group_name
491
491
492 # skip folders that are now removed repos
492 # skip folders that are now removed repos
493 if REMOVED_REPO_PAT.match(group_name):
493 if REMOVED_REPO_PAT.match(group_name):
494 break
494 break
495
495
496 if group is None:
496 if group is None:
497 log.debug('creating group level: %s group_name: %s',
497 log.debug('creating group level: %s group_name: %s',
498 lvl, group_name)
498 lvl, group_name)
499 group = RepoGroup(group_name, parent)
499 group = RepoGroup(group_name, parent)
500 group.group_description = desc
500 group.group_description = desc
501 group.user = owner
501 group.user = owner
502 sa.add(group)
502 sa.add(group)
503 perm_obj = rgm._create_default_perms(group)
503 perm_obj = rgm._create_default_perms(group)
504 sa.add(perm_obj)
504 sa.add(perm_obj)
505 sa.flush()
505 sa.flush()
506
506
507 parent = group
507 parent = group
508 return group
508 return group
509
509
510
510
511 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
511 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
512 """
512 """
513 maps all repos given in initial_repo_list, non existing repositories
513 maps all repos given in initial_repo_list, non existing repositories
514 are created, if remove_obsolete is True it also checks for db entries
514 are created, if remove_obsolete is True it also checks for db entries
515 that are not in initial_repo_list and removes them.
515 that are not in initial_repo_list and removes them.
516
516
517 :param initial_repo_list: list of repositories found by scanning methods
517 :param initial_repo_list: list of repositories found by scanning methods
518 :param remove_obsolete: check for obsolete entries in database
518 :param remove_obsolete: check for obsolete entries in database
519 """
519 """
520 from rhodecode.model.repo import RepoModel
520 from rhodecode.model.repo import RepoModel
521 from rhodecode.model.repo_group import RepoGroupModel
521 from rhodecode.model.repo_group import RepoGroupModel
522 from rhodecode.model.settings import SettingsModel
522 from rhodecode.model.settings import SettingsModel
523
523
524 sa = meta.Session()
524 sa = meta.Session()
525 repo_model = RepoModel()
525 repo_model = RepoModel()
526 user = User.get_first_super_admin()
526 user = User.get_first_super_admin()
527 added = []
527 added = []
528
528
529 # creation defaults
529 # creation defaults
530 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
530 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
531 enable_statistics = defs.get('repo_enable_statistics')
531 enable_statistics = defs.get('repo_enable_statistics')
532 enable_locking = defs.get('repo_enable_locking')
532 enable_locking = defs.get('repo_enable_locking')
533 enable_downloads = defs.get('repo_enable_downloads')
533 enable_downloads = defs.get('repo_enable_downloads')
534 private = defs.get('repo_private')
534 private = defs.get('repo_private')
535
535
536 for name, repo in initial_repo_list.items():
536 for name, repo in initial_repo_list.items():
537 group = map_groups(name)
537 group = map_groups(name)
538 unicode_name = safe_unicode(name)
538 unicode_name = safe_unicode(name)
539 db_repo = repo_model.get_by_repo_name(unicode_name)
539 db_repo = repo_model.get_by_repo_name(unicode_name)
540 # found repo that is on filesystem not in RhodeCode database
540 # found repo that is on filesystem not in RhodeCode database
541 if not db_repo:
541 if not db_repo:
542 log.info('repository %s not found, creating now', name)
542 log.info('repository %s not found, creating now', name)
543 added.append(name)
543 added.append(name)
544 desc = (repo.description
544 desc = (repo.description
545 if repo.description != 'unknown'
545 if repo.description != 'unknown'
546 else '%s repository' % name)
546 else '%s repository' % name)
547
547
548 db_repo = repo_model._create_repo(
548 db_repo = repo_model._create_repo(
549 repo_name=name,
549 repo_name=name,
550 repo_type=repo.alias,
550 repo_type=repo.alias,
551 description=desc,
551 description=desc,
552 repo_group=getattr(group, 'group_id', None),
552 repo_group=getattr(group, 'group_id', None),
553 owner=user,
553 owner=user,
554 enable_locking=enable_locking,
554 enable_locking=enable_locking,
555 enable_downloads=enable_downloads,
555 enable_downloads=enable_downloads,
556 enable_statistics=enable_statistics,
556 enable_statistics=enable_statistics,
557 private=private,
557 private=private,
558 state=Repository.STATE_CREATED
558 state=Repository.STATE_CREATED
559 )
559 )
560 sa.commit()
560 sa.commit()
561 # we added that repo just now, and make sure we updated server info
561 # we added that repo just now, and make sure we updated server info
562 if db_repo.repo_type == 'git':
562 if db_repo.repo_type == 'git':
563 git_repo = db_repo.scm_instance()
563 git_repo = db_repo.scm_instance()
564 # update repository server-info
564 # update repository server-info
565 log.debug('Running update server info')
565 log.debug('Running update server info')
566 git_repo._update_server_info()
566 git_repo._update_server_info()
567
567
568 db_repo.update_commit_cache()
568 db_repo.update_commit_cache()
569
569
570 config = db_repo._config
570 config = db_repo._config
571 config.set('extensions', 'largefiles', '')
571 config.set('extensions', 'largefiles', '')
572 repo = db_repo.scm_instance(config=config)
572 repo = db_repo.scm_instance(config=config)
573 repo.install_hooks()
573 repo.install_hooks()
574
574
575 removed = []
575 removed = []
576 if remove_obsolete:
576 if remove_obsolete:
577 # remove from database those repositories that are not in the filesystem
577 # remove from database those repositories that are not in the filesystem
578 for repo in sa.query(Repository).all():
578 for repo in sa.query(Repository).all():
579 if repo.repo_name not in initial_repo_list.keys():
579 if repo.repo_name not in initial_repo_list.keys():
580 log.debug("Removing non-existing repository found in db `%s`",
580 log.debug("Removing non-existing repository found in db `%s`",
581 repo.repo_name)
581 repo.repo_name)
582 try:
582 try:
583 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
583 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
584 sa.commit()
584 sa.commit()
585 removed.append(repo.repo_name)
585 removed.append(repo.repo_name)
586 except Exception:
586 except Exception:
587 # don't hold further removals on error
587 # don't hold further removals on error
588 log.error(traceback.format_exc())
588 log.error(traceback.format_exc())
589 sa.rollback()
589 sa.rollback()
590
590
591 def splitter(full_repo_name):
591 def splitter(full_repo_name):
592 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
592 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
593 gr_name = None
593 gr_name = None
594 if len(_parts) == 2:
594 if len(_parts) == 2:
595 gr_name = _parts[0]
595 gr_name = _parts[0]
596 return gr_name
596 return gr_name
597
597
598 initial_repo_group_list = [splitter(x) for x in
598 initial_repo_group_list = [splitter(x) for x in
599 initial_repo_list.keys() if splitter(x)]
599 initial_repo_list.keys() if splitter(x)]
600
600
601 # remove from database those repository groups that are not in the
601 # remove from database those repository groups that are not in the
602 # filesystem due to parent child relationships we need to delete them
602 # filesystem due to parent child relationships we need to delete them
603 # in a specific order of most nested first
603 # in a specific order of most nested first
604 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
604 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
605 nested_sort = lambda gr: len(gr.split('/'))
605 nested_sort = lambda gr: len(gr.split('/'))
606 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
606 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
607 if group_name not in initial_repo_group_list:
607 if group_name not in initial_repo_group_list:
608 repo_group = RepoGroup.get_by_group_name(group_name)
608 repo_group = RepoGroup.get_by_group_name(group_name)
609 if (repo_group.children.all() or
609 if (repo_group.children.all() or
610 not RepoGroupModel().check_exist_filesystem(
610 not RepoGroupModel().check_exist_filesystem(
611 group_name=group_name, exc_on_failure=False)):
611 group_name=group_name, exc_on_failure=False)):
612 continue
612 continue
613
613
614 log.info(
614 log.info(
615 'Removing non-existing repository group found in db `%s`',
615 'Removing non-existing repository group found in db `%s`',
616 group_name)
616 group_name)
617 try:
617 try:
618 RepoGroupModel(sa).delete(group_name, fs_remove=False)
618 RepoGroupModel(sa).delete(group_name, fs_remove=False)
619 sa.commit()
619 sa.commit()
620 removed.append(group_name)
620 removed.append(group_name)
621 except Exception:
621 except Exception:
622 # don't hold further removals on error
622 # don't hold further removals on error
623 log.exception(
623 log.exception(
624 'Unable to remove repository group `%s`',
624 'Unable to remove repository group `%s`',
625 group_name)
625 group_name)
626 sa.rollback()
626 sa.rollback()
627 raise
627 raise
628
628
629 return added, removed
629 return added, removed
630
630
631
631
632 def load_rcextensions(root_path):
632 def load_rcextensions(root_path):
633 import rhodecode
633 import rhodecode
634 from rhodecode.config import conf
634 from rhodecode.config import conf
635
635
636 path = os.path.join(root_path)
636 path = os.path.join(root_path)
637 sys.path.append(path)
637 sys.path.append(path)
638
638
639 try:
639 try:
640 rcextensions = __import__('rcextensions')
640 rcextensions = __import__('rcextensions')
641 except ImportError:
641 except ImportError:
642 if os.path.isdir(os.path.join(path, 'rcextensions')):
642 if os.path.isdir(os.path.join(path, 'rcextensions')):
643 log.warn('Unable to load rcextensions from %s', path)
643 log.warn('Unable to load rcextensions from %s', path)
644 rcextensions = None
644 rcextensions = None
645
645
646 if rcextensions:
646 if rcextensions:
647 log.info('Loaded rcextensions from %s...', rcextensions)
647 log.info('Loaded rcextensions from %s...', rcextensions)
648 rhodecode.EXTENSIONS = rcextensions
648 rhodecode.EXTENSIONS = rcextensions
649
649
650 # Additional mappings that are not present in the pygments lexers
650 # Additional mappings that are not present in the pygments lexers
651 conf.LANGUAGES_EXTENSIONS_MAP.update(
651 conf.LANGUAGES_EXTENSIONS_MAP.update(
652 getattr(rhodecode.EXTENSIONS, 'EXTRA_MAPPINGS', {}))
652 getattr(rhodecode.EXTENSIONS, 'EXTRA_MAPPINGS', {}))
653
653
654
654
655 def get_custom_lexer(extension):
655 def get_custom_lexer(extension):
656 """
656 """
657 returns a custom lexer if it is defined in rcextensions module, or None
657 returns a custom lexer if it is defined in rcextensions module, or None
658 if there's no custom lexer defined
658 if there's no custom lexer defined
659 """
659 """
660 import rhodecode
660 import rhodecode
661 from pygments import lexers
661 from pygments import lexers
662
662
663 # custom override made by RhodeCode
663 # custom override made by RhodeCode
664 if extension in ['mako']:
664 if extension in ['mako']:
665 return lexers.get_lexer_by_name('html+mako')
665 return lexers.get_lexer_by_name('html+mako')
666
666
667 # check if we didn't define this extension as other lexer
667 # check if we didn't define this extension as other lexer
668 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
668 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
669 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
669 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
670 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
670 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
671 return lexers.get_lexer_by_name(_lexer_name)
671 return lexers.get_lexer_by_name(_lexer_name)
672
672
673
673
674 #==============================================================================
674 #==============================================================================
675 # TEST FUNCTIONS AND CREATORS
675 # TEST FUNCTIONS AND CREATORS
676 #==============================================================================
676 #==============================================================================
677 def create_test_index(repo_location, config):
677 def create_test_index(repo_location, config):
678 """
678 """
679 Makes default test index.
679 Makes default test index.
680 """
680 """
681 import rc_testdata
681 import rc_testdata
682
682
683 rc_testdata.extract_search_index(
683 rc_testdata.extract_search_index(
684 'vcs_search_index', os.path.dirname(config['search.location']))
684 'vcs_search_index', os.path.dirname(config['search.location']))
685
685
686
686
687 def create_test_directory(test_path):
687 def create_test_directory(test_path):
688 """
688 """
689 Create test directory if it doesn't exist.
689 Create test directory if it doesn't exist.
690 """
690 """
691 if not os.path.isdir(test_path):
691 if not os.path.isdir(test_path):
692 log.debug('Creating testdir %s', test_path)
692 log.debug('Creating testdir %s', test_path)
693 os.makedirs(test_path)
693 os.makedirs(test_path)
694
694
695
695
696 def create_test_database(test_path, config):
696 def create_test_database(test_path, config):
697 """
697 """
698 Makes a fresh database.
698 Makes a fresh database.
699 """
699 """
700 from rhodecode.lib.db_manage import DbManage
700 from rhodecode.lib.db_manage import DbManage
701
701
702 # PART ONE create db
702 # PART ONE create db
703 dbconf = config['sqlalchemy.db1.url']
703 dbconf = config['sqlalchemy.db1.url']
704 log.debug('making test db %s', dbconf)
704 log.debug('making test db %s', dbconf)
705
705
706 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
706 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
707 tests=True, cli_args={'force_ask': True})
707 tests=True, cli_args={'force_ask': True})
708 dbmanage.create_tables(override=True)
708 dbmanage.create_tables(override=True)
709 dbmanage.set_db_version()
709 dbmanage.set_db_version()
710 # for tests dynamically set new root paths based on generated content
710 # for tests dynamically set new root paths based on generated content
711 dbmanage.create_settings(dbmanage.config_prompt(test_path))
711 dbmanage.create_settings(dbmanage.config_prompt(test_path))
712 dbmanage.create_default_user()
712 dbmanage.create_default_user()
713 dbmanage.create_test_admin_and_users()
713 dbmanage.create_test_admin_and_users()
714 dbmanage.create_permissions()
714 dbmanage.create_permissions()
715 dbmanage.populate_default_permissions()
715 dbmanage.populate_default_permissions()
716 Session().commit()
716 Session().commit()
717
717
718
718
719 def create_test_repositories(test_path, config):
719 def create_test_repositories(test_path, config):
720 """
720 """
721 Creates test repositories in the temporary directory. Repositories are
721 Creates test repositories in the temporary directory. Repositories are
722 extracted from archives within the rc_testdata package.
722 extracted from archives within the rc_testdata package.
723 """
723 """
724 import rc_testdata
724 import rc_testdata
725 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
725 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
726
726
727 log.debug('making test vcs repositories')
727 log.debug('making test vcs repositories')
728
728
729 idx_path = config['search.location']
729 idx_path = config['search.location']
730 data_path = config['cache_dir']
730 data_path = config['cache_dir']
731
731
732 # clean index and data
732 # clean index and data
733 if idx_path and os.path.exists(idx_path):
733 if idx_path and os.path.exists(idx_path):
734 log.debug('remove %s', idx_path)
734 log.debug('remove %s', idx_path)
735 shutil.rmtree(idx_path)
735 shutil.rmtree(idx_path)
736
736
737 if data_path and os.path.exists(data_path):
737 if data_path and os.path.exists(data_path):
738 log.debug('remove %s', data_path)
738 log.debug('remove %s', data_path)
739 shutil.rmtree(data_path)
739 shutil.rmtree(data_path)
740
740
741 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
741 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
742 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
742 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
743
743
744 # Note: Subversion is in the process of being integrated with the system,
744 # Note: Subversion is in the process of being integrated with the system,
745 # until we have a properly packed version of the test svn repository, this
745 # until we have a properly packed version of the test svn repository, this
746 # tries to copy over the repo from a package "rc_testdata"
746 # tries to copy over the repo from a package "rc_testdata"
747 svn_repo_path = rc_testdata.get_svn_repo_archive()
747 svn_repo_path = rc_testdata.get_svn_repo_archive()
748 with tarfile.open(svn_repo_path) as tar:
748 with tarfile.open(svn_repo_path) as tar:
749 tar.extractall(jn(test_path, SVN_REPO))
749 tar.extractall(jn(test_path, SVN_REPO))
750
750
751
751
752 def password_changed(auth_user, session):
752 def password_changed(auth_user, session):
753 # Never report password change in case of default user or anonymous user.
753 # Never report password change in case of default user or anonymous user.
754 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
754 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
755 return False
755 return False
756
756
757 password_hash = md5(auth_user.password) if auth_user.password else None
757 password_hash = md5(auth_user.password) if auth_user.password else None
758 rhodecode_user = session.get('rhodecode_user', {})
758 rhodecode_user = session.get('rhodecode_user', {})
759 session_password_hash = rhodecode_user.get('password', '')
759 session_password_hash = rhodecode_user.get('password', '')
760 return password_hash != session_password_hash
760 return password_hash != session_password_hash
761
761
762
762
763 def read_opensource_licenses():
763 def read_opensource_licenses():
764 global _license_cache
764 global _license_cache
765
765
766 if not _license_cache:
766 if not _license_cache:
767 licenses = pkg_resources.resource_string(
767 licenses = pkg_resources.resource_string(
768 'rhodecode', 'config/licenses.json')
768 'rhodecode', 'config/licenses.json')
769 _license_cache = json.loads(licenses)
769 _license_cache = json.loads(licenses)
770
770
771 return _license_cache
771 return _license_cache
772
772
773
773
774 def generate_platform_uuid():
774 def generate_platform_uuid():
775 """
775 """
776 Generates platform UUID based on it's name
776 Generates platform UUID based on it's name
777 """
777 """
778 import platform
778 import platform
779
779
780 try:
780 try:
781 uuid_list = [platform.platform()]
781 uuid_list = [platform.platform()]
782 return hashlib.sha256(':'.join(uuid_list)).hexdigest()
782 return hashlib.sha256(':'.join(uuid_list)).hexdigest()
783 except Exception as e:
783 except Exception as e:
784 log.error('Failed to generate host uuid: %s', e)
784 log.error('Failed to generate host uuid: %s', e)
785 return 'UNDEFINED'
785 return 'UNDEFINED'
786
786
787
787
788 def send_test_email(recipients, email_body='TEST EMAIL'):
788 def send_test_email(recipients, email_body='TEST EMAIL'):
789 """
789 """
790 Simple code for generating test emails.
790 Simple code for generating test emails.
791 Usage::
791 Usage::
792
792
793 from rhodecode.lib import utils
793 from rhodecode.lib import utils
794 utils.send_test_email()
794 utils.send_test_email()
795 """
795 """
796 from rhodecode.lib.celerylib import tasks, run_task
796 from rhodecode.lib.celerylib import tasks, run_task
797
797
798 email_body = email_body_plaintext = email_body
798 email_body = email_body_plaintext = email_body
799 subject = 'SUBJECT FROM: {}'.format(socket.gethostname())
799 subject = 'SUBJECT FROM: {}'.format(socket.gethostname())
800 tasks.send_email(recipients, subject, email_body_plaintext, email_body)
800 tasks.send_email(recipients, subject, email_body_plaintext, email_body)
General Comments 0
You need to be logged in to leave comments. Login now