##// END OF EJS Templates
shadow: Use only a single regular expression to generate and match repo/group slugs.
Martin Bornhold -
r901:8555dd6b default
parent child Browse files
Show More
@@ -1,506 +1,505 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2016 RhodeCode GmbH
3 # Copyright (C) 2014-2016 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 SimpleVCS middleware for handling protocol request (push/clone etc.)
22 SimpleVCS middleware for handling protocol request (push/clone etc.)
23 It's implemented with basic auth function
23 It's implemented with basic auth function
24 """
24 """
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import importlib
28 import importlib
29 import re
29 import re
30 from functools import wraps
30 from functools import wraps
31
31
32 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
32 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
33 from webob.exc import (
33 from webob.exc import (
34 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
34 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
35
35
36 import rhodecode
36 import rhodecode
37 from rhodecode.authentication.base import authenticate, VCS_TYPE
37 from rhodecode.authentication.base import authenticate, VCS_TYPE
38 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
38 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
39 from rhodecode.lib.base import BasicAuth, get_ip_addr, vcs_operation_context
39 from rhodecode.lib.base import BasicAuth, get_ip_addr, vcs_operation_context
40 from rhodecode.lib.exceptions import (
40 from rhodecode.lib.exceptions import (
41 HTTPLockedRC, HTTPRequirementError, UserCreationError,
41 HTTPLockedRC, HTTPRequirementError, UserCreationError,
42 NotAllowedToCreateUserError)
42 NotAllowedToCreateUserError)
43 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
43 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
44 from rhodecode.lib.middleware import appenlight
44 from rhodecode.lib.middleware import appenlight
45 from rhodecode.lib.middleware.utils import scm_app
45 from rhodecode.lib.middleware.utils import scm_app
46 from rhodecode.lib.utils import (
46 from rhodecode.lib.utils import (
47 is_valid_repo, get_rhodecode_realm, get_rhodecode_base_path)
47 is_valid_repo, get_rhodecode_realm, get_rhodecode_base_path)
48 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool
48 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool
49 from rhodecode.lib.vcs.conf import settings as vcs_settings
49 from rhodecode.lib.vcs.conf import settings as vcs_settings
50 from rhodecode.lib.vcs.backends import base
50 from rhodecode.lib.vcs.backends import base
51 from rhodecode.model import meta
51 from rhodecode.model import meta
52 from rhodecode.model.db import User, Repository, PullRequest
52 from rhodecode.model.db import User, Repository, PullRequest
53 from rhodecode.model.scm import ScmModel
53 from rhodecode.model.scm import ScmModel
54 from rhodecode.model.pull_request import PullRequestModel
54 from rhodecode.model.pull_request import PullRequestModel
55
55
56
56
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59
59
60 def initialize_generator(factory):
60 def initialize_generator(factory):
61 """
61 """
62 Initializes the returned generator by draining its first element.
62 Initializes the returned generator by draining its first element.
63
63
64 This can be used to give a generator an initializer, which is the code
64 This can be used to give a generator an initializer, which is the code
65 up to the first yield statement. This decorator enforces that the first
65 up to the first yield statement. This decorator enforces that the first
66 produced element has the value ``"__init__"`` to make its special
66 produced element has the value ``"__init__"`` to make its special
67 purpose very explicit in the using code.
67 purpose very explicit in the using code.
68 """
68 """
69
69
70 @wraps(factory)
70 @wraps(factory)
71 def wrapper(*args, **kwargs):
71 def wrapper(*args, **kwargs):
72 gen = factory(*args, **kwargs)
72 gen = factory(*args, **kwargs)
73 try:
73 try:
74 init = gen.next()
74 init = gen.next()
75 except StopIteration:
75 except StopIteration:
76 raise ValueError('Generator must yield at least one element.')
76 raise ValueError('Generator must yield at least one element.')
77 if init != "__init__":
77 if init != "__init__":
78 raise ValueError('First yielded element must be "__init__".')
78 raise ValueError('First yielded element must be "__init__".')
79 return gen
79 return gen
80 return wrapper
80 return wrapper
81
81
82
82
83 class SimpleVCS(object):
83 class SimpleVCS(object):
84 """Common functionality for SCM HTTP handlers."""
84 """Common functionality for SCM HTTP handlers."""
85
85
86 SCM = 'unknown'
86 SCM = 'unknown'
87
87
88 acl_repo_name = None
88 acl_repo_name = None
89 url_repo_name = None
89 url_repo_name = None
90 vcs_repo_name = None
90 vcs_repo_name = None
91
91
92 def __init__(self, application, config, registry):
92 def __init__(self, application, config, registry):
93 self.registry = registry
93 self.registry = registry
94 self.application = application
94 self.application = application
95 self.config = config
95 self.config = config
96 # re-populated by specialized middleware
96 # re-populated by specialized middleware
97 self.repo_vcs_config = base.Config()
97 self.repo_vcs_config = base.Config()
98
98
99 # base path of repo locations
99 # base path of repo locations
100 self.basepath = get_rhodecode_base_path()
100 self.basepath = get_rhodecode_base_path()
101 # authenticate this VCS request using authfunc
101 # authenticate this VCS request using authfunc
102 auth_ret_code_detection = \
102 auth_ret_code_detection = \
103 str2bool(self.config.get('auth_ret_code_detection', False))
103 str2bool(self.config.get('auth_ret_code_detection', False))
104 self.authenticate = BasicAuth(
104 self.authenticate = BasicAuth(
105 '', authenticate, registry, config.get('auth_ret_code'),
105 '', authenticate, registry, config.get('auth_ret_code'),
106 auth_ret_code_detection)
106 auth_ret_code_detection)
107 self.ip_addr = '0.0.0.0'
107 self.ip_addr = '0.0.0.0'
108
108
109 def set_repo_names(self, environ):
109 def set_repo_names(self, environ):
110 """
110 """
111 This will populate the attributes acl_repo_name, url_repo_name,
111 This will populate the attributes acl_repo_name, url_repo_name,
112 vcs_repo_name and is_shadow_repo on the current instance.
112 vcs_repo_name and is_shadow_repo on the current instance.
113 """
113 """
114 # TODO: martinb: Move to class or module scope.
114 # TODO: martinb: Move to class or module scope.
115 # TODO: martinb: Check if we have to use re.UNICODE.
115 from rhodecode.lib.utils import SLUG_RE
116 # TODO: martinb: Check which chars are allowed for repo/group names.
117 # These chars are excluded: '`?=[]\;\'"<>,/~!@#$%^&*()+{}|: '
118 # Code from: rhodecode/lib/utils.py:repo_name_slug()
119 pr_regex = re.compile(
116 pr_regex = re.compile(
120 '(?P<base_name>(?:[\w-]+)(?:/[\w-]+)*)/' # repo groups
117 '(?P<groups>(?:{slug_pat})(?:/{slug_pat})*)' # repo groups
121 '(?P<repo_name>[\w-]+)' # target repo name
118 '/(?P<target>{slug_pat})' # target repo
122 '/pull-request/(?P<pr_id>\d+)/repository') # pr suffix
119 '/pull-request/(?P<pr_id>\d+)' # pull request
120 '/repository$' # shadow repo
121 .format(slug_pat=SLUG_RE.pattern))
123
122
124 # Get url repo name from environment.
123 # Get url repo name from environment.
125 self.url_repo_name = self._get_repository_name(environ)
124 self.url_repo_name = self._get_repository_name(environ)
126
125
127 # Check if this is a request to a shadow repository. In case of a
126 # Check if this is a request to a shadow repository. In case of a
128 # shadow repo set vcs_repo_name to the file system path pointing to the
127 # shadow repo set vcs_repo_name to the file system path pointing to the
129 # shadow repo. And set acl_repo_name to the pull request target repo
128 # shadow repo. And set acl_repo_name to the pull request target repo
130 # because we use the target repo for permission checks. Otherwise all
129 # because we use the target repo for permission checks. Otherwise all
131 # names are equal.
130 # names are equal.
132 match = pr_regex.match(self.url_repo_name)
131 match = pr_regex.match(self.url_repo_name)
133 if match:
132 if match:
134 # Get pull request instance.
133 # Get pull request instance.
135 match_dict = match.groupdict()
134 match_dict = match.groupdict()
136 pr_id = match_dict['pr_id']
135 pr_id = match_dict['pr_id']
137 pull_request = PullRequest.get(pr_id)
136 pull_request = PullRequest.get(pr_id)
138
137
139 # Get file system path to shadow repository.
138 # Get file system path to shadow repository.
140 workspace_id = PullRequestModel()._workspace_id(pull_request)
139 workspace_id = PullRequestModel()._workspace_id(pull_request)
141 target_vcs = pull_request.target_repo.scm_instance()
140 target_vcs = pull_request.target_repo.scm_instance()
142 vcs_repo_name = target_vcs._get_shadow_repository_path(
141 vcs_repo_name = target_vcs._get_shadow_repository_path(
143 workspace_id)
142 workspace_id)
144
143
145 # Store names for later usage.
144 # Store names for later usage.
146 self.vcs_repo_name = vcs_repo_name
145 self.vcs_repo_name = vcs_repo_name
147 self.acl_repo_name = pull_request.target_repo.repo_name
146 self.acl_repo_name = pull_request.target_repo.repo_name
148 self.is_shadow_repo = True
147 self.is_shadow_repo = True
149 else:
148 else:
150 # All names are equal for normal (non shadow) repositories.
149 # All names are equal for normal (non shadow) repositories.
151 self.acl_repo_name = self.url_repo_name
150 self.acl_repo_name = self.url_repo_name
152 self.vcs_repo_name = self.url_repo_name
151 self.vcs_repo_name = self.url_repo_name
153 self.is_shadow_repo = False
152 self.is_shadow_repo = False
154
153
155 @property
154 @property
156 def scm_app(self):
155 def scm_app(self):
157 custom_implementation = self.config.get('vcs.scm_app_implementation')
156 custom_implementation = self.config.get('vcs.scm_app_implementation')
158 if custom_implementation and custom_implementation != 'pyro4':
157 if custom_implementation and custom_implementation != 'pyro4':
159 log.info(
158 log.info(
160 "Using custom implementation of scm_app: %s",
159 "Using custom implementation of scm_app: %s",
161 custom_implementation)
160 custom_implementation)
162 scm_app_impl = importlib.import_module(custom_implementation)
161 scm_app_impl = importlib.import_module(custom_implementation)
163 else:
162 else:
164 scm_app_impl = scm_app
163 scm_app_impl = scm_app
165 return scm_app_impl
164 return scm_app_impl
166
165
167 def _get_by_id(self, repo_name):
166 def _get_by_id(self, repo_name):
168 """
167 """
169 Gets a special pattern _<ID> from clone url and tries to replace it
168 Gets a special pattern _<ID> from clone url and tries to replace it
170 with a repository_name for support of _<ID> non changeable urls
169 with a repository_name for support of _<ID> non changeable urls
171 """
170 """
172
171
173 data = repo_name.split('/')
172 data = repo_name.split('/')
174 if len(data) >= 2:
173 if len(data) >= 2:
175 from rhodecode.model.repo import RepoModel
174 from rhodecode.model.repo import RepoModel
176 by_id_match = RepoModel().get_repo_by_id(repo_name)
175 by_id_match = RepoModel().get_repo_by_id(repo_name)
177 if by_id_match:
176 if by_id_match:
178 data[1] = by_id_match.repo_name
177 data[1] = by_id_match.repo_name
179
178
180 return safe_str('/'.join(data))
179 return safe_str('/'.join(data))
181
180
182 def _invalidate_cache(self, repo_name):
181 def _invalidate_cache(self, repo_name):
183 """
182 """
184 Set's cache for this repository for invalidation on next access
183 Set's cache for this repository for invalidation on next access
185
184
186 :param repo_name: full repo name, also a cache key
185 :param repo_name: full repo name, also a cache key
187 """
186 """
188 ScmModel().mark_for_invalidation(repo_name)
187 ScmModel().mark_for_invalidation(repo_name)
189
188
190 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
189 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
191 db_repo = Repository.get_by_repo_name(repo_name)
190 db_repo = Repository.get_by_repo_name(repo_name)
192 if not db_repo:
191 if not db_repo:
193 log.debug('Repository `%s` not found inside the database.',
192 log.debug('Repository `%s` not found inside the database.',
194 repo_name)
193 repo_name)
195 return False
194 return False
196
195
197 if db_repo.repo_type != scm_type:
196 if db_repo.repo_type != scm_type:
198 log.warning(
197 log.warning(
199 'Repository `%s` have incorrect scm_type, expected %s got %s',
198 'Repository `%s` have incorrect scm_type, expected %s got %s',
200 repo_name, db_repo.repo_type, scm_type)
199 repo_name, db_repo.repo_type, scm_type)
201 return False
200 return False
202
201
203 return is_valid_repo(repo_name, base_path, expect_scm=scm_type)
202 return is_valid_repo(repo_name, base_path, expect_scm=scm_type)
204
203
205 def valid_and_active_user(self, user):
204 def valid_and_active_user(self, user):
206 """
205 """
207 Checks if that user is not empty, and if it's actually object it checks
206 Checks if that user is not empty, and if it's actually object it checks
208 if he's active.
207 if he's active.
209
208
210 :param user: user object or None
209 :param user: user object or None
211 :return: boolean
210 :return: boolean
212 """
211 """
213 if user is None:
212 if user is None:
214 return False
213 return False
215
214
216 elif user.active:
215 elif user.active:
217 return True
216 return True
218
217
219 return False
218 return False
220
219
221 def _check_permission(self, action, user, repo_name, ip_addr=None):
220 def _check_permission(self, action, user, repo_name, ip_addr=None):
222 """
221 """
223 Checks permissions using action (push/pull) user and repository
222 Checks permissions using action (push/pull) user and repository
224 name
223 name
225
224
226 :param action: push or pull action
225 :param action: push or pull action
227 :param user: user instance
226 :param user: user instance
228 :param repo_name: repository name
227 :param repo_name: repository name
229 """
228 """
230 # check IP
229 # check IP
231 inherit = user.inherit_default_permissions
230 inherit = user.inherit_default_permissions
232 ip_allowed = AuthUser.check_ip_allowed(user.user_id, ip_addr,
231 ip_allowed = AuthUser.check_ip_allowed(user.user_id, ip_addr,
233 inherit_from_default=inherit)
232 inherit_from_default=inherit)
234 if ip_allowed:
233 if ip_allowed:
235 log.info('Access for IP:%s allowed', ip_addr)
234 log.info('Access for IP:%s allowed', ip_addr)
236 else:
235 else:
237 return False
236 return False
238
237
239 if action == 'push':
238 if action == 'push':
240 if not HasPermissionAnyMiddleware('repository.write',
239 if not HasPermissionAnyMiddleware('repository.write',
241 'repository.admin')(user,
240 'repository.admin')(user,
242 repo_name):
241 repo_name):
243 return False
242 return False
244
243
245 else:
244 else:
246 # any other action need at least read permission
245 # any other action need at least read permission
247 if not HasPermissionAnyMiddleware('repository.read',
246 if not HasPermissionAnyMiddleware('repository.read',
248 'repository.write',
247 'repository.write',
249 'repository.admin')(user,
248 'repository.admin')(user,
250 repo_name):
249 repo_name):
251 return False
250 return False
252
251
253 return True
252 return True
254
253
255 def _check_ssl(self, environ, start_response):
254 def _check_ssl(self, environ, start_response):
256 """
255 """
257 Checks the SSL check flag and returns False if SSL is not present
256 Checks the SSL check flag and returns False if SSL is not present
258 and required True otherwise
257 and required True otherwise
259 """
258 """
260 org_proto = environ['wsgi._org_proto']
259 org_proto = environ['wsgi._org_proto']
261 # check if we have SSL required ! if not it's a bad request !
260 # check if we have SSL required ! if not it's a bad request !
262 require_ssl = str2bool(self.repo_vcs_config.get('web', 'push_ssl'))
261 require_ssl = str2bool(self.repo_vcs_config.get('web', 'push_ssl'))
263 if require_ssl and org_proto == 'http':
262 if require_ssl and org_proto == 'http':
264 log.debug('proto is %s and SSL is required BAD REQUEST !',
263 log.debug('proto is %s and SSL is required BAD REQUEST !',
265 org_proto)
264 org_proto)
266 return False
265 return False
267 return True
266 return True
268
267
269 def __call__(self, environ, start_response):
268 def __call__(self, environ, start_response):
270 try:
269 try:
271 return self._handle_request(environ, start_response)
270 return self._handle_request(environ, start_response)
272 except Exception:
271 except Exception:
273 log.exception("Exception while handling request")
272 log.exception("Exception while handling request")
274 appenlight.track_exception(environ)
273 appenlight.track_exception(environ)
275 return HTTPInternalServerError()(environ, start_response)
274 return HTTPInternalServerError()(environ, start_response)
276 finally:
275 finally:
277 meta.Session.remove()
276 meta.Session.remove()
278
277
279 def _handle_request(self, environ, start_response):
278 def _handle_request(self, environ, start_response):
280
279
281 if not self._check_ssl(environ, start_response):
280 if not self._check_ssl(environ, start_response):
282 reason = ('SSL required, while RhodeCode was unable '
281 reason = ('SSL required, while RhodeCode was unable '
283 'to detect this as SSL request')
282 'to detect this as SSL request')
284 log.debug('User not allowed to proceed, %s', reason)
283 log.debug('User not allowed to proceed, %s', reason)
285 return HTTPNotAcceptable(reason)(environ, start_response)
284 return HTTPNotAcceptable(reason)(environ, start_response)
286
285
287 if not self.url_repo_name:
286 if not self.url_repo_name:
288 log.warning('Repository name is empty: %s', self.url_repo_name)
287 log.warning('Repository name is empty: %s', self.url_repo_name)
289 # failed to get repo name, we fail now
288 # failed to get repo name, we fail now
290 return HTTPNotFound()(environ, start_response)
289 return HTTPNotFound()(environ, start_response)
291 log.debug('Extracted repo name is %s', self.url_repo_name)
290 log.debug('Extracted repo name is %s', self.url_repo_name)
292
291
293 ip_addr = get_ip_addr(environ)
292 ip_addr = get_ip_addr(environ)
294 username = None
293 username = None
295
294
296 # skip passing error to error controller
295 # skip passing error to error controller
297 environ['pylons.status_code_redirect'] = True
296 environ['pylons.status_code_redirect'] = True
298
297
299 # ======================================================================
298 # ======================================================================
300 # GET ACTION PULL or PUSH
299 # GET ACTION PULL or PUSH
301 # ======================================================================
300 # ======================================================================
302 action = self._get_action(environ)
301 action = self._get_action(environ)
303
302
304 # ======================================================================
303 # ======================================================================
305 # Check if this is a request to a shadow repository of a pull request.
304 # Check if this is a request to a shadow repository of a pull request.
306 # In this case only pull action is allowed.
305 # In this case only pull action is allowed.
307 # ======================================================================
306 # ======================================================================
308 if self.is_shadow_repo and action != 'pull':
307 if self.is_shadow_repo and action != 'pull':
309 reason = 'Only pull action is allowed for shadow repositories.'
308 reason = 'Only pull action is allowed for shadow repositories.'
310 log.debug('User not allowed to proceed, %s', reason)
309 log.debug('User not allowed to proceed, %s', reason)
311 return HTTPNotAcceptable(reason)(environ, start_response)
310 return HTTPNotAcceptable(reason)(environ, start_response)
312
311
313 # ======================================================================
312 # ======================================================================
314 # CHECK ANONYMOUS PERMISSION
313 # CHECK ANONYMOUS PERMISSION
315 # ======================================================================
314 # ======================================================================
316 if action in ['pull', 'push']:
315 if action in ['pull', 'push']:
317 anonymous_user = User.get_default_user()
316 anonymous_user = User.get_default_user()
318 username = anonymous_user.username
317 username = anonymous_user.username
319 if anonymous_user.active:
318 if anonymous_user.active:
320 # ONLY check permissions if the user is activated
319 # ONLY check permissions if the user is activated
321 anonymous_perm = self._check_permission(
320 anonymous_perm = self._check_permission(
322 action, anonymous_user, self.acl_repo_name, ip_addr)
321 action, anonymous_user, self.acl_repo_name, ip_addr)
323 else:
322 else:
324 anonymous_perm = False
323 anonymous_perm = False
325
324
326 if not anonymous_user.active or not anonymous_perm:
325 if not anonymous_user.active or not anonymous_perm:
327 if not anonymous_user.active:
326 if not anonymous_user.active:
328 log.debug('Anonymous access is disabled, running '
327 log.debug('Anonymous access is disabled, running '
329 'authentication')
328 'authentication')
330
329
331 if not anonymous_perm:
330 if not anonymous_perm:
332 log.debug('Not enough credentials to access this '
331 log.debug('Not enough credentials to access this '
333 'repository as anonymous user')
332 'repository as anonymous user')
334
333
335 username = None
334 username = None
336 # ==============================================================
335 # ==============================================================
337 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
336 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
338 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
337 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
339 # ==============================================================
338 # ==============================================================
340
339
341 # try to auth based on environ, container auth methods
340 # try to auth based on environ, container auth methods
342 log.debug('Running PRE-AUTH for container based authentication')
341 log.debug('Running PRE-AUTH for container based authentication')
343 pre_auth = authenticate(
342 pre_auth = authenticate(
344 '', '', environ, VCS_TYPE, registry=self.registry)
343 '', '', environ, VCS_TYPE, registry=self.registry)
345 if pre_auth and pre_auth.get('username'):
344 if pre_auth and pre_auth.get('username'):
346 username = pre_auth['username']
345 username = pre_auth['username']
347 log.debug('PRE-AUTH got %s as username', username)
346 log.debug('PRE-AUTH got %s as username', username)
348
347
349 # If not authenticated by the container, running basic auth
348 # If not authenticated by the container, running basic auth
350 if not username:
349 if not username:
351 self.authenticate.realm = get_rhodecode_realm()
350 self.authenticate.realm = get_rhodecode_realm()
352
351
353 try:
352 try:
354 result = self.authenticate(environ)
353 result = self.authenticate(environ)
355 except (UserCreationError, NotAllowedToCreateUserError) as e:
354 except (UserCreationError, NotAllowedToCreateUserError) as e:
356 log.error(e)
355 log.error(e)
357 reason = safe_str(e)
356 reason = safe_str(e)
358 return HTTPNotAcceptable(reason)(environ, start_response)
357 return HTTPNotAcceptable(reason)(environ, start_response)
359
358
360 if isinstance(result, str):
359 if isinstance(result, str):
361 AUTH_TYPE.update(environ, 'basic')
360 AUTH_TYPE.update(environ, 'basic')
362 REMOTE_USER.update(environ, result)
361 REMOTE_USER.update(environ, result)
363 username = result
362 username = result
364 else:
363 else:
365 return result.wsgi_application(environ, start_response)
364 return result.wsgi_application(environ, start_response)
366
365
367 # ==============================================================
366 # ==============================================================
368 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
367 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
369 # ==============================================================
368 # ==============================================================
370 user = User.get_by_username(username)
369 user = User.get_by_username(username)
371 if not self.valid_and_active_user(user):
370 if not self.valid_and_active_user(user):
372 return HTTPForbidden()(environ, start_response)
371 return HTTPForbidden()(environ, start_response)
373 username = user.username
372 username = user.username
374 user.update_lastactivity()
373 user.update_lastactivity()
375 meta.Session().commit()
374 meta.Session().commit()
376
375
377 # check user attributes for password change flag
376 # check user attributes for password change flag
378 user_obj = user
377 user_obj = user
379 if user_obj and user_obj.username != User.DEFAULT_USER and \
378 if user_obj and user_obj.username != User.DEFAULT_USER and \
380 user_obj.user_data.get('force_password_change'):
379 user_obj.user_data.get('force_password_change'):
381 reason = 'password change required'
380 reason = 'password change required'
382 log.debug('User not allowed to authenticate, %s', reason)
381 log.debug('User not allowed to authenticate, %s', reason)
383 return HTTPNotAcceptable(reason)(environ, start_response)
382 return HTTPNotAcceptable(reason)(environ, start_response)
384
383
385 # check permissions for this repository
384 # check permissions for this repository
386 perm = self._check_permission(
385 perm = self._check_permission(
387 action, user, self.acl_repo_name, ip_addr)
386 action, user, self.acl_repo_name, ip_addr)
388 if not perm:
387 if not perm:
389 return HTTPForbidden()(environ, start_response)
388 return HTTPForbidden()(environ, start_response)
390
389
391 # extras are injected into UI object and later available
390 # extras are injected into UI object and later available
392 # in hooks executed by rhodecode
391 # in hooks executed by rhodecode
393 check_locking = _should_check_locking(environ.get('QUERY_STRING'))
392 check_locking = _should_check_locking(environ.get('QUERY_STRING'))
394 extras = vcs_operation_context(
393 extras = vcs_operation_context(
395 environ, repo_name=self.acl_repo_name, username=username,
394 environ, repo_name=self.acl_repo_name, username=username,
396 action=action, scm=self.SCM, check_locking=check_locking,
395 action=action, scm=self.SCM, check_locking=check_locking,
397 is_shadow_repo=self.is_shadow_repo
396 is_shadow_repo=self.is_shadow_repo
398 )
397 )
399
398
400 # ======================================================================
399 # ======================================================================
401 # REQUEST HANDLING
400 # REQUEST HANDLING
402 # ======================================================================
401 # ======================================================================
403 str_repo_name = safe_str(self.url_repo_name)
402 str_repo_name = safe_str(self.url_repo_name)
404 repo_path = os.path.join(
403 repo_path = os.path.join(
405 safe_str(self.basepath), safe_str(self.vcs_repo_name))
404 safe_str(self.basepath), safe_str(self.vcs_repo_name))
406 log.debug('Repository path is %s', repo_path)
405 log.debug('Repository path is %s', repo_path)
407
406
408 fix_PATH()
407 fix_PATH()
409
408
410 log.info(
409 log.info(
411 '%s action on %s repo "%s" by "%s" from %s',
410 '%s action on %s repo "%s" by "%s" from %s',
412 action, self.SCM, str_repo_name, safe_str(username), ip_addr)
411 action, self.SCM, str_repo_name, safe_str(username), ip_addr)
413
412
414 return self._generate_vcs_response(
413 return self._generate_vcs_response(
415 environ, start_response, repo_path, self.url_repo_name, extras, action)
414 environ, start_response, repo_path, self.url_repo_name, extras, action)
416
415
417 @initialize_generator
416 @initialize_generator
418 def _generate_vcs_response(
417 def _generate_vcs_response(
419 self, environ, start_response, repo_path, repo_name, extras,
418 self, environ, start_response, repo_path, repo_name, extras,
420 action):
419 action):
421 """
420 """
422 Returns a generator for the response content.
421 Returns a generator for the response content.
423
422
424 This method is implemented as a generator, so that it can trigger
423 This method is implemented as a generator, so that it can trigger
425 the cache validation after all content sent back to the client. It
424 the cache validation after all content sent back to the client. It
426 also handles the locking exceptions which will be triggered when
425 also handles the locking exceptions which will be triggered when
427 the first chunk is produced by the underlying WSGI application.
426 the first chunk is produced by the underlying WSGI application.
428 """
427 """
429 callback_daemon, extras = self._prepare_callback_daemon(extras)
428 callback_daemon, extras = self._prepare_callback_daemon(extras)
430 config = self._create_config(extras, self.acl_repo_name)
429 config = self._create_config(extras, self.acl_repo_name)
431 log.debug('HOOKS extras is %s', extras)
430 log.debug('HOOKS extras is %s', extras)
432 app = self._create_wsgi_app(repo_path, repo_name, config)
431 app = self._create_wsgi_app(repo_path, repo_name, config)
433
432
434 try:
433 try:
435 with callback_daemon:
434 with callback_daemon:
436 try:
435 try:
437 response = app(environ, start_response)
436 response = app(environ, start_response)
438 finally:
437 finally:
439 # This statement works together with the decorator
438 # This statement works together with the decorator
440 # "initialize_generator" above. The decorator ensures that
439 # "initialize_generator" above. The decorator ensures that
441 # we hit the first yield statement before the generator is
440 # we hit the first yield statement before the generator is
442 # returned back to the WSGI server. This is needed to
441 # returned back to the WSGI server. This is needed to
443 # ensure that the call to "app" above triggers the
442 # ensure that the call to "app" above triggers the
444 # needed callback to "start_response" before the
443 # needed callback to "start_response" before the
445 # generator is actually used.
444 # generator is actually used.
446 yield "__init__"
445 yield "__init__"
447
446
448 for chunk in response:
447 for chunk in response:
449 yield chunk
448 yield chunk
450 except Exception as exc:
449 except Exception as exc:
451 # TODO: johbo: Improve "translating" back the exception.
450 # TODO: johbo: Improve "translating" back the exception.
452 if getattr(exc, '_vcs_kind', None) == 'repo_locked':
451 if getattr(exc, '_vcs_kind', None) == 'repo_locked':
453 exc = HTTPLockedRC(*exc.args)
452 exc = HTTPLockedRC(*exc.args)
454 _code = rhodecode.CONFIG.get('lock_ret_code')
453 _code = rhodecode.CONFIG.get('lock_ret_code')
455 log.debug('Repository LOCKED ret code %s!', (_code,))
454 log.debug('Repository LOCKED ret code %s!', (_code,))
456 elif getattr(exc, '_vcs_kind', None) == 'requirement':
455 elif getattr(exc, '_vcs_kind', None) == 'requirement':
457 log.debug(
456 log.debug(
458 'Repository requires features unknown to this Mercurial')
457 'Repository requires features unknown to this Mercurial')
459 exc = HTTPRequirementError(*exc.args)
458 exc = HTTPRequirementError(*exc.args)
460 else:
459 else:
461 raise
460 raise
462
461
463 for chunk in exc(environ, start_response):
462 for chunk in exc(environ, start_response):
464 yield chunk
463 yield chunk
465 finally:
464 finally:
466 # invalidate cache on push
465 # invalidate cache on push
467 try:
466 try:
468 if action == 'push':
467 if action == 'push':
469 self._invalidate_cache(repo_name)
468 self._invalidate_cache(repo_name)
470 finally:
469 finally:
471 meta.Session.remove()
470 meta.Session.remove()
472
471
473 def _get_repository_name(self, environ):
472 def _get_repository_name(self, environ):
474 """Get repository name out of the environmnent
473 """Get repository name out of the environmnent
475
474
476 :param environ: WSGI environment
475 :param environ: WSGI environment
477 """
476 """
478 raise NotImplementedError()
477 raise NotImplementedError()
479
478
480 def _get_action(self, environ):
479 def _get_action(self, environ):
481 """Map request commands into a pull or push command.
480 """Map request commands into a pull or push command.
482
481
483 :param environ: WSGI environment
482 :param environ: WSGI environment
484 """
483 """
485 raise NotImplementedError()
484 raise NotImplementedError()
486
485
487 def _create_wsgi_app(self, repo_path, repo_name, config):
486 def _create_wsgi_app(self, repo_path, repo_name, config):
488 """Return the WSGI app that will finally handle the request."""
487 """Return the WSGI app that will finally handle the request."""
489 raise NotImplementedError()
488 raise NotImplementedError()
490
489
491 def _create_config(self, extras, repo_name):
490 def _create_config(self, extras, repo_name):
492 """Create a Pyro safe config representation."""
491 """Create a Pyro safe config representation."""
493 raise NotImplementedError()
492 raise NotImplementedError()
494
493
495 def _prepare_callback_daemon(self, extras):
494 def _prepare_callback_daemon(self, extras):
496 return prepare_callback_daemon(
495 return prepare_callback_daemon(
497 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
496 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
498 use_direct_calls=vcs_settings.HOOKS_DIRECT_CALLS)
497 use_direct_calls=vcs_settings.HOOKS_DIRECT_CALLS)
499
498
500
499
501 def _should_check_locking(query_string):
500 def _should_check_locking(query_string):
502 # this is kind of hacky, but due to how mercurial handles client-server
501 # this is kind of hacky, but due to how mercurial handles client-server
503 # server see all operation on commit; bookmarks, phases and
502 # server see all operation on commit; bookmarks, phases and
504 # obsolescence marker in different transaction, we don't want to check
503 # obsolescence marker in different transaction, we don't want to check
505 # locking on those
504 # locking on those
506 return query_string not in ['cmd=listkeys']
505 return query_string not in ['cmd=listkeys']
@@ -1,1011 +1,998 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 shutil
31 import shutil
32 import tempfile
32 import tempfile
33 import traceback
33 import traceback
34 import tarfile
34 import tarfile
35 import warnings
35 import warnings
36 from os.path import join as jn
36 from os.path import join as jn
37
37
38 import paste
38 import paste
39 import pkg_resources
39 import pkg_resources
40 from paste.script.command import Command, BadCommand
40 from paste.script.command import Command, BadCommand
41 from webhelpers.text import collapse, remove_formatting, strip_tags
41 from webhelpers.text import collapse, remove_formatting, strip_tags
42 from mako import exceptions
42 from mako import exceptions
43 from pyramid.threadlocal import get_current_registry
43 from pyramid.threadlocal import get_current_registry
44
44
45 from rhodecode.lib.fakemod import create_module
45 from rhodecode.lib.fakemod import create_module
46 from rhodecode.lib.vcs.backends.base import Config
46 from rhodecode.lib.vcs.backends.base import Config
47 from rhodecode.lib.vcs.exceptions import VCSError
47 from rhodecode.lib.vcs.exceptions import VCSError
48 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
48 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
49 from rhodecode.lib.utils2 import (
49 from rhodecode.lib.utils2 import (
50 safe_str, safe_unicode, get_current_rhodecode_user, md5)
50 safe_str, safe_unicode, get_current_rhodecode_user, md5)
51 from rhodecode.model import meta
51 from rhodecode.model import meta
52 from rhodecode.model.db import (
52 from rhodecode.model.db import (
53 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
53 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
54 from rhodecode.model.meta import Session
54 from rhodecode.model.meta import Session
55
55
56
56
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
59 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
60
60
61 _license_cache = None
61 # String of characters which are not allowed in repo/group slugs.
62
62 SLUG_BAD_CHARS = re.escape('`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
63
63 # Regex that matches forbidden characters in repo/group slugs.
64 def recursive_replace(str_, replace=' '):
64 SLUG_BAD_CHAR_RE = re.compile('[{}]'.format(SLUG_BAD_CHARS))
65 """
65 # Regex that matches allowed characters in repo/group slugs.
66 Recursive replace of given sign to just one instance
66 SLUG_GOOD_CHAR_RE = re.compile('[^{}]'.format(SLUG_BAD_CHARS))
67
67 # Regex that matches whole repo/group slugs.
68 :param str_: given string
68 SLUG_RE = re.compile('[^{}]+'.format(SLUG_BAD_CHARS))
69 :param replace: char to find and replace multiple instances
70
69
71 Examples::
70 _license_cache = None
72 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
73 'Mighty-Mighty-Bo-sstones'
74 """
75
76 if str_.find(replace * 2) == -1:
77 return str_
78 else:
79 str_ = str_.replace(replace * 2, replace)
80 return recursive_replace(str_, replace)
81
71
82
72
83 def repo_name_slug(value):
73 def repo_name_slug(value):
84 """
74 """
85 Return slug of name of repository
75 Return slug of name of repository
86 This function is called on each creation/modification
76 This function is called on each creation/modification
87 of repository to prevent bad names in repo
77 of repository to prevent bad names in repo
88 """
78 """
79 replacement_char = '-'
89
80
90 slug = remove_formatting(value)
81 slug = remove_formatting(value)
91 slug = strip_tags(slug)
82 slug = SLUG_BAD_CHAR_RE.sub(replacement_char, slug)
92
83 slug = collapse(slug, replacement_char)
93 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
94 slug = slug.replace(c, '-')
95 slug = recursive_replace(slug, '-')
96 slug = collapse(slug, '-')
97 return slug
84 return slug
98
85
99
86
100 #==============================================================================
87 #==============================================================================
101 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
88 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
102 #==============================================================================
89 #==============================================================================
103 def get_repo_slug(request):
90 def get_repo_slug(request):
104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
91 _repo = request.environ['pylons.routes_dict'].get('repo_name')
105 if _repo:
92 if _repo:
106 _repo = _repo.rstrip('/')
93 _repo = _repo.rstrip('/')
107 return _repo
94 return _repo
108
95
109
96
110 def get_repo_group_slug(request):
97 def get_repo_group_slug(request):
111 _group = request.environ['pylons.routes_dict'].get('group_name')
98 _group = request.environ['pylons.routes_dict'].get('group_name')
112 if _group:
99 if _group:
113 _group = _group.rstrip('/')
100 _group = _group.rstrip('/')
114 return _group
101 return _group
115
102
116
103
117 def get_user_group_slug(request):
104 def get_user_group_slug(request):
118 _group = request.environ['pylons.routes_dict'].get('user_group_id')
105 _group = request.environ['pylons.routes_dict'].get('user_group_id')
119 try:
106 try:
120 _group = UserGroup.get(_group)
107 _group = UserGroup.get(_group)
121 if _group:
108 if _group:
122 _group = _group.users_group_name
109 _group = _group.users_group_name
123 except Exception:
110 except Exception:
124 log.debug(traceback.format_exc())
111 log.debug(traceback.format_exc())
125 #catch all failures here
112 #catch all failures here
126 pass
113 pass
127
114
128 return _group
115 return _group
129
116
130
117
131 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
118 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
132 """
119 """
133 Action logger for various actions made by users
120 Action logger for various actions made by users
134
121
135 :param user: user that made this action, can be a unique username string or
122 :param user: user that made this action, can be a unique username string or
136 object containing user_id attribute
123 object containing user_id attribute
137 :param action: action to log, should be on of predefined unique actions for
124 :param action: action to log, should be on of predefined unique actions for
138 easy translations
125 easy translations
139 :param repo: string name of repository or object containing repo_id,
126 :param repo: string name of repository or object containing repo_id,
140 that action was made on
127 that action was made on
141 :param ipaddr: optional ip address from what the action was made
128 :param ipaddr: optional ip address from what the action was made
142 :param sa: optional sqlalchemy session
129 :param sa: optional sqlalchemy session
143
130
144 """
131 """
145
132
146 if not sa:
133 if not sa:
147 sa = meta.Session()
134 sa = meta.Session()
148 # if we don't get explicit IP address try to get one from registered user
135 # if we don't get explicit IP address try to get one from registered user
149 # in tmpl context var
136 # in tmpl context var
150 if not ipaddr:
137 if not ipaddr:
151 ipaddr = getattr(get_current_rhodecode_user(), 'ip_addr', '')
138 ipaddr = getattr(get_current_rhodecode_user(), 'ip_addr', '')
152
139
153 try:
140 try:
154 if getattr(user, 'user_id', None):
141 if getattr(user, 'user_id', None):
155 user_obj = User.get(user.user_id)
142 user_obj = User.get(user.user_id)
156 elif isinstance(user, basestring):
143 elif isinstance(user, basestring):
157 user_obj = User.get_by_username(user)
144 user_obj = User.get_by_username(user)
158 else:
145 else:
159 raise Exception('You have to provide a user object or a username')
146 raise Exception('You have to provide a user object or a username')
160
147
161 if getattr(repo, 'repo_id', None):
148 if getattr(repo, 'repo_id', None):
162 repo_obj = Repository.get(repo.repo_id)
149 repo_obj = Repository.get(repo.repo_id)
163 repo_name = repo_obj.repo_name
150 repo_name = repo_obj.repo_name
164 elif isinstance(repo, basestring):
151 elif isinstance(repo, basestring):
165 repo_name = repo.lstrip('/')
152 repo_name = repo.lstrip('/')
166 repo_obj = Repository.get_by_repo_name(repo_name)
153 repo_obj = Repository.get_by_repo_name(repo_name)
167 else:
154 else:
168 repo_obj = None
155 repo_obj = None
169 repo_name = ''
156 repo_name = ''
170
157
171 user_log = UserLog()
158 user_log = UserLog()
172 user_log.user_id = user_obj.user_id
159 user_log.user_id = user_obj.user_id
173 user_log.username = user_obj.username
160 user_log.username = user_obj.username
174 action = safe_unicode(action)
161 action = safe_unicode(action)
175 user_log.action = action[:1200000]
162 user_log.action = action[:1200000]
176
163
177 user_log.repository = repo_obj
164 user_log.repository = repo_obj
178 user_log.repository_name = repo_name
165 user_log.repository_name = repo_name
179
166
180 user_log.action_date = datetime.datetime.now()
167 user_log.action_date = datetime.datetime.now()
181 user_log.user_ip = ipaddr
168 user_log.user_ip = ipaddr
182 sa.add(user_log)
169 sa.add(user_log)
183
170
184 log.info('Logging action:`%s` on repo:`%s` by user:%s ip:%s',
171 log.info('Logging action:`%s` on repo:`%s` by user:%s ip:%s',
185 action, safe_unicode(repo), user_obj, ipaddr)
172 action, safe_unicode(repo), user_obj, ipaddr)
186 if commit:
173 if commit:
187 sa.commit()
174 sa.commit()
188 except Exception:
175 except Exception:
189 log.error(traceback.format_exc())
176 log.error(traceback.format_exc())
190 raise
177 raise
191
178
192
179
193 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
180 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
194 """
181 """
195 Scans given path for repos and return (name,(type,path)) tuple
182 Scans given path for repos and return (name,(type,path)) tuple
196
183
197 :param path: path to scan for repositories
184 :param path: path to scan for repositories
198 :param recursive: recursive search and return names with subdirs in front
185 :param recursive: recursive search and return names with subdirs in front
199 """
186 """
200
187
201 # remove ending slash for better results
188 # remove ending slash for better results
202 path = path.rstrip(os.sep)
189 path = path.rstrip(os.sep)
203 log.debug('now scanning in %s location recursive:%s...', path, recursive)
190 log.debug('now scanning in %s location recursive:%s...', path, recursive)
204
191
205 def _get_repos(p):
192 def _get_repos(p):
206 dirpaths = _get_dirpaths(p)
193 dirpaths = _get_dirpaths(p)
207 if not _is_dir_writable(p):
194 if not _is_dir_writable(p):
208 log.warning('repo path without write access: %s', p)
195 log.warning('repo path without write access: %s', p)
209
196
210 for dirpath in dirpaths:
197 for dirpath in dirpaths:
211 if os.path.isfile(os.path.join(p, dirpath)):
198 if os.path.isfile(os.path.join(p, dirpath)):
212 continue
199 continue
213 cur_path = os.path.join(p, dirpath)
200 cur_path = os.path.join(p, dirpath)
214
201
215 # skip removed repos
202 # skip removed repos
216 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
203 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
217 continue
204 continue
218
205
219 #skip .<somethin> dirs
206 #skip .<somethin> dirs
220 if dirpath.startswith('.'):
207 if dirpath.startswith('.'):
221 continue
208 continue
222
209
223 try:
210 try:
224 scm_info = get_scm(cur_path)
211 scm_info = get_scm(cur_path)
225 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
212 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
226 except VCSError:
213 except VCSError:
227 if not recursive:
214 if not recursive:
228 continue
215 continue
229 #check if this dir containts other repos for recursive scan
216 #check if this dir containts other repos for recursive scan
230 rec_path = os.path.join(p, dirpath)
217 rec_path = os.path.join(p, dirpath)
231 if os.path.isdir(rec_path):
218 if os.path.isdir(rec_path):
232 for inner_scm in _get_repos(rec_path):
219 for inner_scm in _get_repos(rec_path):
233 yield inner_scm
220 yield inner_scm
234
221
235 return _get_repos(path)
222 return _get_repos(path)
236
223
237
224
238 def _get_dirpaths(p):
225 def _get_dirpaths(p):
239 try:
226 try:
240 # OS-independable way of checking if we have at least read-only
227 # OS-independable way of checking if we have at least read-only
241 # access or not.
228 # access or not.
242 dirpaths = os.listdir(p)
229 dirpaths = os.listdir(p)
243 except OSError:
230 except OSError:
244 log.warning('ignoring repo path without read access: %s', p)
231 log.warning('ignoring repo path without read access: %s', p)
245 return []
232 return []
246
233
247 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
234 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
248 # decode paths and suddenly returns unicode objects itself. The items it
235 # decode paths and suddenly returns unicode objects itself. The items it
249 # cannot decode are returned as strings and cause issues.
236 # cannot decode are returned as strings and cause issues.
250 #
237 #
251 # Those paths are ignored here until a solid solution for path handling has
238 # Those paths are ignored here until a solid solution for path handling has
252 # been built.
239 # been built.
253 expected_type = type(p)
240 expected_type = type(p)
254
241
255 def _has_correct_type(item):
242 def _has_correct_type(item):
256 if type(item) is not expected_type:
243 if type(item) is not expected_type:
257 log.error(
244 log.error(
258 u"Ignoring path %s since it cannot be decoded into unicode.",
245 u"Ignoring path %s since it cannot be decoded into unicode.",
259 # Using "repr" to make sure that we see the byte value in case
246 # Using "repr" to make sure that we see the byte value in case
260 # of support.
247 # of support.
261 repr(item))
248 repr(item))
262 return False
249 return False
263 return True
250 return True
264
251
265 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
252 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
266
253
267 return dirpaths
254 return dirpaths
268
255
269
256
270 def _is_dir_writable(path):
257 def _is_dir_writable(path):
271 """
258 """
272 Probe if `path` is writable.
259 Probe if `path` is writable.
273
260
274 Due to trouble on Cygwin / Windows, this is actually probing if it is
261 Due to trouble on Cygwin / Windows, this is actually probing if it is
275 possible to create a file inside of `path`, stat does not produce reliable
262 possible to create a file inside of `path`, stat does not produce reliable
276 results in this case.
263 results in this case.
277 """
264 """
278 try:
265 try:
279 with tempfile.TemporaryFile(dir=path):
266 with tempfile.TemporaryFile(dir=path):
280 pass
267 pass
281 except OSError:
268 except OSError:
282 return False
269 return False
283 return True
270 return True
284
271
285
272
286 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None):
273 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None):
287 """
274 """
288 Returns True if given path is a valid repository False otherwise.
275 Returns True if given path is a valid repository False otherwise.
289 If expect_scm param is given also, compare if given scm is the same
276 If expect_scm param is given also, compare if given scm is the same
290 as expected from scm parameter. If explicit_scm is given don't try to
277 as expected from scm parameter. If explicit_scm is given don't try to
291 detect the scm, just use the given one to check if repo is valid
278 detect the scm, just use the given one to check if repo is valid
292
279
293 :param repo_name:
280 :param repo_name:
294 :param base_path:
281 :param base_path:
295 :param expect_scm:
282 :param expect_scm:
296 :param explicit_scm:
283 :param explicit_scm:
297
284
298 :return True: if given path is a valid repository
285 :return True: if given path is a valid repository
299 """
286 """
300 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
287 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
301 log.debug('Checking if `%s` is a valid path for repository', repo_name)
288 log.debug('Checking if `%s` is a valid path for repository', repo_name)
302
289
303 try:
290 try:
304 if explicit_scm:
291 if explicit_scm:
305 detected_scms = [get_scm_backend(explicit_scm)]
292 detected_scms = [get_scm_backend(explicit_scm)]
306 else:
293 else:
307 detected_scms = get_scm(full_path)
294 detected_scms = get_scm(full_path)
308
295
309 if expect_scm:
296 if expect_scm:
310 return detected_scms[0] == expect_scm
297 return detected_scms[0] == expect_scm
311 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
298 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
312 return True
299 return True
313 except VCSError:
300 except VCSError:
314 log.debug('path: %s is not a valid repo !', full_path)
301 log.debug('path: %s is not a valid repo !', full_path)
315 return False
302 return False
316
303
317
304
318 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
305 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
319 """
306 """
320 Returns True if given path is a repository group, False otherwise
307 Returns True if given path is a repository group, False otherwise
321
308
322 :param repo_name:
309 :param repo_name:
323 :param base_path:
310 :param base_path:
324 """
311 """
325 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
312 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
326 log.debug('Checking if `%s` is a valid path for repository group',
313 log.debug('Checking if `%s` is a valid path for repository group',
327 repo_group_name)
314 repo_group_name)
328
315
329 # check if it's not a repo
316 # check if it's not a repo
330 if is_valid_repo(repo_group_name, base_path):
317 if is_valid_repo(repo_group_name, base_path):
331 log.debug('Repo called %s exist, it is not a valid '
318 log.debug('Repo called %s exist, it is not a valid '
332 'repo group' % repo_group_name)
319 'repo group' % repo_group_name)
333 return False
320 return False
334
321
335 try:
322 try:
336 # we need to check bare git repos at higher level
323 # we need to check bare git repos at higher level
337 # since we might match branches/hooks/info/objects or possible
324 # since we might match branches/hooks/info/objects or possible
338 # other things inside bare git repo
325 # other things inside bare git repo
339 scm_ = get_scm(os.path.dirname(full_path))
326 scm_ = get_scm(os.path.dirname(full_path))
340 log.debug('path: %s is a vcs object:%s, not valid '
327 log.debug('path: %s is a vcs object:%s, not valid '
341 'repo group' % (full_path, scm_))
328 'repo group' % (full_path, scm_))
342 return False
329 return False
343 except VCSError:
330 except VCSError:
344 pass
331 pass
345
332
346 # check if it's a valid path
333 # check if it's a valid path
347 if skip_path_check or os.path.isdir(full_path):
334 if skip_path_check or os.path.isdir(full_path):
348 log.debug('path: %s is a valid repo group !', full_path)
335 log.debug('path: %s is a valid repo group !', full_path)
349 return True
336 return True
350
337
351 log.debug('path: %s is not a valid repo group !', full_path)
338 log.debug('path: %s is not a valid repo group !', full_path)
352 return False
339 return False
353
340
354
341
355 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
342 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
356 while True:
343 while True:
357 ok = raw_input(prompt)
344 ok = raw_input(prompt)
358 if ok.lower() in ('y', 'ye', 'yes'):
345 if ok.lower() in ('y', 'ye', 'yes'):
359 return True
346 return True
360 if ok.lower() in ('n', 'no', 'nop', 'nope'):
347 if ok.lower() in ('n', 'no', 'nop', 'nope'):
361 return False
348 return False
362 retries = retries - 1
349 retries = retries - 1
363 if retries < 0:
350 if retries < 0:
364 raise IOError
351 raise IOError
365 print(complaint)
352 print(complaint)
366
353
367 # propagated from mercurial documentation
354 # propagated from mercurial documentation
368 ui_sections = [
355 ui_sections = [
369 'alias', 'auth',
356 'alias', 'auth',
370 'decode/encode', 'defaults',
357 'decode/encode', 'defaults',
371 'diff', 'email',
358 'diff', 'email',
372 'extensions', 'format',
359 'extensions', 'format',
373 'merge-patterns', 'merge-tools',
360 'merge-patterns', 'merge-tools',
374 'hooks', 'http_proxy',
361 'hooks', 'http_proxy',
375 'smtp', 'patch',
362 'smtp', 'patch',
376 'paths', 'profiling',
363 'paths', 'profiling',
377 'server', 'trusted',
364 'server', 'trusted',
378 'ui', 'web', ]
365 'ui', 'web', ]
379
366
380
367
381 def config_data_from_db(clear_session=True, repo=None):
368 def config_data_from_db(clear_session=True, repo=None):
382 """
369 """
383 Read the configuration data from the database and return configuration
370 Read the configuration data from the database and return configuration
384 tuples.
371 tuples.
385 """
372 """
386 from rhodecode.model.settings import VcsSettingsModel
373 from rhodecode.model.settings import VcsSettingsModel
387
374
388 config = []
375 config = []
389
376
390 sa = meta.Session()
377 sa = meta.Session()
391 settings_model = VcsSettingsModel(repo=repo, sa=sa)
378 settings_model = VcsSettingsModel(repo=repo, sa=sa)
392
379
393 ui_settings = settings_model.get_ui_settings()
380 ui_settings = settings_model.get_ui_settings()
394
381
395 for setting in ui_settings:
382 for setting in ui_settings:
396 if setting.active:
383 if setting.active:
397 log.debug(
384 log.debug(
398 'settings ui from db: [%s] %s=%s',
385 'settings ui from db: [%s] %s=%s',
399 setting.section, setting.key, setting.value)
386 setting.section, setting.key, setting.value)
400 config.append((
387 config.append((
401 safe_str(setting.section), safe_str(setting.key),
388 safe_str(setting.section), safe_str(setting.key),
402 safe_str(setting.value)))
389 safe_str(setting.value)))
403 if setting.key == 'push_ssl':
390 if setting.key == 'push_ssl':
404 # force set push_ssl requirement to False, rhodecode
391 # force set push_ssl requirement to False, rhodecode
405 # handles that
392 # handles that
406 config.append((
393 config.append((
407 safe_str(setting.section), safe_str(setting.key), False))
394 safe_str(setting.section), safe_str(setting.key), False))
408 if clear_session:
395 if clear_session:
409 meta.Session.remove()
396 meta.Session.remove()
410
397
411 # TODO: mikhail: probably it makes no sense to re-read hooks information.
398 # TODO: mikhail: probably it makes no sense to re-read hooks information.
412 # It's already there and activated/deactivated
399 # It's already there and activated/deactivated
413 skip_entries = []
400 skip_entries = []
414 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
401 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
415 if 'pull' not in enabled_hook_classes:
402 if 'pull' not in enabled_hook_classes:
416 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
403 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
417 if 'push' not in enabled_hook_classes:
404 if 'push' not in enabled_hook_classes:
418 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
405 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
419
406
420 config = [entry for entry in config if entry[:2] not in skip_entries]
407 config = [entry for entry in config if entry[:2] not in skip_entries]
421
408
422 return config
409 return config
423
410
424
411
425 def make_db_config(clear_session=True, repo=None):
412 def make_db_config(clear_session=True, repo=None):
426 """
413 """
427 Create a :class:`Config` instance based on the values in the database.
414 Create a :class:`Config` instance based on the values in the database.
428 """
415 """
429 config = Config()
416 config = Config()
430 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
417 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
431 for section, option, value in config_data:
418 for section, option, value in config_data:
432 config.set(section, option, value)
419 config.set(section, option, value)
433 return config
420 return config
434
421
435
422
436 def get_enabled_hook_classes(ui_settings):
423 def get_enabled_hook_classes(ui_settings):
437 """
424 """
438 Return the enabled hook classes.
425 Return the enabled hook classes.
439
426
440 :param ui_settings: List of ui_settings as returned
427 :param ui_settings: List of ui_settings as returned
441 by :meth:`VcsSettingsModel.get_ui_settings`
428 by :meth:`VcsSettingsModel.get_ui_settings`
442
429
443 :return: a list with the enabled hook classes. The order is not guaranteed.
430 :return: a list with the enabled hook classes. The order is not guaranteed.
444 :rtype: list
431 :rtype: list
445 """
432 """
446 enabled_hooks = []
433 enabled_hooks = []
447 active_hook_keys = [
434 active_hook_keys = [
448 key for section, key, value, active in ui_settings
435 key for section, key, value, active in ui_settings
449 if section == 'hooks' and active]
436 if section == 'hooks' and active]
450
437
451 hook_names = {
438 hook_names = {
452 RhodeCodeUi.HOOK_PUSH: 'push',
439 RhodeCodeUi.HOOK_PUSH: 'push',
453 RhodeCodeUi.HOOK_PULL: 'pull',
440 RhodeCodeUi.HOOK_PULL: 'pull',
454 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
441 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
455 }
442 }
456
443
457 for key in active_hook_keys:
444 for key in active_hook_keys:
458 hook = hook_names.get(key)
445 hook = hook_names.get(key)
459 if hook:
446 if hook:
460 enabled_hooks.append(hook)
447 enabled_hooks.append(hook)
461
448
462 return enabled_hooks
449 return enabled_hooks
463
450
464
451
465 def set_rhodecode_config(config):
452 def set_rhodecode_config(config):
466 """
453 """
467 Updates pylons config with new settings from database
454 Updates pylons config with new settings from database
468
455
469 :param config:
456 :param config:
470 """
457 """
471 from rhodecode.model.settings import SettingsModel
458 from rhodecode.model.settings import SettingsModel
472 app_settings = SettingsModel().get_all_settings()
459 app_settings = SettingsModel().get_all_settings()
473
460
474 for k, v in app_settings.items():
461 for k, v in app_settings.items():
475 config[k] = v
462 config[k] = v
476
463
477
464
478 def get_rhodecode_realm():
465 def get_rhodecode_realm():
479 """
466 """
480 Return the rhodecode realm from database.
467 Return the rhodecode realm from database.
481 """
468 """
482 from rhodecode.model.settings import SettingsModel
469 from rhodecode.model.settings import SettingsModel
483 realm = SettingsModel().get_setting_by_name('realm')
470 realm = SettingsModel().get_setting_by_name('realm')
484 return safe_str(realm.app_settings_value)
471 return safe_str(realm.app_settings_value)
485
472
486
473
487 def get_rhodecode_base_path():
474 def get_rhodecode_base_path():
488 """
475 """
489 Returns the base path. The base path is the filesystem path which points
476 Returns the base path. The base path is the filesystem path which points
490 to the repository store.
477 to the repository store.
491 """
478 """
492 from rhodecode.model.settings import SettingsModel
479 from rhodecode.model.settings import SettingsModel
493 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
480 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
494 return safe_str(paths_ui.ui_value)
481 return safe_str(paths_ui.ui_value)
495
482
496
483
497 def map_groups(path):
484 def map_groups(path):
498 """
485 """
499 Given a full path to a repository, create all nested groups that this
486 Given a full path to a repository, create all nested groups that this
500 repo is inside. This function creates parent-child relationships between
487 repo is inside. This function creates parent-child relationships between
501 groups and creates default perms for all new groups.
488 groups and creates default perms for all new groups.
502
489
503 :param paths: full path to repository
490 :param paths: full path to repository
504 """
491 """
505 from rhodecode.model.repo_group import RepoGroupModel
492 from rhodecode.model.repo_group import RepoGroupModel
506 sa = meta.Session()
493 sa = meta.Session()
507 groups = path.split(Repository.NAME_SEP)
494 groups = path.split(Repository.NAME_SEP)
508 parent = None
495 parent = None
509 group = None
496 group = None
510
497
511 # last element is repo in nested groups structure
498 # last element is repo in nested groups structure
512 groups = groups[:-1]
499 groups = groups[:-1]
513 rgm = RepoGroupModel(sa)
500 rgm = RepoGroupModel(sa)
514 owner = User.get_first_super_admin()
501 owner = User.get_first_super_admin()
515 for lvl, group_name in enumerate(groups):
502 for lvl, group_name in enumerate(groups):
516 group_name = '/'.join(groups[:lvl] + [group_name])
503 group_name = '/'.join(groups[:lvl] + [group_name])
517 group = RepoGroup.get_by_group_name(group_name)
504 group = RepoGroup.get_by_group_name(group_name)
518 desc = '%s group' % group_name
505 desc = '%s group' % group_name
519
506
520 # skip folders that are now removed repos
507 # skip folders that are now removed repos
521 if REMOVED_REPO_PAT.match(group_name):
508 if REMOVED_REPO_PAT.match(group_name):
522 break
509 break
523
510
524 if group is None:
511 if group is None:
525 log.debug('creating group level: %s group_name: %s',
512 log.debug('creating group level: %s group_name: %s',
526 lvl, group_name)
513 lvl, group_name)
527 group = RepoGroup(group_name, parent)
514 group = RepoGroup(group_name, parent)
528 group.group_description = desc
515 group.group_description = desc
529 group.user = owner
516 group.user = owner
530 sa.add(group)
517 sa.add(group)
531 perm_obj = rgm._create_default_perms(group)
518 perm_obj = rgm._create_default_perms(group)
532 sa.add(perm_obj)
519 sa.add(perm_obj)
533 sa.flush()
520 sa.flush()
534
521
535 parent = group
522 parent = group
536 return group
523 return group
537
524
538
525
539 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
526 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
540 """
527 """
541 maps all repos given in initial_repo_list, non existing repositories
528 maps all repos given in initial_repo_list, non existing repositories
542 are created, if remove_obsolete is True it also checks for db entries
529 are created, if remove_obsolete is True it also checks for db entries
543 that are not in initial_repo_list and removes them.
530 that are not in initial_repo_list and removes them.
544
531
545 :param initial_repo_list: list of repositories found by scanning methods
532 :param initial_repo_list: list of repositories found by scanning methods
546 :param remove_obsolete: check for obsolete entries in database
533 :param remove_obsolete: check for obsolete entries in database
547 """
534 """
548 from rhodecode.model.repo import RepoModel
535 from rhodecode.model.repo import RepoModel
549 from rhodecode.model.scm import ScmModel
536 from rhodecode.model.scm import ScmModel
550 from rhodecode.model.repo_group import RepoGroupModel
537 from rhodecode.model.repo_group import RepoGroupModel
551 from rhodecode.model.settings import SettingsModel
538 from rhodecode.model.settings import SettingsModel
552
539
553 sa = meta.Session()
540 sa = meta.Session()
554 repo_model = RepoModel()
541 repo_model = RepoModel()
555 user = User.get_first_super_admin()
542 user = User.get_first_super_admin()
556 added = []
543 added = []
557
544
558 # creation defaults
545 # creation defaults
559 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
546 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
560 enable_statistics = defs.get('repo_enable_statistics')
547 enable_statistics = defs.get('repo_enable_statistics')
561 enable_locking = defs.get('repo_enable_locking')
548 enable_locking = defs.get('repo_enable_locking')
562 enable_downloads = defs.get('repo_enable_downloads')
549 enable_downloads = defs.get('repo_enable_downloads')
563 private = defs.get('repo_private')
550 private = defs.get('repo_private')
564
551
565 for name, repo in initial_repo_list.items():
552 for name, repo in initial_repo_list.items():
566 group = map_groups(name)
553 group = map_groups(name)
567 unicode_name = safe_unicode(name)
554 unicode_name = safe_unicode(name)
568 db_repo = repo_model.get_by_repo_name(unicode_name)
555 db_repo = repo_model.get_by_repo_name(unicode_name)
569 # found repo that is on filesystem not in RhodeCode database
556 # found repo that is on filesystem not in RhodeCode database
570 if not db_repo:
557 if not db_repo:
571 log.info('repository %s not found, creating now', name)
558 log.info('repository %s not found, creating now', name)
572 added.append(name)
559 added.append(name)
573 desc = (repo.description
560 desc = (repo.description
574 if repo.description != 'unknown'
561 if repo.description != 'unknown'
575 else '%s repository' % name)
562 else '%s repository' % name)
576
563
577 db_repo = repo_model._create_repo(
564 db_repo = repo_model._create_repo(
578 repo_name=name,
565 repo_name=name,
579 repo_type=repo.alias,
566 repo_type=repo.alias,
580 description=desc,
567 description=desc,
581 repo_group=getattr(group, 'group_id', None),
568 repo_group=getattr(group, 'group_id', None),
582 owner=user,
569 owner=user,
583 enable_locking=enable_locking,
570 enable_locking=enable_locking,
584 enable_downloads=enable_downloads,
571 enable_downloads=enable_downloads,
585 enable_statistics=enable_statistics,
572 enable_statistics=enable_statistics,
586 private=private,
573 private=private,
587 state=Repository.STATE_CREATED
574 state=Repository.STATE_CREATED
588 )
575 )
589 sa.commit()
576 sa.commit()
590 # we added that repo just now, and make sure we updated server info
577 # we added that repo just now, and make sure we updated server info
591 if db_repo.repo_type == 'git':
578 if db_repo.repo_type == 'git':
592 git_repo = db_repo.scm_instance()
579 git_repo = db_repo.scm_instance()
593 # update repository server-info
580 # update repository server-info
594 log.debug('Running update server info')
581 log.debug('Running update server info')
595 git_repo._update_server_info()
582 git_repo._update_server_info()
596
583
597 db_repo.update_commit_cache()
584 db_repo.update_commit_cache()
598
585
599 config = db_repo._config
586 config = db_repo._config
600 config.set('extensions', 'largefiles', '')
587 config.set('extensions', 'largefiles', '')
601 ScmModel().install_hooks(
588 ScmModel().install_hooks(
602 db_repo.scm_instance(config=config),
589 db_repo.scm_instance(config=config),
603 repo_type=db_repo.repo_type)
590 repo_type=db_repo.repo_type)
604
591
605 removed = []
592 removed = []
606 if remove_obsolete:
593 if remove_obsolete:
607 # remove from database those repositories that are not in the filesystem
594 # remove from database those repositories that are not in the filesystem
608 for repo in sa.query(Repository).all():
595 for repo in sa.query(Repository).all():
609 if repo.repo_name not in initial_repo_list.keys():
596 if repo.repo_name not in initial_repo_list.keys():
610 log.debug("Removing non-existing repository found in db `%s`",
597 log.debug("Removing non-existing repository found in db `%s`",
611 repo.repo_name)
598 repo.repo_name)
612 try:
599 try:
613 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
600 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
614 sa.commit()
601 sa.commit()
615 removed.append(repo.repo_name)
602 removed.append(repo.repo_name)
616 except Exception:
603 except Exception:
617 # don't hold further removals on error
604 # don't hold further removals on error
618 log.error(traceback.format_exc())
605 log.error(traceback.format_exc())
619 sa.rollback()
606 sa.rollback()
620
607
621 def splitter(full_repo_name):
608 def splitter(full_repo_name):
622 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
609 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
623 gr_name = None
610 gr_name = None
624 if len(_parts) == 2:
611 if len(_parts) == 2:
625 gr_name = _parts[0]
612 gr_name = _parts[0]
626 return gr_name
613 return gr_name
627
614
628 initial_repo_group_list = [splitter(x) for x in
615 initial_repo_group_list = [splitter(x) for x in
629 initial_repo_list.keys() if splitter(x)]
616 initial_repo_list.keys() if splitter(x)]
630
617
631 # remove from database those repository groups that are not in the
618 # remove from database those repository groups that are not in the
632 # filesystem due to parent child relationships we need to delete them
619 # filesystem due to parent child relationships we need to delete them
633 # in a specific order of most nested first
620 # in a specific order of most nested first
634 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
621 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
635 nested_sort = lambda gr: len(gr.split('/'))
622 nested_sort = lambda gr: len(gr.split('/'))
636 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
623 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
637 if group_name not in initial_repo_group_list:
624 if group_name not in initial_repo_group_list:
638 repo_group = RepoGroup.get_by_group_name(group_name)
625 repo_group = RepoGroup.get_by_group_name(group_name)
639 if (repo_group.children.all() or
626 if (repo_group.children.all() or
640 not RepoGroupModel().check_exist_filesystem(
627 not RepoGroupModel().check_exist_filesystem(
641 group_name=group_name, exc_on_failure=False)):
628 group_name=group_name, exc_on_failure=False)):
642 continue
629 continue
643
630
644 log.info(
631 log.info(
645 'Removing non-existing repository group found in db `%s`',
632 'Removing non-existing repository group found in db `%s`',
646 group_name)
633 group_name)
647 try:
634 try:
648 RepoGroupModel(sa).delete(group_name, fs_remove=False)
635 RepoGroupModel(sa).delete(group_name, fs_remove=False)
649 sa.commit()
636 sa.commit()
650 removed.append(group_name)
637 removed.append(group_name)
651 except Exception:
638 except Exception:
652 # don't hold further removals on error
639 # don't hold further removals on error
653 log.exception(
640 log.exception(
654 'Unable to remove repository group `%s`',
641 'Unable to remove repository group `%s`',
655 group_name)
642 group_name)
656 sa.rollback()
643 sa.rollback()
657 raise
644 raise
658
645
659 return added, removed
646 return added, removed
660
647
661
648
662 def get_default_cache_settings(settings):
649 def get_default_cache_settings(settings):
663 cache_settings = {}
650 cache_settings = {}
664 for key in settings.keys():
651 for key in settings.keys():
665 for prefix in ['beaker.cache.', 'cache.']:
652 for prefix in ['beaker.cache.', 'cache.']:
666 if key.startswith(prefix):
653 if key.startswith(prefix):
667 name = key.split(prefix)[1].strip()
654 name = key.split(prefix)[1].strip()
668 cache_settings[name] = settings[key].strip()
655 cache_settings[name] = settings[key].strip()
669 return cache_settings
656 return cache_settings
670
657
671
658
672 # set cache regions for beaker so celery can utilise it
659 # set cache regions for beaker so celery can utilise it
673 def add_cache(settings):
660 def add_cache(settings):
674 from rhodecode.lib import caches
661 from rhodecode.lib import caches
675 cache_settings = {'regions': None}
662 cache_settings = {'regions': None}
676 # main cache settings used as default ...
663 # main cache settings used as default ...
677 cache_settings.update(get_default_cache_settings(settings))
664 cache_settings.update(get_default_cache_settings(settings))
678
665
679 if cache_settings['regions']:
666 if cache_settings['regions']:
680 for region in cache_settings['regions'].split(','):
667 for region in cache_settings['regions'].split(','):
681 region = region.strip()
668 region = region.strip()
682 region_settings = {}
669 region_settings = {}
683 for key, value in cache_settings.items():
670 for key, value in cache_settings.items():
684 if key.startswith(region):
671 if key.startswith(region):
685 region_settings[key.split('.')[1]] = value
672 region_settings[key.split('.')[1]] = value
686
673
687 caches.configure_cache_region(
674 caches.configure_cache_region(
688 region, region_settings, cache_settings)
675 region, region_settings, cache_settings)
689
676
690
677
691 def load_rcextensions(root_path):
678 def load_rcextensions(root_path):
692 import rhodecode
679 import rhodecode
693 from rhodecode.config import conf
680 from rhodecode.config import conf
694
681
695 path = os.path.join(root_path, 'rcextensions', '__init__.py')
682 path = os.path.join(root_path, 'rcextensions', '__init__.py')
696 if os.path.isfile(path):
683 if os.path.isfile(path):
697 rcext = create_module('rc', path)
684 rcext = create_module('rc', path)
698 EXT = rhodecode.EXTENSIONS = rcext
685 EXT = rhodecode.EXTENSIONS = rcext
699 log.debug('Found rcextensions now loading %s...', rcext)
686 log.debug('Found rcextensions now loading %s...', rcext)
700
687
701 # Additional mappings that are not present in the pygments lexers
688 # Additional mappings that are not present in the pygments lexers
702 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
689 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
703
690
704 # auto check if the module is not missing any data, set to default if is
691 # auto check if the module is not missing any data, set to default if is
705 # this will help autoupdate new feature of rcext module
692 # this will help autoupdate new feature of rcext module
706 #from rhodecode.config import rcextensions
693 #from rhodecode.config import rcextensions
707 #for k in dir(rcextensions):
694 #for k in dir(rcextensions):
708 # if not k.startswith('_') and not hasattr(EXT, k):
695 # if not k.startswith('_') and not hasattr(EXT, k):
709 # setattr(EXT, k, getattr(rcextensions, k))
696 # setattr(EXT, k, getattr(rcextensions, k))
710
697
711
698
712 def get_custom_lexer(extension):
699 def get_custom_lexer(extension):
713 """
700 """
714 returns a custom lexer if it is defined in rcextensions module, or None
701 returns a custom lexer if it is defined in rcextensions module, or None
715 if there's no custom lexer defined
702 if there's no custom lexer defined
716 """
703 """
717 import rhodecode
704 import rhodecode
718 from pygments import lexers
705 from pygments import lexers
719 # check if we didn't define this extension as other lexer
706 # check if we didn't define this extension as other lexer
720 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
707 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
721 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
708 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
722 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
709 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
723 return lexers.get_lexer_by_name(_lexer_name)
710 return lexers.get_lexer_by_name(_lexer_name)
724
711
725
712
726 #==============================================================================
713 #==============================================================================
727 # TEST FUNCTIONS AND CREATORS
714 # TEST FUNCTIONS AND CREATORS
728 #==============================================================================
715 #==============================================================================
729 def create_test_index(repo_location, config):
716 def create_test_index(repo_location, config):
730 """
717 """
731 Makes default test index.
718 Makes default test index.
732 """
719 """
733 import rc_testdata
720 import rc_testdata
734
721
735 rc_testdata.extract_search_index(
722 rc_testdata.extract_search_index(
736 'vcs_search_index', os.path.dirname(config['search.location']))
723 'vcs_search_index', os.path.dirname(config['search.location']))
737
724
738
725
739 def create_test_directory(test_path):
726 def create_test_directory(test_path):
740 """
727 """
741 Create test directory if it doesn't exist.
728 Create test directory if it doesn't exist.
742 """
729 """
743 if not os.path.isdir(test_path):
730 if not os.path.isdir(test_path):
744 log.debug('Creating testdir %s', test_path)
731 log.debug('Creating testdir %s', test_path)
745 os.makedirs(test_path)
732 os.makedirs(test_path)
746
733
747
734
748 def create_test_database(test_path, config):
735 def create_test_database(test_path, config):
749 """
736 """
750 Makes a fresh database.
737 Makes a fresh database.
751 """
738 """
752 from rhodecode.lib.db_manage import DbManage
739 from rhodecode.lib.db_manage import DbManage
753
740
754 # PART ONE create db
741 # PART ONE create db
755 dbconf = config['sqlalchemy.db1.url']
742 dbconf = config['sqlalchemy.db1.url']
756 log.debug('making test db %s', dbconf)
743 log.debug('making test db %s', dbconf)
757
744
758 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
745 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
759 tests=True, cli_args={'force_ask': True})
746 tests=True, cli_args={'force_ask': True})
760 dbmanage.create_tables(override=True)
747 dbmanage.create_tables(override=True)
761 dbmanage.set_db_version()
748 dbmanage.set_db_version()
762 # for tests dynamically set new root paths based on generated content
749 # for tests dynamically set new root paths based on generated content
763 dbmanage.create_settings(dbmanage.config_prompt(test_path))
750 dbmanage.create_settings(dbmanage.config_prompt(test_path))
764 dbmanage.create_default_user()
751 dbmanage.create_default_user()
765 dbmanage.create_test_admin_and_users()
752 dbmanage.create_test_admin_and_users()
766 dbmanage.create_permissions()
753 dbmanage.create_permissions()
767 dbmanage.populate_default_permissions()
754 dbmanage.populate_default_permissions()
768 Session().commit()
755 Session().commit()
769
756
770
757
771 def create_test_repositories(test_path, config):
758 def create_test_repositories(test_path, config):
772 """
759 """
773 Creates test repositories in the temporary directory. Repositories are
760 Creates test repositories in the temporary directory. Repositories are
774 extracted from archives within the rc_testdata package.
761 extracted from archives within the rc_testdata package.
775 """
762 """
776 import rc_testdata
763 import rc_testdata
777 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
764 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
778
765
779 log.debug('making test vcs repositories')
766 log.debug('making test vcs repositories')
780
767
781 idx_path = config['search.location']
768 idx_path = config['search.location']
782 data_path = config['cache_dir']
769 data_path = config['cache_dir']
783
770
784 # clean index and data
771 # clean index and data
785 if idx_path and os.path.exists(idx_path):
772 if idx_path and os.path.exists(idx_path):
786 log.debug('remove %s', idx_path)
773 log.debug('remove %s', idx_path)
787 shutil.rmtree(idx_path)
774 shutil.rmtree(idx_path)
788
775
789 if data_path and os.path.exists(data_path):
776 if data_path and os.path.exists(data_path):
790 log.debug('remove %s', data_path)
777 log.debug('remove %s', data_path)
791 shutil.rmtree(data_path)
778 shutil.rmtree(data_path)
792
779
793 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
780 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
794 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
781 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
795
782
796 # Note: Subversion is in the process of being integrated with the system,
783 # Note: Subversion is in the process of being integrated with the system,
797 # until we have a properly packed version of the test svn repository, this
784 # until we have a properly packed version of the test svn repository, this
798 # tries to copy over the repo from a package "rc_testdata"
785 # tries to copy over the repo from a package "rc_testdata"
799 svn_repo_path = rc_testdata.get_svn_repo_archive()
786 svn_repo_path = rc_testdata.get_svn_repo_archive()
800 with tarfile.open(svn_repo_path) as tar:
787 with tarfile.open(svn_repo_path) as tar:
801 tar.extractall(jn(test_path, SVN_REPO))
788 tar.extractall(jn(test_path, SVN_REPO))
802
789
803
790
804 #==============================================================================
791 #==============================================================================
805 # PASTER COMMANDS
792 # PASTER COMMANDS
806 #==============================================================================
793 #==============================================================================
807 class BasePasterCommand(Command):
794 class BasePasterCommand(Command):
808 """
795 """
809 Abstract Base Class for paster commands.
796 Abstract Base Class for paster commands.
810
797
811 The celery commands are somewhat aggressive about loading
798 The celery commands are somewhat aggressive about loading
812 celery.conf, and since our module sets the `CELERY_LOADER`
799 celery.conf, and since our module sets the `CELERY_LOADER`
813 environment variable to our loader, we have to bootstrap a bit and
800 environment variable to our loader, we have to bootstrap a bit and
814 make sure we've had a chance to load the pylons config off of the
801 make sure we've had a chance to load the pylons config off of the
815 command line, otherwise everything fails.
802 command line, otherwise everything fails.
816 """
803 """
817 min_args = 1
804 min_args = 1
818 min_args_error = "Please provide a paster config file as an argument."
805 min_args_error = "Please provide a paster config file as an argument."
819 takes_config_file = 1
806 takes_config_file = 1
820 requires_config_file = True
807 requires_config_file = True
821
808
822 def notify_msg(self, msg, log=False):
809 def notify_msg(self, msg, log=False):
823 """Make a notification to user, additionally if logger is passed
810 """Make a notification to user, additionally if logger is passed
824 it logs this action using given logger
811 it logs this action using given logger
825
812
826 :param msg: message that will be printed to user
813 :param msg: message that will be printed to user
827 :param log: logging instance, to use to additionally log this message
814 :param log: logging instance, to use to additionally log this message
828
815
829 """
816 """
830 if log and isinstance(log, logging):
817 if log and isinstance(log, logging):
831 log(msg)
818 log(msg)
832
819
833 def run(self, args):
820 def run(self, args):
834 """
821 """
835 Overrides Command.run
822 Overrides Command.run
836
823
837 Checks for a config file argument and loads it.
824 Checks for a config file argument and loads it.
838 """
825 """
839 if len(args) < self.min_args:
826 if len(args) < self.min_args:
840 raise BadCommand(
827 raise BadCommand(
841 self.min_args_error % {'min_args': self.min_args,
828 self.min_args_error % {'min_args': self.min_args,
842 'actual_args': len(args)})
829 'actual_args': len(args)})
843
830
844 # Decrement because we're going to lob off the first argument.
831 # Decrement because we're going to lob off the first argument.
845 # @@ This is hacky
832 # @@ This is hacky
846 self.min_args -= 1
833 self.min_args -= 1
847 self.bootstrap_config(args[0])
834 self.bootstrap_config(args[0])
848 self.update_parser()
835 self.update_parser()
849 return super(BasePasterCommand, self).run(args[1:])
836 return super(BasePasterCommand, self).run(args[1:])
850
837
851 def update_parser(self):
838 def update_parser(self):
852 """
839 """
853 Abstract method. Allows for the class' parser to be updated
840 Abstract method. Allows for the class' parser to be updated
854 before the superclass' `run` method is called. Necessary to
841 before the superclass' `run` method is called. Necessary to
855 allow options/arguments to be passed through to the underlying
842 allow options/arguments to be passed through to the underlying
856 celery command.
843 celery command.
857 """
844 """
858 raise NotImplementedError("Abstract Method.")
845 raise NotImplementedError("Abstract Method.")
859
846
860 def bootstrap_config(self, conf):
847 def bootstrap_config(self, conf):
861 """
848 """
862 Loads the pylons configuration.
849 Loads the pylons configuration.
863 """
850 """
864 from pylons import config as pylonsconfig
851 from pylons import config as pylonsconfig
865
852
866 self.path_to_ini_file = os.path.realpath(conf)
853 self.path_to_ini_file = os.path.realpath(conf)
867 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
854 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
868 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
855 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
869
856
870 def _init_session(self):
857 def _init_session(self):
871 """
858 """
872 Inits SqlAlchemy Session
859 Inits SqlAlchemy Session
873 """
860 """
874 logging.config.fileConfig(self.path_to_ini_file)
861 logging.config.fileConfig(self.path_to_ini_file)
875 from pylons import config
862 from pylons import config
876 from rhodecode.config.utils import initialize_database
863 from rhodecode.config.utils import initialize_database
877
864
878 # get to remove repos !!
865 # get to remove repos !!
879 add_cache(config)
866 add_cache(config)
880 initialize_database(config)
867 initialize_database(config)
881
868
882
869
883 @decorator.decorator
870 @decorator.decorator
884 def jsonify(func, *args, **kwargs):
871 def jsonify(func, *args, **kwargs):
885 """Action decorator that formats output for JSON
872 """Action decorator that formats output for JSON
886
873
887 Given a function that will return content, this decorator will turn
874 Given a function that will return content, this decorator will turn
888 the result into JSON, with a content-type of 'application/json' and
875 the result into JSON, with a content-type of 'application/json' and
889 output it.
876 output it.
890
877
891 """
878 """
892 from pylons.decorators.util import get_pylons
879 from pylons.decorators.util import get_pylons
893 from rhodecode.lib.ext_json import json
880 from rhodecode.lib.ext_json import json
894 pylons = get_pylons(args)
881 pylons = get_pylons(args)
895 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
882 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
896 data = func(*args, **kwargs)
883 data = func(*args, **kwargs)
897 if isinstance(data, (list, tuple)):
884 if isinstance(data, (list, tuple)):
898 msg = "JSON responses with Array envelopes are susceptible to " \
885 msg = "JSON responses with Array envelopes are susceptible to " \
899 "cross-site data leak attacks, see " \
886 "cross-site data leak attacks, see " \
900 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
887 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
901 warnings.warn(msg, Warning, 2)
888 warnings.warn(msg, Warning, 2)
902 log.warning(msg)
889 log.warning(msg)
903 log.debug("Returning JSON wrapped action output")
890 log.debug("Returning JSON wrapped action output")
904 return json.dumps(data, encoding='utf-8')
891 return json.dumps(data, encoding='utf-8')
905
892
906
893
907 class PartialRenderer(object):
894 class PartialRenderer(object):
908 """
895 """
909 Partial renderer used to render chunks of html used in datagrids
896 Partial renderer used to render chunks of html used in datagrids
910 use like::
897 use like::
911
898
912 _render = PartialRenderer('data_table/_dt_elements.html')
899 _render = PartialRenderer('data_table/_dt_elements.html')
913 _render('quick_menu', args, kwargs)
900 _render('quick_menu', args, kwargs)
914 PartialRenderer.h,
901 PartialRenderer.h,
915 c,
902 c,
916 _,
903 _,
917 ungettext
904 ungettext
918 are the template stuff initialized inside and can be re-used later
905 are the template stuff initialized inside and can be re-used later
919
906
920 :param tmpl_name: template path relate to /templates/ dir
907 :param tmpl_name: template path relate to /templates/ dir
921 """
908 """
922
909
923 def __init__(self, tmpl_name):
910 def __init__(self, tmpl_name):
924 import rhodecode
911 import rhodecode
925 from pylons import request, tmpl_context as c
912 from pylons import request, tmpl_context as c
926 from pylons.i18n.translation import _, ungettext
913 from pylons.i18n.translation import _, ungettext
927 from rhodecode.lib import helpers as h
914 from rhodecode.lib import helpers as h
928
915
929 self.tmpl_name = tmpl_name
916 self.tmpl_name = tmpl_name
930 self.rhodecode = rhodecode
917 self.rhodecode = rhodecode
931 self.c = c
918 self.c = c
932 self._ = _
919 self._ = _
933 self.ungettext = ungettext
920 self.ungettext = ungettext
934 self.h = h
921 self.h = h
935 self.request = request
922 self.request = request
936
923
937 def _mako_lookup(self):
924 def _mako_lookup(self):
938 _tmpl_lookup = self.rhodecode.CONFIG['pylons.app_globals'].mako_lookup
925 _tmpl_lookup = self.rhodecode.CONFIG['pylons.app_globals'].mako_lookup
939 return _tmpl_lookup.get_template(self.tmpl_name)
926 return _tmpl_lookup.get_template(self.tmpl_name)
940
927
941 def _update_kwargs_for_render(self, kwargs):
928 def _update_kwargs_for_render(self, kwargs):
942 """
929 """
943 Inject params required for Mako rendering
930 Inject params required for Mako rendering
944 """
931 """
945 _kwargs = {
932 _kwargs = {
946 '_': self._,
933 '_': self._,
947 'h': self.h,
934 'h': self.h,
948 'c': self.c,
935 'c': self.c,
949 'request': self.request,
936 'request': self.request,
950 'ungettext': self.ungettext,
937 'ungettext': self.ungettext,
951 }
938 }
952 _kwargs.update(kwargs)
939 _kwargs.update(kwargs)
953 return _kwargs
940 return _kwargs
954
941
955 def _render_with_exc(self, render_func, args, kwargs):
942 def _render_with_exc(self, render_func, args, kwargs):
956 try:
943 try:
957 return render_func.render(*args, **kwargs)
944 return render_func.render(*args, **kwargs)
958 except:
945 except:
959 log.error(exceptions.text_error_template().render())
946 log.error(exceptions.text_error_template().render())
960 raise
947 raise
961
948
962 def _get_template(self, template_obj, def_name):
949 def _get_template(self, template_obj, def_name):
963 if def_name:
950 if def_name:
964 tmpl = template_obj.get_def(def_name)
951 tmpl = template_obj.get_def(def_name)
965 else:
952 else:
966 tmpl = template_obj
953 tmpl = template_obj
967 return tmpl
954 return tmpl
968
955
969 def render(self, def_name, *args, **kwargs):
956 def render(self, def_name, *args, **kwargs):
970 lookup_obj = self._mako_lookup()
957 lookup_obj = self._mako_lookup()
971 tmpl = self._get_template(lookup_obj, def_name=def_name)
958 tmpl = self._get_template(lookup_obj, def_name=def_name)
972 kwargs = self._update_kwargs_for_render(kwargs)
959 kwargs = self._update_kwargs_for_render(kwargs)
973 return self._render_with_exc(tmpl, args, kwargs)
960 return self._render_with_exc(tmpl, args, kwargs)
974
961
975 def __call__(self, tmpl, *args, **kwargs):
962 def __call__(self, tmpl, *args, **kwargs):
976 return self.render(tmpl, *args, **kwargs)
963 return self.render(tmpl, *args, **kwargs)
977
964
978
965
979 def password_changed(auth_user, session):
966 def password_changed(auth_user, session):
980 # Never report password change in case of default user or anonymous user.
967 # Never report password change in case of default user or anonymous user.
981 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
968 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
982 return False
969 return False
983
970
984 password_hash = md5(auth_user.password) if auth_user.password else None
971 password_hash = md5(auth_user.password) if auth_user.password else None
985 rhodecode_user = session.get('rhodecode_user', {})
972 rhodecode_user = session.get('rhodecode_user', {})
986 session_password_hash = rhodecode_user.get('password', '')
973 session_password_hash = rhodecode_user.get('password', '')
987 return password_hash != session_password_hash
974 return password_hash != session_password_hash
988
975
989
976
990 def read_opensource_licenses():
977 def read_opensource_licenses():
991 global _license_cache
978 global _license_cache
992
979
993 if not _license_cache:
980 if not _license_cache:
994 licenses = pkg_resources.resource_string(
981 licenses = pkg_resources.resource_string(
995 'rhodecode', 'config/licenses.json')
982 'rhodecode', 'config/licenses.json')
996 _license_cache = json.loads(licenses)
983 _license_cache = json.loads(licenses)
997
984
998 return _license_cache
985 return _license_cache
999
986
1000
987
1001 def get_registry(request):
988 def get_registry(request):
1002 """
989 """
1003 Utility to get the pyramid registry from a request. During migration to
990 Utility to get the pyramid registry from a request. During migration to
1004 pyramid we sometimes want to use the pyramid registry from pylons context.
991 pyramid we sometimes want to use the pyramid registry from pylons context.
1005 Therefore this utility returns `request.registry` for pyramid requests and
992 Therefore this utility returns `request.registry` for pyramid requests and
1006 uses `get_current_registry()` for pylons requests.
993 uses `get_current_registry()` for pylons requests.
1007 """
994 """
1008 try:
995 try:
1009 return request.registry
996 return request.registry
1010 except AttributeError:
997 except AttributeError:
1011 return get_current_registry()
998 return get_current_registry()
General Comments 0
You need to be logged in to leave comments. Login now