##// END OF EJS Templates
auth-token: expose fetched token in unified way into request attribute....
marcink -
r4002:5f150e86 default
parent child Browse files
Show More
@@ -1,592 +1,597 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 The base Controller API
22 The base Controller API
23 Provides the BaseController class for subclassing. And usage in different
23 Provides the BaseController class for subclassing. And usage in different
24 controllers
24 controllers
25 """
25 """
26
26
27 import logging
27 import logging
28 import socket
28 import socket
29
29
30 import markupsafe
30 import markupsafe
31 import ipaddress
31 import ipaddress
32
32
33 from paste.auth.basic import AuthBasicAuthenticator
33 from paste.auth.basic import AuthBasicAuthenticator
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
36
36
37 import rhodecode
37 import rhodecode
38 from rhodecode.apps._base import TemplateArgs
38 from rhodecode.apps._base import TemplateArgs
39 from rhodecode.authentication.base import VCS_TYPE
39 from rhodecode.authentication.base import VCS_TYPE
40 from rhodecode.lib import auth, utils2
40 from rhodecode.lib import auth, utils2
41 from rhodecode.lib import helpers as h
41 from rhodecode.lib import helpers as h
42 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
42 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
43 from rhodecode.lib.exceptions import UserCreationError
43 from rhodecode.lib.exceptions import UserCreationError
44 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
44 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
45 from rhodecode.lib.utils2 import (
45 from rhodecode.lib.utils2 import (
46 str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str)
46 str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str)
47 from rhodecode.model.db import Repository, User, ChangesetComment, UserBookmark
47 from rhodecode.model.db import Repository, User, ChangesetComment, UserBookmark
48 from rhodecode.model.notification import NotificationModel
48 from rhodecode.model.notification import NotificationModel
49 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
49 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 def _filter_proxy(ip):
54 def _filter_proxy(ip):
55 """
55 """
56 Passed in IP addresses in HEADERS can be in a special format of multiple
56 Passed in IP addresses in HEADERS can be in a special format of multiple
57 ips. Those comma separated IPs are passed from various proxies in the
57 ips. Those comma separated IPs are passed from various proxies in the
58 chain of request processing. The left-most being the original client.
58 chain of request processing. The left-most being the original client.
59 We only care about the first IP which came from the org. client.
59 We only care about the first IP which came from the org. client.
60
60
61 :param ip: ip string from headers
61 :param ip: ip string from headers
62 """
62 """
63 if ',' in ip:
63 if ',' in ip:
64 _ips = ip.split(',')
64 _ips = ip.split(',')
65 _first_ip = _ips[0].strip()
65 _first_ip = _ips[0].strip()
66 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
66 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
67 return _first_ip
67 return _first_ip
68 return ip
68 return ip
69
69
70
70
71 def _filter_port(ip):
71 def _filter_port(ip):
72 """
72 """
73 Removes a port from ip, there are 4 main cases to handle here.
73 Removes a port from ip, there are 4 main cases to handle here.
74 - ipv4 eg. 127.0.0.1
74 - ipv4 eg. 127.0.0.1
75 - ipv6 eg. ::1
75 - ipv6 eg. ::1
76 - ipv4+port eg. 127.0.0.1:8080
76 - ipv4+port eg. 127.0.0.1:8080
77 - ipv6+port eg. [::1]:8080
77 - ipv6+port eg. [::1]:8080
78
78
79 :param ip:
79 :param ip:
80 """
80 """
81 def is_ipv6(ip_addr):
81 def is_ipv6(ip_addr):
82 if hasattr(socket, 'inet_pton'):
82 if hasattr(socket, 'inet_pton'):
83 try:
83 try:
84 socket.inet_pton(socket.AF_INET6, ip_addr)
84 socket.inet_pton(socket.AF_INET6, ip_addr)
85 except socket.error:
85 except socket.error:
86 return False
86 return False
87 else:
87 else:
88 # fallback to ipaddress
88 # fallback to ipaddress
89 try:
89 try:
90 ipaddress.IPv6Address(safe_unicode(ip_addr))
90 ipaddress.IPv6Address(safe_unicode(ip_addr))
91 except Exception:
91 except Exception:
92 return False
92 return False
93 return True
93 return True
94
94
95 if ':' not in ip: # must be ipv4 pure ip
95 if ':' not in ip: # must be ipv4 pure ip
96 return ip
96 return ip
97
97
98 if '[' in ip and ']' in ip: # ipv6 with port
98 if '[' in ip and ']' in ip: # ipv6 with port
99 return ip.split(']')[0][1:].lower()
99 return ip.split(']')[0][1:].lower()
100
100
101 # must be ipv6 or ipv4 with port
101 # must be ipv6 or ipv4 with port
102 if is_ipv6(ip):
102 if is_ipv6(ip):
103 return ip
103 return ip
104 else:
104 else:
105 ip, _port = ip.split(':')[:2] # means ipv4+port
105 ip, _port = ip.split(':')[:2] # means ipv4+port
106 return ip
106 return ip
107
107
108
108
109 def get_ip_addr(environ):
109 def get_ip_addr(environ):
110 proxy_key = 'HTTP_X_REAL_IP'
110 proxy_key = 'HTTP_X_REAL_IP'
111 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
111 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
112 def_key = 'REMOTE_ADDR'
112 def_key = 'REMOTE_ADDR'
113 _filters = lambda x: _filter_port(_filter_proxy(x))
113 _filters = lambda x: _filter_port(_filter_proxy(x))
114
114
115 ip = environ.get(proxy_key)
115 ip = environ.get(proxy_key)
116 if ip:
116 if ip:
117 return _filters(ip)
117 return _filters(ip)
118
118
119 ip = environ.get(proxy_key2)
119 ip = environ.get(proxy_key2)
120 if ip:
120 if ip:
121 return _filters(ip)
121 return _filters(ip)
122
122
123 ip = environ.get(def_key, '0.0.0.0')
123 ip = environ.get(def_key, '0.0.0.0')
124 return _filters(ip)
124 return _filters(ip)
125
125
126
126
127 def get_server_ip_addr(environ, log_errors=True):
127 def get_server_ip_addr(environ, log_errors=True):
128 hostname = environ.get('SERVER_NAME')
128 hostname = environ.get('SERVER_NAME')
129 try:
129 try:
130 return socket.gethostbyname(hostname)
130 return socket.gethostbyname(hostname)
131 except Exception as e:
131 except Exception as e:
132 if log_errors:
132 if log_errors:
133 # in some cases this lookup is not possible, and we don't want to
133 # in some cases this lookup is not possible, and we don't want to
134 # make it an exception in logs
134 # make it an exception in logs
135 log.exception('Could not retrieve server ip address: %s', e)
135 log.exception('Could not retrieve server ip address: %s', e)
136 return hostname
136 return hostname
137
137
138
138
139 def get_server_port(environ):
139 def get_server_port(environ):
140 return environ.get('SERVER_PORT')
140 return environ.get('SERVER_PORT')
141
141
142
142
143 def get_access_path(environ):
143 def get_access_path(environ):
144 path = environ.get('PATH_INFO')
144 path = environ.get('PATH_INFO')
145 org_req = environ.get('pylons.original_request')
145 org_req = environ.get('pylons.original_request')
146 if org_req:
146 if org_req:
147 path = org_req.environ.get('PATH_INFO')
147 path = org_req.environ.get('PATH_INFO')
148 return path
148 return path
149
149
150
150
151 def get_user_agent(environ):
151 def get_user_agent(environ):
152 return environ.get('HTTP_USER_AGENT')
152 return environ.get('HTTP_USER_AGENT')
153
153
154
154
155 def vcs_operation_context(
155 def vcs_operation_context(
156 environ, repo_name, username, action, scm, check_locking=True,
156 environ, repo_name, username, action, scm, check_locking=True,
157 is_shadow_repo=False, check_branch_perms=False, detect_force_push=False):
157 is_shadow_repo=False, check_branch_perms=False, detect_force_push=False):
158 """
158 """
159 Generate the context for a vcs operation, e.g. push or pull.
159 Generate the context for a vcs operation, e.g. push or pull.
160
160
161 This context is passed over the layers so that hooks triggered by the
161 This context is passed over the layers so that hooks triggered by the
162 vcs operation know details like the user, the user's IP address etc.
162 vcs operation know details like the user, the user's IP address etc.
163
163
164 :param check_locking: Allows to switch of the computation of the locking
164 :param check_locking: Allows to switch of the computation of the locking
165 data. This serves mainly the need of the simplevcs middleware to be
165 data. This serves mainly the need of the simplevcs middleware to be
166 able to disable this for certain operations.
166 able to disable this for certain operations.
167
167
168 """
168 """
169 # Tri-state value: False: unlock, None: nothing, True: lock
169 # Tri-state value: False: unlock, None: nothing, True: lock
170 make_lock = None
170 make_lock = None
171 locked_by = [None, None, None]
171 locked_by = [None, None, None]
172 is_anonymous = username == User.DEFAULT_USER
172 is_anonymous = username == User.DEFAULT_USER
173 user = User.get_by_username(username)
173 user = User.get_by_username(username)
174 if not is_anonymous and check_locking:
174 if not is_anonymous and check_locking:
175 log.debug('Checking locking on repository "%s"', repo_name)
175 log.debug('Checking locking on repository "%s"', repo_name)
176 repo = Repository.get_by_repo_name(repo_name)
176 repo = Repository.get_by_repo_name(repo_name)
177 make_lock, __, locked_by = repo.get_locking_state(
177 make_lock, __, locked_by = repo.get_locking_state(
178 action, user.user_id)
178 action, user.user_id)
179 user_id = user.user_id
179 user_id = user.user_id
180 settings_model = VcsSettingsModel(repo=repo_name)
180 settings_model = VcsSettingsModel(repo=repo_name)
181 ui_settings = settings_model.get_ui_settings()
181 ui_settings = settings_model.get_ui_settings()
182
182
183 # NOTE(marcink): This should be also in sync with
183 # NOTE(marcink): This should be also in sync with
184 # rhodecode/apps/ssh_support/lib/backends/base.py:update_environment scm_data
184 # rhodecode/apps/ssh_support/lib/backends/base.py:update_environment scm_data
185 store = [x for x in ui_settings if x.key == '/']
185 store = [x for x in ui_settings if x.key == '/']
186 repo_store = ''
186 repo_store = ''
187 if store:
187 if store:
188 repo_store = store[0].value
188 repo_store = store[0].value
189
189
190 scm_data = {
190 scm_data = {
191 'ip': get_ip_addr(environ),
191 'ip': get_ip_addr(environ),
192 'username': username,
192 'username': username,
193 'user_id': user_id,
193 'user_id': user_id,
194 'action': action,
194 'action': action,
195 'repository': repo_name,
195 'repository': repo_name,
196 'scm': scm,
196 'scm': scm,
197 'config': rhodecode.CONFIG['__file__'],
197 'config': rhodecode.CONFIG['__file__'],
198 'repo_store': repo_store,
198 'repo_store': repo_store,
199 'make_lock': make_lock,
199 'make_lock': make_lock,
200 'locked_by': locked_by,
200 'locked_by': locked_by,
201 'server_url': utils2.get_server_url(environ),
201 'server_url': utils2.get_server_url(environ),
202 'user_agent': get_user_agent(environ),
202 'user_agent': get_user_agent(environ),
203 'hooks': get_enabled_hook_classes(ui_settings),
203 'hooks': get_enabled_hook_classes(ui_settings),
204 'is_shadow_repo': is_shadow_repo,
204 'is_shadow_repo': is_shadow_repo,
205 'detect_force_push': detect_force_push,
205 'detect_force_push': detect_force_push,
206 'check_branch_perms': check_branch_perms,
206 'check_branch_perms': check_branch_perms,
207 }
207 }
208 return scm_data
208 return scm_data
209
209
210
210
211 class BasicAuth(AuthBasicAuthenticator):
211 class BasicAuth(AuthBasicAuthenticator):
212
212
213 def __init__(self, realm, authfunc, registry, auth_http_code=None,
213 def __init__(self, realm, authfunc, registry, auth_http_code=None,
214 initial_call_detection=False, acl_repo_name=None):
214 initial_call_detection=False, acl_repo_name=None):
215 self.realm = realm
215 self.realm = realm
216 self.initial_call = initial_call_detection
216 self.initial_call = initial_call_detection
217 self.authfunc = authfunc
217 self.authfunc = authfunc
218 self.registry = registry
218 self.registry = registry
219 self.acl_repo_name = acl_repo_name
219 self.acl_repo_name = acl_repo_name
220 self._rc_auth_http_code = auth_http_code
220 self._rc_auth_http_code = auth_http_code
221
221
222 def _get_response_from_code(self, http_code):
222 def _get_response_from_code(self, http_code):
223 try:
223 try:
224 return get_exception(safe_int(http_code))
224 return get_exception(safe_int(http_code))
225 except Exception:
225 except Exception:
226 log.exception('Failed to fetch response for code %s', http_code)
226 log.exception('Failed to fetch response for code %s', http_code)
227 return HTTPForbidden
227 return HTTPForbidden
228
228
229 def get_rc_realm(self):
229 def get_rc_realm(self):
230 return safe_str(self.registry.rhodecode_settings.get('rhodecode_realm'))
230 return safe_str(self.registry.rhodecode_settings.get('rhodecode_realm'))
231
231
232 def build_authentication(self):
232 def build_authentication(self):
233 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
233 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
234 if self._rc_auth_http_code and not self.initial_call:
234 if self._rc_auth_http_code and not self.initial_call:
235 # return alternative HTTP code if alternative http return code
235 # return alternative HTTP code if alternative http return code
236 # is specified in RhodeCode config, but ONLY if it's not the
236 # is specified in RhodeCode config, but ONLY if it's not the
237 # FIRST call
237 # FIRST call
238 custom_response_klass = self._get_response_from_code(
238 custom_response_klass = self._get_response_from_code(
239 self._rc_auth_http_code)
239 self._rc_auth_http_code)
240 return custom_response_klass(headers=head)
240 return custom_response_klass(headers=head)
241 return HTTPUnauthorized(headers=head)
241 return HTTPUnauthorized(headers=head)
242
242
243 def authenticate(self, environ):
243 def authenticate(self, environ):
244 authorization = AUTHORIZATION(environ)
244 authorization = AUTHORIZATION(environ)
245 if not authorization:
245 if not authorization:
246 return self.build_authentication()
246 return self.build_authentication()
247 (authmeth, auth) = authorization.split(' ', 1)
247 (authmeth, auth) = authorization.split(' ', 1)
248 if 'basic' != authmeth.lower():
248 if 'basic' != authmeth.lower():
249 return self.build_authentication()
249 return self.build_authentication()
250 auth = auth.strip().decode('base64')
250 auth = auth.strip().decode('base64')
251 _parts = auth.split(':', 1)
251 _parts = auth.split(':', 1)
252 if len(_parts) == 2:
252 if len(_parts) == 2:
253 username, password = _parts
253 username, password = _parts
254 auth_data = self.authfunc(
254 auth_data = self.authfunc(
255 username, password, environ, VCS_TYPE,
255 username, password, environ, VCS_TYPE,
256 registry=self.registry, acl_repo_name=self.acl_repo_name)
256 registry=self.registry, acl_repo_name=self.acl_repo_name)
257 if auth_data:
257 if auth_data:
258 return {'username': username, 'auth_data': auth_data}
258 return {'username': username, 'auth_data': auth_data}
259 if username and password:
259 if username and password:
260 # we mark that we actually executed authentication once, at
260 # we mark that we actually executed authentication once, at
261 # that point we can use the alternative auth code
261 # that point we can use the alternative auth code
262 self.initial_call = False
262 self.initial_call = False
263
263
264 return self.build_authentication()
264 return self.build_authentication()
265
265
266 __call__ = authenticate
266 __call__ = authenticate
267
267
268
268
269 def calculate_version_hash(config):
269 def calculate_version_hash(config):
270 return sha1(
270 return sha1(
271 config.get('beaker.session.secret', '') +
271 config.get('beaker.session.secret', '') +
272 rhodecode.__version__)[:8]
272 rhodecode.__version__)[:8]
273
273
274
274
275 def get_current_lang(request):
275 def get_current_lang(request):
276 # NOTE(marcink): remove after pyramid move
276 # NOTE(marcink): remove after pyramid move
277 try:
277 try:
278 return translation.get_lang()[0]
278 return translation.get_lang()[0]
279 except:
279 except:
280 pass
280 pass
281
281
282 return getattr(request, '_LOCALE_', request.locale_name)
282 return getattr(request, '_LOCALE_', request.locale_name)
283
283
284
284
285 def attach_context_attributes(context, request, user_id=None):
285 def attach_context_attributes(context, request, user_id=None):
286 """
286 """
287 Attach variables into template context called `c`.
287 Attach variables into template context called `c`.
288 """
288 """
289 config = request.registry.settings
289 config = request.registry.settings
290
290
291 rc_config = SettingsModel().get_all_settings(cache=True)
291 rc_config = SettingsModel().get_all_settings(cache=True)
292 context.rc_config = rc_config
292 context.rc_config = rc_config
293 context.rhodecode_version = rhodecode.__version__
293 context.rhodecode_version = rhodecode.__version__
294 context.rhodecode_edition = config.get('rhodecode.edition')
294 context.rhodecode_edition = config.get('rhodecode.edition')
295 # unique secret + version does not leak the version but keep consistency
295 # unique secret + version does not leak the version but keep consistency
296 context.rhodecode_version_hash = calculate_version_hash(config)
296 context.rhodecode_version_hash = calculate_version_hash(config)
297
297
298 # Default language set for the incoming request
298 # Default language set for the incoming request
299 context.language = get_current_lang(request)
299 context.language = get_current_lang(request)
300
300
301 # Visual options
301 # Visual options
302 context.visual = AttributeDict({})
302 context.visual = AttributeDict({})
303
303
304 # DB stored Visual Items
304 # DB stored Visual Items
305 context.visual.show_public_icon = str2bool(
305 context.visual.show_public_icon = str2bool(
306 rc_config.get('rhodecode_show_public_icon'))
306 rc_config.get('rhodecode_show_public_icon'))
307 context.visual.show_private_icon = str2bool(
307 context.visual.show_private_icon = str2bool(
308 rc_config.get('rhodecode_show_private_icon'))
308 rc_config.get('rhodecode_show_private_icon'))
309 context.visual.stylify_metatags = str2bool(
309 context.visual.stylify_metatags = str2bool(
310 rc_config.get('rhodecode_stylify_metatags'))
310 rc_config.get('rhodecode_stylify_metatags'))
311 context.visual.dashboard_items = safe_int(
311 context.visual.dashboard_items = safe_int(
312 rc_config.get('rhodecode_dashboard_items', 100))
312 rc_config.get('rhodecode_dashboard_items', 100))
313 context.visual.admin_grid_items = safe_int(
313 context.visual.admin_grid_items = safe_int(
314 rc_config.get('rhodecode_admin_grid_items', 100))
314 rc_config.get('rhodecode_admin_grid_items', 100))
315 context.visual.show_revision_number = str2bool(
315 context.visual.show_revision_number = str2bool(
316 rc_config.get('rhodecode_show_revision_number', True))
316 rc_config.get('rhodecode_show_revision_number', True))
317 context.visual.show_sha_length = safe_int(
317 context.visual.show_sha_length = safe_int(
318 rc_config.get('rhodecode_show_sha_length', 100))
318 rc_config.get('rhodecode_show_sha_length', 100))
319 context.visual.repository_fields = str2bool(
319 context.visual.repository_fields = str2bool(
320 rc_config.get('rhodecode_repository_fields'))
320 rc_config.get('rhodecode_repository_fields'))
321 context.visual.show_version = str2bool(
321 context.visual.show_version = str2bool(
322 rc_config.get('rhodecode_show_version'))
322 rc_config.get('rhodecode_show_version'))
323 context.visual.use_gravatar = str2bool(
323 context.visual.use_gravatar = str2bool(
324 rc_config.get('rhodecode_use_gravatar'))
324 rc_config.get('rhodecode_use_gravatar'))
325 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
325 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
326 context.visual.default_renderer = rc_config.get(
326 context.visual.default_renderer = rc_config.get(
327 'rhodecode_markup_renderer', 'rst')
327 'rhodecode_markup_renderer', 'rst')
328 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
328 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
329 context.visual.rhodecode_support_url = \
329 context.visual.rhodecode_support_url = \
330 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
330 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
331
331
332 context.visual.affected_files_cut_off = 60
332 context.visual.affected_files_cut_off = 60
333
333
334 context.pre_code = rc_config.get('rhodecode_pre_code')
334 context.pre_code = rc_config.get('rhodecode_pre_code')
335 context.post_code = rc_config.get('rhodecode_post_code')
335 context.post_code = rc_config.get('rhodecode_post_code')
336 context.rhodecode_name = rc_config.get('rhodecode_title')
336 context.rhodecode_name = rc_config.get('rhodecode_title')
337 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
337 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
338 # if we have specified default_encoding in the request, it has more
338 # if we have specified default_encoding in the request, it has more
339 # priority
339 # priority
340 if request.GET.get('default_encoding'):
340 if request.GET.get('default_encoding'):
341 context.default_encodings.insert(0, request.GET.get('default_encoding'))
341 context.default_encodings.insert(0, request.GET.get('default_encoding'))
342 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
342 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
343 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
343 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
344
344
345 # INI stored
345 # INI stored
346 context.labs_active = str2bool(
346 context.labs_active = str2bool(
347 config.get('labs_settings_active', 'false'))
347 config.get('labs_settings_active', 'false'))
348 context.ssh_enabled = str2bool(
348 context.ssh_enabled = str2bool(
349 config.get('ssh.generate_authorized_keyfile', 'false'))
349 config.get('ssh.generate_authorized_keyfile', 'false'))
350 context.ssh_key_generator_enabled = str2bool(
350 context.ssh_key_generator_enabled = str2bool(
351 config.get('ssh.enable_ui_key_generator', 'true'))
351 config.get('ssh.enable_ui_key_generator', 'true'))
352
352
353 context.visual.allow_repo_location_change = str2bool(
353 context.visual.allow_repo_location_change = str2bool(
354 config.get('allow_repo_location_change', True))
354 config.get('allow_repo_location_change', True))
355 context.visual.allow_custom_hooks_settings = str2bool(
355 context.visual.allow_custom_hooks_settings = str2bool(
356 config.get('allow_custom_hooks_settings', True))
356 config.get('allow_custom_hooks_settings', True))
357 context.debug_style = str2bool(config.get('debug_style', False))
357 context.debug_style = str2bool(config.get('debug_style', False))
358
358
359 context.rhodecode_instanceid = config.get('instance_id')
359 context.rhodecode_instanceid = config.get('instance_id')
360
360
361 context.visual.cut_off_limit_diff = safe_int(
361 context.visual.cut_off_limit_diff = safe_int(
362 config.get('cut_off_limit_diff'))
362 config.get('cut_off_limit_diff'))
363 context.visual.cut_off_limit_file = safe_int(
363 context.visual.cut_off_limit_file = safe_int(
364 config.get('cut_off_limit_file'))
364 config.get('cut_off_limit_file'))
365
365
366 # AppEnlight
366 # AppEnlight
367 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
367 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
368 context.appenlight_api_public_key = config.get(
368 context.appenlight_api_public_key = config.get(
369 'appenlight.api_public_key', '')
369 'appenlight.api_public_key', '')
370 context.appenlight_server_url = config.get('appenlight.server_url', '')
370 context.appenlight_server_url = config.get('appenlight.server_url', '')
371
371
372 diffmode = {
372 diffmode = {
373 "unified": "unified",
373 "unified": "unified",
374 "sideside": "sideside"
374 "sideside": "sideside"
375 }.get(request.GET.get('diffmode'))
375 }.get(request.GET.get('diffmode'))
376
376
377 is_api = hasattr(request, 'rpc_user')
377 is_api = hasattr(request, 'rpc_user')
378 session_attrs = {
378 session_attrs = {
379 # defaults
379 # defaults
380 "clone_url_format": "http",
380 "clone_url_format": "http",
381 "diffmode": "sideside"
381 "diffmode": "sideside"
382 }
382 }
383
383
384 if not is_api:
384 if not is_api:
385 # don't access pyramid session for API calls
385 # don't access pyramid session for API calls
386 if diffmode and diffmode != request.session.get('rc_user_session_attr.diffmode'):
386 if diffmode and diffmode != request.session.get('rc_user_session_attr.diffmode'):
387 request.session['rc_user_session_attr.diffmode'] = diffmode
387 request.session['rc_user_session_attr.diffmode'] = diffmode
388
388
389 # session settings per user
389 # session settings per user
390
390
391 for k, v in request.session.items():
391 for k, v in request.session.items():
392 pref = 'rc_user_session_attr.'
392 pref = 'rc_user_session_attr.'
393 if k and k.startswith(pref):
393 if k and k.startswith(pref):
394 k = k[len(pref):]
394 k = k[len(pref):]
395 session_attrs[k] = v
395 session_attrs[k] = v
396
396
397 context.user_session_attrs = session_attrs
397 context.user_session_attrs = session_attrs
398
398
399 # JS template context
399 # JS template context
400 context.template_context = {
400 context.template_context = {
401 'repo_name': None,
401 'repo_name': None,
402 'repo_type': None,
402 'repo_type': None,
403 'repo_landing_commit': None,
403 'repo_landing_commit': None,
404 'rhodecode_user': {
404 'rhodecode_user': {
405 'username': None,
405 'username': None,
406 'email': None,
406 'email': None,
407 'notification_status': False
407 'notification_status': False
408 },
408 },
409 'session_attrs': session_attrs,
409 'session_attrs': session_attrs,
410 'visual': {
410 'visual': {
411 'default_renderer': None
411 'default_renderer': None
412 },
412 },
413 'commit_data': {
413 'commit_data': {
414 'commit_id': None
414 'commit_id': None
415 },
415 },
416 'pull_request_data': {'pull_request_id': None},
416 'pull_request_data': {'pull_request_id': None},
417 'timeago': {
417 'timeago': {
418 'refresh_time': 120 * 1000,
418 'refresh_time': 120 * 1000,
419 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
419 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
420 },
420 },
421 'pyramid_dispatch': {
421 'pyramid_dispatch': {
422
422
423 },
423 },
424 'extra': {'plugins': {}}
424 'extra': {'plugins': {}}
425 }
425 }
426 # END CONFIG VARS
426 # END CONFIG VARS
427 if is_api:
427 if is_api:
428 csrf_token = None
428 csrf_token = None
429 else:
429 else:
430 csrf_token = auth.get_csrf_token(session=request.session)
430 csrf_token = auth.get_csrf_token(session=request.session)
431
431
432 context.csrf_token = csrf_token
432 context.csrf_token = csrf_token
433 context.backends = rhodecode.BACKENDS.keys()
433 context.backends = rhodecode.BACKENDS.keys()
434 context.backends.sort()
434 context.backends.sort()
435 unread_count = 0
435 unread_count = 0
436 user_bookmark_list = []
436 user_bookmark_list = []
437 if user_id:
437 if user_id:
438 unread_count = NotificationModel().get_unread_cnt_for_user(user_id)
438 unread_count = NotificationModel().get_unread_cnt_for_user(user_id)
439 user_bookmark_list = UserBookmark.get_bookmarks_for_user(user_id)
439 user_bookmark_list = UserBookmark.get_bookmarks_for_user(user_id)
440 context.unread_notifications = unread_count
440 context.unread_notifications = unread_count
441 context.bookmark_items = user_bookmark_list
441 context.bookmark_items = user_bookmark_list
442
442
443 # web case
443 # web case
444 if hasattr(request, 'user'):
444 if hasattr(request, 'user'):
445 context.auth_user = request.user
445 context.auth_user = request.user
446 context.rhodecode_user = request.user
446 context.rhodecode_user = request.user
447
447
448 # api case
448 # api case
449 if hasattr(request, 'rpc_user'):
449 if hasattr(request, 'rpc_user'):
450 context.auth_user = request.rpc_user
450 context.auth_user = request.rpc_user
451 context.rhodecode_user = request.rpc_user
451 context.rhodecode_user = request.rpc_user
452
452
453 # attach the whole call context to the request
453 # attach the whole call context to the request
454 request.call_context = context
454 request.call_context = context
455
455
456
456
457 def get_auth_user(request):
457 def get_auth_user(request):
458 environ = request.environ
458 environ = request.environ
459 session = request.session
459 session = request.session
460
460
461 ip_addr = get_ip_addr(environ)
461 ip_addr = get_ip_addr(environ)
462
462 # make sure that we update permissions each time we call controller
463 # make sure that we update permissions each time we call controller
463 _auth_token = (request.GET.get('auth_token', '') or
464 _auth_token = (request.GET.get('auth_token', '') or request.GET.get('api_key', ''))
464 request.GET.get('api_key', ''))
465 if not _auth_token:
466 url_auth_token = request.matchdict.get('_auth_token')
467 _auth_token = url_auth_token
468 if _auth_token:
469 log.debug('Using URL extracted auth token `...%s`', _auth_token[-4:])
465
470
466 if _auth_token:
471 if _auth_token:
467 # when using API_KEY we assume user exists, and
472 # when using API_KEY we assume user exists, and
468 # doesn't need auth based on cookies.
473 # doesn't need auth based on cookies.
469 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
474 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
470 authenticated = False
475 authenticated = False
471 else:
476 else:
472 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
477 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
473 try:
478 try:
474 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
479 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
475 ip_addr=ip_addr)
480 ip_addr=ip_addr)
476 except UserCreationError as e:
481 except UserCreationError as e:
477 h.flash(e, 'error')
482 h.flash(e, 'error')
478 # container auth or other auth functions that create users
483 # container auth or other auth functions that create users
479 # on the fly can throw this exception signaling that there's
484 # on the fly can throw this exception signaling that there's
480 # issue with user creation, explanation should be provided
485 # issue with user creation, explanation should be provided
481 # in Exception itself. We then create a simple blank
486 # in Exception itself. We then create a simple blank
482 # AuthUser
487 # AuthUser
483 auth_user = AuthUser(ip_addr=ip_addr)
488 auth_user = AuthUser(ip_addr=ip_addr)
484
489
485 # in case someone changes a password for user it triggers session
490 # in case someone changes a password for user it triggers session
486 # flush and forces a re-login
491 # flush and forces a re-login
487 if password_changed(auth_user, session):
492 if password_changed(auth_user, session):
488 session.invalidate()
493 session.invalidate()
489 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
494 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
490 auth_user = AuthUser(ip_addr=ip_addr)
495 auth_user = AuthUser(ip_addr=ip_addr)
491
496
492 authenticated = cookie_store.get('is_authenticated')
497 authenticated = cookie_store.get('is_authenticated')
493
498
494 if not auth_user.is_authenticated and auth_user.is_user_object:
499 if not auth_user.is_authenticated and auth_user.is_user_object:
495 # user is not authenticated and not empty
500 # user is not authenticated and not empty
496 auth_user.set_authenticated(authenticated)
501 auth_user.set_authenticated(authenticated)
497
502
498 return auth_user
503 return auth_user, _auth_token
499
504
500
505
501 def h_filter(s):
506 def h_filter(s):
502 """
507 """
503 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
508 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
504 we wrap this with additional functionality that converts None to empty
509 we wrap this with additional functionality that converts None to empty
505 strings
510 strings
506 """
511 """
507 if s is None:
512 if s is None:
508 return markupsafe.Markup()
513 return markupsafe.Markup()
509 return markupsafe.escape(s)
514 return markupsafe.escape(s)
510
515
511
516
512 def add_events_routes(config):
517 def add_events_routes(config):
513 """
518 """
514 Adds routing that can be used in events. Because some events are triggered
519 Adds routing that can be used in events. Because some events are triggered
515 outside of pyramid context, we need to bootstrap request with some
520 outside of pyramid context, we need to bootstrap request with some
516 routing registered
521 routing registered
517 """
522 """
518
523
519 from rhodecode.apps._base import ADMIN_PREFIX
524 from rhodecode.apps._base import ADMIN_PREFIX
520
525
521 config.add_route(name='home', pattern='/')
526 config.add_route(name='home', pattern='/')
522
527
523 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
528 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
524 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
529 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
525 config.add_route(name='repo_summary', pattern='/{repo_name}')
530 config.add_route(name='repo_summary', pattern='/{repo_name}')
526 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
531 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
527 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
532 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
528
533
529 config.add_route(name='pullrequest_show',
534 config.add_route(name='pullrequest_show',
530 pattern='/{repo_name}/pull-request/{pull_request_id}')
535 pattern='/{repo_name}/pull-request/{pull_request_id}')
531 config.add_route(name='pull_requests_global',
536 config.add_route(name='pull_requests_global',
532 pattern='/pull-request/{pull_request_id}')
537 pattern='/pull-request/{pull_request_id}')
533 config.add_route(name='repo_commit',
538 config.add_route(name='repo_commit',
534 pattern='/{repo_name}/changeset/{commit_id}')
539 pattern='/{repo_name}/changeset/{commit_id}')
535
540
536 config.add_route(name='repo_files',
541 config.add_route(name='repo_files',
537 pattern='/{repo_name}/files/{commit_id}/{f_path}')
542 pattern='/{repo_name}/files/{commit_id}/{f_path}')
538
543
539
544
540 def bootstrap_config(request):
545 def bootstrap_config(request):
541 import pyramid.testing
546 import pyramid.testing
542 registry = pyramid.testing.Registry('RcTestRegistry')
547 registry = pyramid.testing.Registry('RcTestRegistry')
543
548
544 config = pyramid.testing.setUp(registry=registry, request=request)
549 config = pyramid.testing.setUp(registry=registry, request=request)
545
550
546 # allow pyramid lookup in testing
551 # allow pyramid lookup in testing
547 config.include('pyramid_mako')
552 config.include('pyramid_mako')
548 config.include('rhodecode.lib.rc_beaker')
553 config.include('rhodecode.lib.rc_beaker')
549 config.include('rhodecode.lib.rc_cache')
554 config.include('rhodecode.lib.rc_cache')
550
555
551 add_events_routes(config)
556 add_events_routes(config)
552
557
553 return config
558 return config
554
559
555
560
556 def bootstrap_request(**kwargs):
561 def bootstrap_request(**kwargs):
557 import pyramid.testing
562 import pyramid.testing
558
563
559 class TestRequest(pyramid.testing.DummyRequest):
564 class TestRequest(pyramid.testing.DummyRequest):
560 application_url = kwargs.pop('application_url', 'http://example.com')
565 application_url = kwargs.pop('application_url', 'http://example.com')
561 host = kwargs.pop('host', 'example.com:80')
566 host = kwargs.pop('host', 'example.com:80')
562 domain = kwargs.pop('domain', 'example.com')
567 domain = kwargs.pop('domain', 'example.com')
563
568
564 def translate(self, msg):
569 def translate(self, msg):
565 return msg
570 return msg
566
571
567 def plularize(self, singular, plural, n):
572 def plularize(self, singular, plural, n):
568 return singular
573 return singular
569
574
570 def get_partial_renderer(self, tmpl_name):
575 def get_partial_renderer(self, tmpl_name):
571
576
572 from rhodecode.lib.partial_renderer import get_partial_renderer
577 from rhodecode.lib.partial_renderer import get_partial_renderer
573 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
578 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
574
579
575 _call_context = TemplateArgs()
580 _call_context = TemplateArgs()
576 _call_context.visual = TemplateArgs()
581 _call_context.visual = TemplateArgs()
577 _call_context.visual.show_sha_length = 12
582 _call_context.visual.show_sha_length = 12
578 _call_context.visual.show_revision_number = True
583 _call_context.visual.show_revision_number = True
579
584
580 @property
585 @property
581 def call_context(self):
586 def call_context(self):
582 return self._call_context
587 return self._call_context
583
588
584 class TestDummySession(pyramid.testing.DummySession):
589 class TestDummySession(pyramid.testing.DummySession):
585 def save(*arg, **kw):
590 def save(*arg, **kw):
586 pass
591 pass
587
592
588 request = TestRequest(**kwargs)
593 request = TestRequest(**kwargs)
589 request.session = TestDummySession()
594 request.session = TestDummySession()
590
595
591 return request
596 return request
592
597
@@ -1,327 +1,328 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 import io
20 import io
21 import re
21 import re
22 import os
22 import os
23 import datetime
23 import datetime
24 import logging
24 import logging
25 import Queue
25 import Queue
26 import subprocess32
26 import subprocess32
27
27
28
28
29 from dateutil.parser import parse
29 from dateutil.parser import parse
30 from pyramid.threadlocal import get_current_request
30 from pyramid.threadlocal import get_current_request
31 from pyramid.interfaces import IRoutesMapper
31 from pyramid.interfaces import IRoutesMapper
32 from pyramid.settings import asbool
32 from pyramid.settings import asbool
33 from pyramid.path import AssetResolver
33 from pyramid.path import AssetResolver
34 from threading import Thread
34 from threading import Thread
35
35
36 from rhodecode.translation import _ as tsf
36 from rhodecode.translation import _ as tsf
37 from rhodecode.config.jsroutes import generate_jsroutes_content
37 from rhodecode.config.jsroutes import generate_jsroutes_content
38 from rhodecode.lib import auth
38 from rhodecode.lib import auth
39 from rhodecode.lib.base import get_auth_user
39 from rhodecode.lib.base import get_auth_user
40
40
41 import rhodecode
41 import rhodecode
42
42
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46
46
47 def add_renderer_globals(event):
47 def add_renderer_globals(event):
48 from rhodecode.lib import helpers
48 from rhodecode.lib import helpers
49
49
50 # TODO: When executed in pyramid view context the request is not available
50 # TODO: When executed in pyramid view context the request is not available
51 # in the event. Find a better solution to get the request.
51 # in the event. Find a better solution to get the request.
52 request = event['request'] or get_current_request()
52 request = event['request'] or get_current_request()
53
53
54 # Add Pyramid translation as '_' to context
54 # Add Pyramid translation as '_' to context
55 event['_'] = request.translate
55 event['_'] = request.translate
56 event['_ungettext'] = request.plularize
56 event['_ungettext'] = request.plularize
57 event['h'] = helpers
57 event['h'] = helpers
58
58
59
59
60 def add_localizer(event):
60 def add_localizer(event):
61 request = event.request
61 request = event.request
62 localizer = request.localizer
62 localizer = request.localizer
63
63
64 def auto_translate(*args, **kwargs):
64 def auto_translate(*args, **kwargs):
65 return localizer.translate(tsf(*args, **kwargs))
65 return localizer.translate(tsf(*args, **kwargs))
66
66
67 request.translate = auto_translate
67 request.translate = auto_translate
68 request.plularize = localizer.pluralize
68 request.plularize = localizer.pluralize
69
69
70
70
71 def set_user_lang(event):
71 def set_user_lang(event):
72 request = event.request
72 request = event.request
73 cur_user = getattr(request, 'user', None)
73 cur_user = getattr(request, 'user', None)
74
74
75 if cur_user:
75 if cur_user:
76 user_lang = cur_user.get_instance().user_data.get('language')
76 user_lang = cur_user.get_instance().user_data.get('language')
77 if user_lang:
77 if user_lang:
78 log.debug('lang: setting current user:%s language to: %s', cur_user, user_lang)
78 log.debug('lang: setting current user:%s language to: %s', cur_user, user_lang)
79 event.request._LOCALE_ = user_lang
79 event.request._LOCALE_ = user_lang
80
80
81
81
82 def add_request_user_context(event):
82 def add_request_user_context(event):
83 """
83 """
84 Adds auth user into request context
84 Adds auth user into request context
85 """
85 """
86 request = event.request
86 request = event.request
87 # access req_id as soon as possible
87 # access req_id as soon as possible
88 req_id = request.req_id
88 req_id = request.req_id
89
89
90 if hasattr(request, 'vcs_call'):
90 if hasattr(request, 'vcs_call'):
91 # skip vcs calls
91 # skip vcs calls
92 return
92 return
93
93
94 if hasattr(request, 'rpc_method'):
94 if hasattr(request, 'rpc_method'):
95 # skip api calls
95 # skip api calls
96 return
96 return
97
97
98 auth_user = get_auth_user(request)
98 auth_user, auth_token = get_auth_user(request)
99 request.user = auth_user
99 request.user = auth_user
100 request.user_auth_token = auth_token
100 request.environ['rc_auth_user'] = auth_user
101 request.environ['rc_auth_user'] = auth_user
101 request.environ['rc_auth_user_id'] = auth_user.user_id
102 request.environ['rc_auth_user_id'] = auth_user.user_id
102 request.environ['rc_req_id'] = req_id
103 request.environ['rc_req_id'] = req_id
103
104
104
105
105 def inject_app_settings(event):
106 def inject_app_settings(event):
106 settings = event.app.registry.settings
107 settings = event.app.registry.settings
107 # inject info about available permissions
108 # inject info about available permissions
108 auth.set_available_permissions(settings)
109 auth.set_available_permissions(settings)
109
110
110
111
111 def scan_repositories_if_enabled(event):
112 def scan_repositories_if_enabled(event):
112 """
113 """
113 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
114 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
114 does a repository scan if enabled in the settings.
115 does a repository scan if enabled in the settings.
115 """
116 """
116 settings = event.app.registry.settings
117 settings = event.app.registry.settings
117 vcs_server_enabled = settings['vcs.server.enable']
118 vcs_server_enabled = settings['vcs.server.enable']
118 import_on_startup = settings['startup.import_repos']
119 import_on_startup = settings['startup.import_repos']
119 if vcs_server_enabled and import_on_startup:
120 if vcs_server_enabled and import_on_startup:
120 from rhodecode.model.scm import ScmModel
121 from rhodecode.model.scm import ScmModel
121 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_base_path
122 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_base_path
122 repositories = ScmModel().repo_scan(get_rhodecode_base_path())
123 repositories = ScmModel().repo_scan(get_rhodecode_base_path())
123 repo2db_mapper(repositories, remove_obsolete=False)
124 repo2db_mapper(repositories, remove_obsolete=False)
124
125
125
126
126 def write_metadata_if_needed(event):
127 def write_metadata_if_needed(event):
127 """
128 """
128 Writes upgrade metadata
129 Writes upgrade metadata
129 """
130 """
130 import rhodecode
131 import rhodecode
131 from rhodecode.lib import system_info
132 from rhodecode.lib import system_info
132 from rhodecode.lib import ext_json
133 from rhodecode.lib import ext_json
133
134
134 fname = '.rcmetadata.json'
135 fname = '.rcmetadata.json'
135 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
136 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
136 metadata_destination = os.path.join(ini_loc, fname)
137 metadata_destination = os.path.join(ini_loc, fname)
137
138
138 def get_update_age():
139 def get_update_age():
139 now = datetime.datetime.utcnow()
140 now = datetime.datetime.utcnow()
140
141
141 with open(metadata_destination, 'rb') as f:
142 with open(metadata_destination, 'rb') as f:
142 data = ext_json.json.loads(f.read())
143 data = ext_json.json.loads(f.read())
143 if 'created_on' in data:
144 if 'created_on' in data:
144 update_date = parse(data['created_on'])
145 update_date = parse(data['created_on'])
145 diff = now - update_date
146 diff = now - update_date
146 return diff.total_seconds() / 60.0
147 return diff.total_seconds() / 60.0
147
148
148 return 0
149 return 0
149
150
150 def write():
151 def write():
151 configuration = system_info.SysInfo(
152 configuration = system_info.SysInfo(
152 system_info.rhodecode_config)()['value']
153 system_info.rhodecode_config)()['value']
153 license_token = configuration['config']['license_token']
154 license_token = configuration['config']['license_token']
154
155
155 setup = dict(
156 setup = dict(
156 workers=configuration['config']['server:main'].get(
157 workers=configuration['config']['server:main'].get(
157 'workers', '?'),
158 'workers', '?'),
158 worker_type=configuration['config']['server:main'].get(
159 worker_type=configuration['config']['server:main'].get(
159 'worker_class', 'sync'),
160 'worker_class', 'sync'),
160 )
161 )
161 dbinfo = system_info.SysInfo(system_info.database_info)()['value']
162 dbinfo = system_info.SysInfo(system_info.database_info)()['value']
162 del dbinfo['url']
163 del dbinfo['url']
163
164
164 metadata = dict(
165 metadata = dict(
165 desc='upgrade metadata info',
166 desc='upgrade metadata info',
166 license_token=license_token,
167 license_token=license_token,
167 created_on=datetime.datetime.utcnow().isoformat(),
168 created_on=datetime.datetime.utcnow().isoformat(),
168 usage=system_info.SysInfo(system_info.usage_info)()['value'],
169 usage=system_info.SysInfo(system_info.usage_info)()['value'],
169 platform=system_info.SysInfo(system_info.platform_type)()['value'],
170 platform=system_info.SysInfo(system_info.platform_type)()['value'],
170 database=dbinfo,
171 database=dbinfo,
171 cpu=system_info.SysInfo(system_info.cpu)()['value'],
172 cpu=system_info.SysInfo(system_info.cpu)()['value'],
172 memory=system_info.SysInfo(system_info.memory)()['value'],
173 memory=system_info.SysInfo(system_info.memory)()['value'],
173 setup=setup
174 setup=setup
174 )
175 )
175
176
176 with open(metadata_destination, 'wb') as f:
177 with open(metadata_destination, 'wb') as f:
177 f.write(ext_json.json.dumps(metadata))
178 f.write(ext_json.json.dumps(metadata))
178
179
179 settings = event.app.registry.settings
180 settings = event.app.registry.settings
180 if settings.get('metadata.skip'):
181 if settings.get('metadata.skip'):
181 return
182 return
182
183
183 # only write this every 24h, workers restart caused unwanted delays
184 # only write this every 24h, workers restart caused unwanted delays
184 try:
185 try:
185 age_in_min = get_update_age()
186 age_in_min = get_update_age()
186 except Exception:
187 except Exception:
187 age_in_min = 0
188 age_in_min = 0
188
189
189 if age_in_min > 60 * 60 * 24:
190 if age_in_min > 60 * 60 * 24:
190 return
191 return
191
192
192 try:
193 try:
193 write()
194 write()
194 except Exception:
195 except Exception:
195 pass
196 pass
196
197
197
198
198 def write_js_routes_if_enabled(event):
199 def write_js_routes_if_enabled(event):
199 registry = event.app.registry
200 registry = event.app.registry
200
201
201 mapper = registry.queryUtility(IRoutesMapper)
202 mapper = registry.queryUtility(IRoutesMapper)
202 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
203 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
203
204
204 def _extract_route_information(route):
205 def _extract_route_information(route):
205 """
206 """
206 Convert a route into tuple(name, path, args), eg:
207 Convert a route into tuple(name, path, args), eg:
207 ('show_user', '/profile/%(username)s', ['username'])
208 ('show_user', '/profile/%(username)s', ['username'])
208 """
209 """
209
210
210 routepath = route.pattern
211 routepath = route.pattern
211 pattern = route.pattern
212 pattern = route.pattern
212
213
213 def replace(matchobj):
214 def replace(matchobj):
214 if matchobj.group(1):
215 if matchobj.group(1):
215 return "%%(%s)s" % matchobj.group(1).split(':')[0]
216 return "%%(%s)s" % matchobj.group(1).split(':')[0]
216 else:
217 else:
217 return "%%(%s)s" % matchobj.group(2)
218 return "%%(%s)s" % matchobj.group(2)
218
219
219 routepath = _argument_prog.sub(replace, routepath)
220 routepath = _argument_prog.sub(replace, routepath)
220
221
221 if not routepath.startswith('/'):
222 if not routepath.startswith('/'):
222 routepath = '/'+routepath
223 routepath = '/'+routepath
223
224
224 return (
225 return (
225 route.name,
226 route.name,
226 routepath,
227 routepath,
227 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
228 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
228 for arg in _argument_prog.findall(pattern)]
229 for arg in _argument_prog.findall(pattern)]
229 )
230 )
230
231
231 def get_routes():
232 def get_routes():
232 # pyramid routes
233 # pyramid routes
233 for route in mapper.get_routes():
234 for route in mapper.get_routes():
234 if not route.name.startswith('__'):
235 if not route.name.startswith('__'):
235 yield _extract_route_information(route)
236 yield _extract_route_information(route)
236
237
237 if asbool(registry.settings.get('generate_js_files', 'false')):
238 if asbool(registry.settings.get('generate_js_files', 'false')):
238 static_path = AssetResolver().resolve('rhodecode:public').abspath()
239 static_path = AssetResolver().resolve('rhodecode:public').abspath()
239 jsroutes = get_routes()
240 jsroutes = get_routes()
240 jsroutes_file_content = generate_jsroutes_content(jsroutes)
241 jsroutes_file_content = generate_jsroutes_content(jsroutes)
241 jsroutes_file_path = os.path.join(
242 jsroutes_file_path = os.path.join(
242 static_path, 'js', 'rhodecode', 'routes.js')
243 static_path, 'js', 'rhodecode', 'routes.js')
243
244
244 try:
245 try:
245 with io.open(jsroutes_file_path, 'w', encoding='utf-8') as f:
246 with io.open(jsroutes_file_path, 'w', encoding='utf-8') as f:
246 f.write(jsroutes_file_content)
247 f.write(jsroutes_file_content)
247 except Exception:
248 except Exception:
248 log.exception('Failed to write routes.js into %s', jsroutes_file_path)
249 log.exception('Failed to write routes.js into %s', jsroutes_file_path)
249
250
250
251
251 class Subscriber(object):
252 class Subscriber(object):
252 """
253 """
253 Base class for subscribers to the pyramid event system.
254 Base class for subscribers to the pyramid event system.
254 """
255 """
255 def __call__(self, event):
256 def __call__(self, event):
256 self.run(event)
257 self.run(event)
257
258
258 def run(self, event):
259 def run(self, event):
259 raise NotImplementedError('Subclass has to implement this.')
260 raise NotImplementedError('Subclass has to implement this.')
260
261
261
262
262 class AsyncSubscriber(Subscriber):
263 class AsyncSubscriber(Subscriber):
263 """
264 """
264 Subscriber that handles the execution of events in a separate task to not
265 Subscriber that handles the execution of events in a separate task to not
265 block the execution of the code which triggers the event. It puts the
266 block the execution of the code which triggers the event. It puts the
266 received events into a queue from which the worker process takes them in
267 received events into a queue from which the worker process takes them in
267 order.
268 order.
268 """
269 """
269 def __init__(self):
270 def __init__(self):
270 self._stop = False
271 self._stop = False
271 self._eventq = Queue.Queue()
272 self._eventq = Queue.Queue()
272 self._worker = self.create_worker()
273 self._worker = self.create_worker()
273 self._worker.start()
274 self._worker.start()
274
275
275 def __call__(self, event):
276 def __call__(self, event):
276 self._eventq.put(event)
277 self._eventq.put(event)
277
278
278 def create_worker(self):
279 def create_worker(self):
279 worker = Thread(target=self.do_work)
280 worker = Thread(target=self.do_work)
280 worker.daemon = True
281 worker.daemon = True
281 return worker
282 return worker
282
283
283 def stop_worker(self):
284 def stop_worker(self):
284 self._stop = False
285 self._stop = False
285 self._eventq.put(None)
286 self._eventq.put(None)
286 self._worker.join()
287 self._worker.join()
287
288
288 def do_work(self):
289 def do_work(self):
289 while not self._stop:
290 while not self._stop:
290 event = self._eventq.get()
291 event = self._eventq.get()
291 if event is not None:
292 if event is not None:
292 self.run(event)
293 self.run(event)
293
294
294
295
295 class AsyncSubprocessSubscriber(AsyncSubscriber):
296 class AsyncSubprocessSubscriber(AsyncSubscriber):
296 """
297 """
297 Subscriber that uses the subprocess32 module to execute a command if an
298 Subscriber that uses the subprocess32 module to execute a command if an
298 event is received. Events are handled asynchronously.
299 event is received. Events are handled asynchronously.
299 """
300 """
300
301
301 def __init__(self, cmd, timeout=None):
302 def __init__(self, cmd, timeout=None):
302 super(AsyncSubprocessSubscriber, self).__init__()
303 super(AsyncSubprocessSubscriber, self).__init__()
303 self._cmd = cmd
304 self._cmd = cmd
304 self._timeout = timeout
305 self._timeout = timeout
305
306
306 def run(self, event):
307 def run(self, event):
307 cmd = self._cmd
308 cmd = self._cmd
308 timeout = self._timeout
309 timeout = self._timeout
309 log.debug('Executing command %s.', cmd)
310 log.debug('Executing command %s.', cmd)
310
311
311 try:
312 try:
312 output = subprocess32.check_output(
313 output = subprocess32.check_output(
313 cmd, timeout=timeout, stderr=subprocess32.STDOUT)
314 cmd, timeout=timeout, stderr=subprocess32.STDOUT)
314 log.debug('Command finished %s', cmd)
315 log.debug('Command finished %s', cmd)
315 if output:
316 if output:
316 log.debug('Command output: %s', output)
317 log.debug('Command output: %s', output)
317 except subprocess32.TimeoutExpired as e:
318 except subprocess32.TimeoutExpired as e:
318 log.exception('Timeout while executing command.')
319 log.exception('Timeout while executing command.')
319 if e.output:
320 if e.output:
320 log.error('Command output: %s', e.output)
321 log.error('Command output: %s', e.output)
321 except subprocess32.CalledProcessError as e:
322 except subprocess32.CalledProcessError as e:
322 log.exception('Error while executing command.')
323 log.exception('Error while executing command.')
323 if e.output:
324 if e.output:
324 log.error('Command output: %s', e.output)
325 log.error('Command output: %s', e.output)
325 except:
326 except:
326 log.exception(
327 log.exception(
327 'Exception while executing command %s.', cmd)
328 'Exception while executing command %s.', cmd)
General Comments 0
You need to be logged in to leave comments. Login now