##// END OF EJS Templates
pyramid: fix problem with default language for users without any explicit one set.
marcink -
r1919:4f4ecf65 default
parent child Browse files
Show More
@@ -1,632 +1,632 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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 ipaddress
30 import ipaddress
31 import pyramid.threadlocal
31 import pyramid.threadlocal
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 from pylons import config, tmpl_context as c, request, url
36 from pylons import config, tmpl_context as c, request, url
37 from pylons.controllers import WSGIController
37 from pylons.controllers import WSGIController
38 from pylons.controllers.util import redirect
38 from pylons.controllers.util import redirect
39 from pylons.i18n import translation
39 from pylons.i18n import translation
40 # marcink: don't remove this import
40 # marcink: don't remove this import
41 from pylons.templating import render_mako as render # noqa
41 from pylons.templating import render_mako as render # noqa
42 from pylons.i18n.translation import _
42 from pylons.i18n.translation import _
43 from webob.exc import HTTPFound
43 from webob.exc import HTTPFound
44
44
45
45
46 import rhodecode
46 import rhodecode
47 from rhodecode.authentication.base import VCS_TYPE
47 from rhodecode.authentication.base import VCS_TYPE
48 from rhodecode.lib import auth, utils2
48 from rhodecode.lib import auth, utils2
49 from rhodecode.lib import helpers as h
49 from rhodecode.lib import helpers as h
50 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
50 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
51 from rhodecode.lib.exceptions import UserCreationError
51 from rhodecode.lib.exceptions import UserCreationError
52 from rhodecode.lib.utils import (
52 from rhodecode.lib.utils import (
53 get_repo_slug, set_rhodecode_config, password_changed,
53 get_repo_slug, set_rhodecode_config, password_changed,
54 get_enabled_hook_classes)
54 get_enabled_hook_classes)
55 from rhodecode.lib.utils2 import (
55 from rhodecode.lib.utils2 import (
56 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist)
56 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist)
57 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
57 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
58 from rhodecode.model import meta
58 from rhodecode.model import meta
59 from rhodecode.model.db import Repository, User, ChangesetComment
59 from rhodecode.model.db import Repository, User, ChangesetComment
60 from rhodecode.model.notification import NotificationModel
60 from rhodecode.model.notification import NotificationModel
61 from rhodecode.model.scm import ScmModel
61 from rhodecode.model.scm import ScmModel
62 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
62 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
63
63
64
64
65 log = logging.getLogger(__name__)
65 log = logging.getLogger(__name__)
66
66
67
67
68 def _filter_proxy(ip):
68 def _filter_proxy(ip):
69 """
69 """
70 Passed in IP addresses in HEADERS can be in a special format of multiple
70 Passed in IP addresses in HEADERS can be in a special format of multiple
71 ips. Those comma separated IPs are passed from various proxies in the
71 ips. Those comma separated IPs are passed from various proxies in the
72 chain of request processing. The left-most being the original client.
72 chain of request processing. The left-most being the original client.
73 We only care about the first IP which came from the org. client.
73 We only care about the first IP which came from the org. client.
74
74
75 :param ip: ip string from headers
75 :param ip: ip string from headers
76 """
76 """
77 if ',' in ip:
77 if ',' in ip:
78 _ips = ip.split(',')
78 _ips = ip.split(',')
79 _first_ip = _ips[0].strip()
79 _first_ip = _ips[0].strip()
80 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
80 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
81 return _first_ip
81 return _first_ip
82 return ip
82 return ip
83
83
84
84
85 def _filter_port(ip):
85 def _filter_port(ip):
86 """
86 """
87 Removes a port from ip, there are 4 main cases to handle here.
87 Removes a port from ip, there are 4 main cases to handle here.
88 - ipv4 eg. 127.0.0.1
88 - ipv4 eg. 127.0.0.1
89 - ipv6 eg. ::1
89 - ipv6 eg. ::1
90 - ipv4+port eg. 127.0.0.1:8080
90 - ipv4+port eg. 127.0.0.1:8080
91 - ipv6+port eg. [::1]:8080
91 - ipv6+port eg. [::1]:8080
92
92
93 :param ip:
93 :param ip:
94 """
94 """
95 def is_ipv6(ip_addr):
95 def is_ipv6(ip_addr):
96 if hasattr(socket, 'inet_pton'):
96 if hasattr(socket, 'inet_pton'):
97 try:
97 try:
98 socket.inet_pton(socket.AF_INET6, ip_addr)
98 socket.inet_pton(socket.AF_INET6, ip_addr)
99 except socket.error:
99 except socket.error:
100 return False
100 return False
101 else:
101 else:
102 # fallback to ipaddress
102 # fallback to ipaddress
103 try:
103 try:
104 ipaddress.IPv6Address(safe_unicode(ip_addr))
104 ipaddress.IPv6Address(safe_unicode(ip_addr))
105 except Exception:
105 except Exception:
106 return False
106 return False
107 return True
107 return True
108
108
109 if ':' not in ip: # must be ipv4 pure ip
109 if ':' not in ip: # must be ipv4 pure ip
110 return ip
110 return ip
111
111
112 if '[' in ip and ']' in ip: # ipv6 with port
112 if '[' in ip and ']' in ip: # ipv6 with port
113 return ip.split(']')[0][1:].lower()
113 return ip.split(']')[0][1:].lower()
114
114
115 # must be ipv6 or ipv4 with port
115 # must be ipv6 or ipv4 with port
116 if is_ipv6(ip):
116 if is_ipv6(ip):
117 return ip
117 return ip
118 else:
118 else:
119 ip, _port = ip.split(':')[:2] # means ipv4+port
119 ip, _port = ip.split(':')[:2] # means ipv4+port
120 return ip
120 return ip
121
121
122
122
123 def get_ip_addr(environ):
123 def get_ip_addr(environ):
124 proxy_key = 'HTTP_X_REAL_IP'
124 proxy_key = 'HTTP_X_REAL_IP'
125 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
125 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
126 def_key = 'REMOTE_ADDR'
126 def_key = 'REMOTE_ADDR'
127 _filters = lambda x: _filter_port(_filter_proxy(x))
127 _filters = lambda x: _filter_port(_filter_proxy(x))
128
128
129 ip = environ.get(proxy_key)
129 ip = environ.get(proxy_key)
130 if ip:
130 if ip:
131 return _filters(ip)
131 return _filters(ip)
132
132
133 ip = environ.get(proxy_key2)
133 ip = environ.get(proxy_key2)
134 if ip:
134 if ip:
135 return _filters(ip)
135 return _filters(ip)
136
136
137 ip = environ.get(def_key, '0.0.0.0')
137 ip = environ.get(def_key, '0.0.0.0')
138 return _filters(ip)
138 return _filters(ip)
139
139
140
140
141 def get_server_ip_addr(environ, log_errors=True):
141 def get_server_ip_addr(environ, log_errors=True):
142 hostname = environ.get('SERVER_NAME')
142 hostname = environ.get('SERVER_NAME')
143 try:
143 try:
144 return socket.gethostbyname(hostname)
144 return socket.gethostbyname(hostname)
145 except Exception as e:
145 except Exception as e:
146 if log_errors:
146 if log_errors:
147 # in some cases this lookup is not possible, and we don't want to
147 # in some cases this lookup is not possible, and we don't want to
148 # make it an exception in logs
148 # make it an exception in logs
149 log.exception('Could not retrieve server ip address: %s', e)
149 log.exception('Could not retrieve server ip address: %s', e)
150 return hostname
150 return hostname
151
151
152
152
153 def get_server_port(environ):
153 def get_server_port(environ):
154 return environ.get('SERVER_PORT')
154 return environ.get('SERVER_PORT')
155
155
156
156
157 def get_access_path(environ):
157 def get_access_path(environ):
158 path = environ.get('PATH_INFO')
158 path = environ.get('PATH_INFO')
159 org_req = environ.get('pylons.original_request')
159 org_req = environ.get('pylons.original_request')
160 if org_req:
160 if org_req:
161 path = org_req.environ.get('PATH_INFO')
161 path = org_req.environ.get('PATH_INFO')
162 return path
162 return path
163
163
164
164
165 def get_user_agent(environ):
165 def get_user_agent(environ):
166 return environ.get('HTTP_USER_AGENT')
166 return environ.get('HTTP_USER_AGENT')
167
167
168
168
169 def vcs_operation_context(
169 def vcs_operation_context(
170 environ, repo_name, username, action, scm, check_locking=True,
170 environ, repo_name, username, action, scm, check_locking=True,
171 is_shadow_repo=False):
171 is_shadow_repo=False):
172 """
172 """
173 Generate the context for a vcs operation, e.g. push or pull.
173 Generate the context for a vcs operation, e.g. push or pull.
174
174
175 This context is passed over the layers so that hooks triggered by the
175 This context is passed over the layers so that hooks triggered by the
176 vcs operation know details like the user, the user's IP address etc.
176 vcs operation know details like the user, the user's IP address etc.
177
177
178 :param check_locking: Allows to switch of the computation of the locking
178 :param check_locking: Allows to switch of the computation of the locking
179 data. This serves mainly the need of the simplevcs middleware to be
179 data. This serves mainly the need of the simplevcs middleware to be
180 able to disable this for certain operations.
180 able to disable this for certain operations.
181
181
182 """
182 """
183 # Tri-state value: False: unlock, None: nothing, True: lock
183 # Tri-state value: False: unlock, None: nothing, True: lock
184 make_lock = None
184 make_lock = None
185 locked_by = [None, None, None]
185 locked_by = [None, None, None]
186 is_anonymous = username == User.DEFAULT_USER
186 is_anonymous = username == User.DEFAULT_USER
187 if not is_anonymous and check_locking:
187 if not is_anonymous and check_locking:
188 log.debug('Checking locking on repository "%s"', repo_name)
188 log.debug('Checking locking on repository "%s"', repo_name)
189 user = User.get_by_username(username)
189 user = User.get_by_username(username)
190 repo = Repository.get_by_repo_name(repo_name)
190 repo = Repository.get_by_repo_name(repo_name)
191 make_lock, __, locked_by = repo.get_locking_state(
191 make_lock, __, locked_by = repo.get_locking_state(
192 action, user.user_id)
192 action, user.user_id)
193
193
194 settings_model = VcsSettingsModel(repo=repo_name)
194 settings_model = VcsSettingsModel(repo=repo_name)
195 ui_settings = settings_model.get_ui_settings()
195 ui_settings = settings_model.get_ui_settings()
196
196
197 extras = {
197 extras = {
198 'ip': get_ip_addr(environ),
198 'ip': get_ip_addr(environ),
199 'username': username,
199 'username': username,
200 'action': action,
200 'action': action,
201 'repository': repo_name,
201 'repository': repo_name,
202 'scm': scm,
202 'scm': scm,
203 'config': rhodecode.CONFIG['__file__'],
203 'config': rhodecode.CONFIG['__file__'],
204 'make_lock': make_lock,
204 'make_lock': make_lock,
205 'locked_by': locked_by,
205 'locked_by': locked_by,
206 'server_url': utils2.get_server_url(environ),
206 'server_url': utils2.get_server_url(environ),
207 'user_agent': get_user_agent(environ),
207 'user_agent': get_user_agent(environ),
208 'hooks': get_enabled_hook_classes(ui_settings),
208 'hooks': get_enabled_hook_classes(ui_settings),
209 'is_shadow_repo': is_shadow_repo,
209 'is_shadow_repo': is_shadow_repo,
210 }
210 }
211 return extras
211 return extras
212
212
213
213
214 class BasicAuth(AuthBasicAuthenticator):
214 class BasicAuth(AuthBasicAuthenticator):
215
215
216 def __init__(self, realm, authfunc, registry, auth_http_code=None,
216 def __init__(self, realm, authfunc, registry, auth_http_code=None,
217 initial_call_detection=False, acl_repo_name=None):
217 initial_call_detection=False, acl_repo_name=None):
218 self.realm = realm
218 self.realm = realm
219 self.initial_call = initial_call_detection
219 self.initial_call = initial_call_detection
220 self.authfunc = authfunc
220 self.authfunc = authfunc
221 self.registry = registry
221 self.registry = registry
222 self.acl_repo_name = acl_repo_name
222 self.acl_repo_name = acl_repo_name
223 self._rc_auth_http_code = auth_http_code
223 self._rc_auth_http_code = auth_http_code
224
224
225 def _get_response_from_code(self, http_code):
225 def _get_response_from_code(self, http_code):
226 try:
226 try:
227 return get_exception(safe_int(http_code))
227 return get_exception(safe_int(http_code))
228 except Exception:
228 except Exception:
229 log.exception('Failed to fetch response for code %s' % http_code)
229 log.exception('Failed to fetch response for code %s' % http_code)
230 return HTTPForbidden
230 return HTTPForbidden
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 if self.authfunc(
254 if 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 return username
257 return username
258 if username and password:
258 if username and password:
259 # we mark that we actually executed authentication once, at
259 # we mark that we actually executed authentication once, at
260 # that point we can use the alternative auth code
260 # that point we can use the alternative auth code
261 self.initial_call = False
261 self.initial_call = False
262
262
263 return self.build_authentication()
263 return self.build_authentication()
264
264
265 __call__ = authenticate
265 __call__ = authenticate
266
266
267
267
268 def calculate_version_hash():
268 def calculate_version_hash():
269 return md5(
269 return md5(
270 config.get('beaker.session.secret', '') +
270 config.get('beaker.session.secret', '') +
271 rhodecode.__version__)[:8]
271 rhodecode.__version__)[:8]
272
272
273
273
274 def get_current_lang(request):
274 def get_current_lang(request):
275 # NOTE(marcink): remove after pyramid move
275 # NOTE(marcink): remove after pyramid move
276 try:
276 try:
277 return translation.get_lang()[0]
277 return translation.get_lang()[0]
278 except:
278 except:
279 pass
279 pass
280
280
281 return getattr(request, '_LOCALE_', None)
281 return getattr(request, '_LOCALE_', request.locale_name)
282
282
283
283
284 def attach_context_attributes(context, request, user_id):
284 def attach_context_attributes(context, request, user_id):
285 """
285 """
286 Attach variables into template context called `c`, please note that
286 Attach variables into template context called `c`, please note that
287 request could be pylons or pyramid request in here.
287 request could be pylons or pyramid request in here.
288 """
288 """
289
289
290 rc_config = SettingsModel().get_all_settings(cache=True)
290 rc_config = SettingsModel().get_all_settings(cache=True)
291
291
292 context.rhodecode_version = rhodecode.__version__
292 context.rhodecode_version = rhodecode.__version__
293 context.rhodecode_edition = config.get('rhodecode.edition')
293 context.rhodecode_edition = config.get('rhodecode.edition')
294 # unique secret + version does not leak the version but keep consistency
294 # unique secret + version does not leak the version but keep consistency
295 context.rhodecode_version_hash = calculate_version_hash()
295 context.rhodecode_version_hash = calculate_version_hash()
296
296
297 # Default language set for the incoming request
297 # Default language set for the incoming request
298 context.language = get_current_lang(request)
298 context.language = get_current_lang(request)
299
299
300 # Visual options
300 # Visual options
301 context.visual = AttributeDict({})
301 context.visual = AttributeDict({})
302
302
303 # DB stored Visual Items
303 # DB stored Visual Items
304 context.visual.show_public_icon = str2bool(
304 context.visual.show_public_icon = str2bool(
305 rc_config.get('rhodecode_show_public_icon'))
305 rc_config.get('rhodecode_show_public_icon'))
306 context.visual.show_private_icon = str2bool(
306 context.visual.show_private_icon = str2bool(
307 rc_config.get('rhodecode_show_private_icon'))
307 rc_config.get('rhodecode_show_private_icon'))
308 context.visual.stylify_metatags = str2bool(
308 context.visual.stylify_metatags = str2bool(
309 rc_config.get('rhodecode_stylify_metatags'))
309 rc_config.get('rhodecode_stylify_metatags'))
310 context.visual.dashboard_items = safe_int(
310 context.visual.dashboard_items = safe_int(
311 rc_config.get('rhodecode_dashboard_items', 100))
311 rc_config.get('rhodecode_dashboard_items', 100))
312 context.visual.admin_grid_items = safe_int(
312 context.visual.admin_grid_items = safe_int(
313 rc_config.get('rhodecode_admin_grid_items', 100))
313 rc_config.get('rhodecode_admin_grid_items', 100))
314 context.visual.repository_fields = str2bool(
314 context.visual.repository_fields = str2bool(
315 rc_config.get('rhodecode_repository_fields'))
315 rc_config.get('rhodecode_repository_fields'))
316 context.visual.show_version = str2bool(
316 context.visual.show_version = str2bool(
317 rc_config.get('rhodecode_show_version'))
317 rc_config.get('rhodecode_show_version'))
318 context.visual.use_gravatar = str2bool(
318 context.visual.use_gravatar = str2bool(
319 rc_config.get('rhodecode_use_gravatar'))
319 rc_config.get('rhodecode_use_gravatar'))
320 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
320 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
321 context.visual.default_renderer = rc_config.get(
321 context.visual.default_renderer = rc_config.get(
322 'rhodecode_markup_renderer', 'rst')
322 'rhodecode_markup_renderer', 'rst')
323 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
323 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
324 context.visual.rhodecode_support_url = \
324 context.visual.rhodecode_support_url = \
325 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
325 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
326
326
327 context.pre_code = rc_config.get('rhodecode_pre_code')
327 context.pre_code = rc_config.get('rhodecode_pre_code')
328 context.post_code = rc_config.get('rhodecode_post_code')
328 context.post_code = rc_config.get('rhodecode_post_code')
329 context.rhodecode_name = rc_config.get('rhodecode_title')
329 context.rhodecode_name = rc_config.get('rhodecode_title')
330 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
330 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
331 # if we have specified default_encoding in the request, it has more
331 # if we have specified default_encoding in the request, it has more
332 # priority
332 # priority
333 if request.GET.get('default_encoding'):
333 if request.GET.get('default_encoding'):
334 context.default_encodings.insert(0, request.GET.get('default_encoding'))
334 context.default_encodings.insert(0, request.GET.get('default_encoding'))
335 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
335 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
336
336
337 # INI stored
337 # INI stored
338 context.labs_active = str2bool(
338 context.labs_active = str2bool(
339 config.get('labs_settings_active', 'false'))
339 config.get('labs_settings_active', 'false'))
340 context.visual.allow_repo_location_change = str2bool(
340 context.visual.allow_repo_location_change = str2bool(
341 config.get('allow_repo_location_change', True))
341 config.get('allow_repo_location_change', True))
342 context.visual.allow_custom_hooks_settings = str2bool(
342 context.visual.allow_custom_hooks_settings = str2bool(
343 config.get('allow_custom_hooks_settings', True))
343 config.get('allow_custom_hooks_settings', True))
344 context.debug_style = str2bool(config.get('debug_style', False))
344 context.debug_style = str2bool(config.get('debug_style', False))
345
345
346 context.rhodecode_instanceid = config.get('instance_id')
346 context.rhodecode_instanceid = config.get('instance_id')
347
347
348 context.visual.cut_off_limit_diff = safe_int(
348 context.visual.cut_off_limit_diff = safe_int(
349 config.get('cut_off_limit_diff'))
349 config.get('cut_off_limit_diff'))
350 context.visual.cut_off_limit_file = safe_int(
350 context.visual.cut_off_limit_file = safe_int(
351 config.get('cut_off_limit_file'))
351 config.get('cut_off_limit_file'))
352
352
353 # AppEnlight
353 # AppEnlight
354 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
354 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
355 context.appenlight_api_public_key = config.get(
355 context.appenlight_api_public_key = config.get(
356 'appenlight.api_public_key', '')
356 'appenlight.api_public_key', '')
357 context.appenlight_server_url = config.get('appenlight.server_url', '')
357 context.appenlight_server_url = config.get('appenlight.server_url', '')
358
358
359 # JS template context
359 # JS template context
360 context.template_context = {
360 context.template_context = {
361 'repo_name': None,
361 'repo_name': None,
362 'repo_type': None,
362 'repo_type': None,
363 'repo_landing_commit': None,
363 'repo_landing_commit': None,
364 'rhodecode_user': {
364 'rhodecode_user': {
365 'username': None,
365 'username': None,
366 'email': None,
366 'email': None,
367 'notification_status': False
367 'notification_status': False
368 },
368 },
369 'visual': {
369 'visual': {
370 'default_renderer': None
370 'default_renderer': None
371 },
371 },
372 'commit_data': {
372 'commit_data': {
373 'commit_id': None
373 'commit_id': None
374 },
374 },
375 'pull_request_data': {'pull_request_id': None},
375 'pull_request_data': {'pull_request_id': None},
376 'timeago': {
376 'timeago': {
377 'refresh_time': 120 * 1000,
377 'refresh_time': 120 * 1000,
378 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
378 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
379 },
379 },
380 'pylons_dispatch': {
380 'pylons_dispatch': {
381 # 'controller': request.environ['pylons.routes_dict']['controller'],
381 # 'controller': request.environ['pylons.routes_dict']['controller'],
382 # 'action': request.environ['pylons.routes_dict']['action'],
382 # 'action': request.environ['pylons.routes_dict']['action'],
383 },
383 },
384 'pyramid_dispatch': {
384 'pyramid_dispatch': {
385
385
386 },
386 },
387 'extra': {'plugins': {}}
387 'extra': {'plugins': {}}
388 }
388 }
389 # END CONFIG VARS
389 # END CONFIG VARS
390
390
391 # TODO: This dosn't work when called from pylons compatibility tween.
391 # TODO: This dosn't work when called from pylons compatibility tween.
392 # Fix this and remove it from base controller.
392 # Fix this and remove it from base controller.
393 # context.repo_name = get_repo_slug(request) # can be empty
393 # context.repo_name = get_repo_slug(request) # can be empty
394
394
395 diffmode = 'sideside'
395 diffmode = 'sideside'
396 if request.GET.get('diffmode'):
396 if request.GET.get('diffmode'):
397 if request.GET['diffmode'] == 'unified':
397 if request.GET['diffmode'] == 'unified':
398 diffmode = 'unified'
398 diffmode = 'unified'
399 elif request.session.get('diffmode'):
399 elif request.session.get('diffmode'):
400 diffmode = request.session['diffmode']
400 diffmode = request.session['diffmode']
401
401
402 context.diffmode = diffmode
402 context.diffmode = diffmode
403
403
404 if request.session.get('diffmode') != diffmode:
404 if request.session.get('diffmode') != diffmode:
405 request.session['diffmode'] = diffmode
405 request.session['diffmode'] = diffmode
406
406
407 context.csrf_token = auth.get_csrf_token(session=request.session)
407 context.csrf_token = auth.get_csrf_token(session=request.session)
408 context.backends = rhodecode.BACKENDS.keys()
408 context.backends = rhodecode.BACKENDS.keys()
409 context.backends.sort()
409 context.backends.sort()
410 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
410 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
411
411
412 # NOTE(marcink): when migrated to pyramid we don't need to set this anymore,
412 # NOTE(marcink): when migrated to pyramid we don't need to set this anymore,
413 # given request will ALWAYS be pyramid one
413 # given request will ALWAYS be pyramid one
414 pyramid_request = pyramid.threadlocal.get_current_request()
414 pyramid_request = pyramid.threadlocal.get_current_request()
415 context.pyramid_request = pyramid_request
415 context.pyramid_request = pyramid_request
416
416
417 # web case
417 # web case
418 if hasattr(pyramid_request, 'user'):
418 if hasattr(pyramid_request, 'user'):
419 context.auth_user = pyramid_request.user
419 context.auth_user = pyramid_request.user
420 context.rhodecode_user = pyramid_request.user
420 context.rhodecode_user = pyramid_request.user
421
421
422 # api case
422 # api case
423 if hasattr(pyramid_request, 'rpc_user'):
423 if hasattr(pyramid_request, 'rpc_user'):
424 context.auth_user = pyramid_request.rpc_user
424 context.auth_user = pyramid_request.rpc_user
425 context.rhodecode_user = pyramid_request.rpc_user
425 context.rhodecode_user = pyramid_request.rpc_user
426
426
427 # attach the whole call context to the request
427 # attach the whole call context to the request
428 request.call_context = context
428 request.call_context = context
429
429
430
430
431 def get_auth_user(request):
431 def get_auth_user(request):
432 environ = request.environ
432 environ = request.environ
433 session = request.session
433 session = request.session
434
434
435 ip_addr = get_ip_addr(environ)
435 ip_addr = get_ip_addr(environ)
436 # make sure that we update permissions each time we call controller
436 # make sure that we update permissions each time we call controller
437 _auth_token = (request.GET.get('auth_token', '') or
437 _auth_token = (request.GET.get('auth_token', '') or
438 request.GET.get('api_key', ''))
438 request.GET.get('api_key', ''))
439
439
440 if _auth_token:
440 if _auth_token:
441 # when using API_KEY we assume user exists, and
441 # when using API_KEY we assume user exists, and
442 # doesn't need auth based on cookies.
442 # doesn't need auth based on cookies.
443 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
443 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
444 authenticated = False
444 authenticated = False
445 else:
445 else:
446 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
446 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
447 try:
447 try:
448 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
448 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
449 ip_addr=ip_addr)
449 ip_addr=ip_addr)
450 except UserCreationError as e:
450 except UserCreationError as e:
451 h.flash(e, 'error')
451 h.flash(e, 'error')
452 # container auth or other auth functions that create users
452 # container auth or other auth functions that create users
453 # on the fly can throw this exception signaling that there's
453 # on the fly can throw this exception signaling that there's
454 # issue with user creation, explanation should be provided
454 # issue with user creation, explanation should be provided
455 # in Exception itself. We then create a simple blank
455 # in Exception itself. We then create a simple blank
456 # AuthUser
456 # AuthUser
457 auth_user = AuthUser(ip_addr=ip_addr)
457 auth_user = AuthUser(ip_addr=ip_addr)
458
458
459 if password_changed(auth_user, session):
459 if password_changed(auth_user, session):
460 session.invalidate()
460 session.invalidate()
461 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
461 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
462 auth_user = AuthUser(ip_addr=ip_addr)
462 auth_user = AuthUser(ip_addr=ip_addr)
463
463
464 authenticated = cookie_store.get('is_authenticated')
464 authenticated = cookie_store.get('is_authenticated')
465
465
466 if not auth_user.is_authenticated and auth_user.is_user_object:
466 if not auth_user.is_authenticated and auth_user.is_user_object:
467 # user is not authenticated and not empty
467 # user is not authenticated and not empty
468 auth_user.set_authenticated(authenticated)
468 auth_user.set_authenticated(authenticated)
469
469
470 return auth_user
470 return auth_user
471
471
472
472
473 class BaseController(WSGIController):
473 class BaseController(WSGIController):
474
474
475 def __before__(self):
475 def __before__(self):
476 """
476 """
477 __before__ is called before controller methods and after __call__
477 __before__ is called before controller methods and after __call__
478 """
478 """
479 # on each call propagate settings calls into global settings.
479 # on each call propagate settings calls into global settings.
480 set_rhodecode_config(config)
480 set_rhodecode_config(config)
481 attach_context_attributes(c, request, self._rhodecode_user.user_id)
481 attach_context_attributes(c, request, self._rhodecode_user.user_id)
482
482
483 # TODO: Remove this when fixed in attach_context_attributes()
483 # TODO: Remove this when fixed in attach_context_attributes()
484 c.repo_name = get_repo_slug(request) # can be empty
484 c.repo_name = get_repo_slug(request) # can be empty
485
485
486 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
486 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
487 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
487 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
488 self.sa = meta.Session
488 self.sa = meta.Session
489 self.scm_model = ScmModel(self.sa)
489 self.scm_model = ScmModel(self.sa)
490
490
491 # set user language
491 # set user language
492 user_lang = getattr(c.pyramid_request, '_LOCALE_', None)
492 user_lang = getattr(c.pyramid_request, '_LOCALE_', None)
493 if user_lang:
493 if user_lang:
494 translation.set_lang(user_lang)
494 translation.set_lang(user_lang)
495 log.debug('set language to %s for user %s',
495 log.debug('set language to %s for user %s',
496 user_lang, self._rhodecode_user)
496 user_lang, self._rhodecode_user)
497
497
498 def _dispatch_redirect(self, with_url, environ, start_response):
498 def _dispatch_redirect(self, with_url, environ, start_response):
499 resp = HTTPFound(with_url)
499 resp = HTTPFound(with_url)
500 environ['SCRIPT_NAME'] = '' # handle prefix middleware
500 environ['SCRIPT_NAME'] = '' # handle prefix middleware
501 environ['PATH_INFO'] = with_url
501 environ['PATH_INFO'] = with_url
502 return resp(environ, start_response)
502 return resp(environ, start_response)
503
503
504 def __call__(self, environ, start_response):
504 def __call__(self, environ, start_response):
505 """Invoke the Controller"""
505 """Invoke the Controller"""
506 # WSGIController.__call__ dispatches to the Controller method
506 # WSGIController.__call__ dispatches to the Controller method
507 # the request is routed to. This routing information is
507 # the request is routed to. This routing information is
508 # available in environ['pylons.routes_dict']
508 # available in environ['pylons.routes_dict']
509 from rhodecode.lib import helpers as h
509 from rhodecode.lib import helpers as h
510
510
511 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
511 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
512 if environ.get('debugtoolbar.wants_pylons_context', False):
512 if environ.get('debugtoolbar.wants_pylons_context', False):
513 environ['debugtoolbar.pylons_context'] = c._current_obj()
513 environ['debugtoolbar.pylons_context'] = c._current_obj()
514
514
515 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
515 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
516 environ['pylons.routes_dict']['action']])
516 environ['pylons.routes_dict']['action']])
517
517
518 self.rc_config = SettingsModel().get_all_settings(cache=True)
518 self.rc_config = SettingsModel().get_all_settings(cache=True)
519 self.ip_addr = get_ip_addr(environ)
519 self.ip_addr = get_ip_addr(environ)
520
520
521 # The rhodecode auth user is looked up and passed through the
521 # The rhodecode auth user is looked up and passed through the
522 # environ by the pylons compatibility tween in pyramid.
522 # environ by the pylons compatibility tween in pyramid.
523 # So we can just grab it from there.
523 # So we can just grab it from there.
524 auth_user = environ['rc_auth_user']
524 auth_user = environ['rc_auth_user']
525
525
526 # set globals for auth user
526 # set globals for auth user
527 request.user = auth_user
527 request.user = auth_user
528 self._rhodecode_user = auth_user
528 self._rhodecode_user = auth_user
529
529
530 log.info('IP: %s User: %s accessed %s [%s]' % (
530 log.info('IP: %s User: %s accessed %s [%s]' % (
531 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
531 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
532 _route_name)
532 _route_name)
533 )
533 )
534
534
535 user_obj = auth_user.get_instance()
535 user_obj = auth_user.get_instance()
536 if user_obj and user_obj.user_data.get('force_password_change'):
536 if user_obj and user_obj.user_data.get('force_password_change'):
537 h.flash('You are required to change your password', 'warning',
537 h.flash('You are required to change your password', 'warning',
538 ignore_duplicate=True)
538 ignore_duplicate=True)
539 return self._dispatch_redirect(
539 return self._dispatch_redirect(
540 url('my_account_password'), environ, start_response)
540 url('my_account_password'), environ, start_response)
541
541
542 return WSGIController.__call__(self, environ, start_response)
542 return WSGIController.__call__(self, environ, start_response)
543
543
544
544
545 class BaseRepoController(BaseController):
545 class BaseRepoController(BaseController):
546 """
546 """
547 Base class for controllers responsible for loading all needed data for
547 Base class for controllers responsible for loading all needed data for
548 repository loaded items are
548 repository loaded items are
549
549
550 c.rhodecode_repo: instance of scm repository
550 c.rhodecode_repo: instance of scm repository
551 c.rhodecode_db_repo: instance of db
551 c.rhodecode_db_repo: instance of db
552 c.repository_requirements_missing: shows that repository specific data
552 c.repository_requirements_missing: shows that repository specific data
553 could not be displayed due to the missing requirements
553 could not be displayed due to the missing requirements
554 c.repository_pull_requests: show number of open pull requests
554 c.repository_pull_requests: show number of open pull requests
555 """
555 """
556
556
557 def __before__(self):
557 def __before__(self):
558 super(BaseRepoController, self).__before__()
558 super(BaseRepoController, self).__before__()
559 if c.repo_name: # extracted from routes
559 if c.repo_name: # extracted from routes
560 db_repo = Repository.get_by_repo_name(c.repo_name)
560 db_repo = Repository.get_by_repo_name(c.repo_name)
561 if not db_repo:
561 if not db_repo:
562 return
562 return
563
563
564 log.debug(
564 log.debug(
565 'Found repository in database %s with state `%s`',
565 'Found repository in database %s with state `%s`',
566 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
566 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
567 route = getattr(request.environ.get('routes.route'), 'name', '')
567 route = getattr(request.environ.get('routes.route'), 'name', '')
568
568
569 # allow to delete repos that are somehow damages in filesystem
569 # allow to delete repos that are somehow damages in filesystem
570 if route in ['delete_repo']:
570 if route in ['delete_repo']:
571 return
571 return
572
572
573 if db_repo.repo_state in [Repository.STATE_PENDING]:
573 if db_repo.repo_state in [Repository.STATE_PENDING]:
574 if route in ['repo_creating_home']:
574 if route in ['repo_creating_home']:
575 return
575 return
576 check_url = url('repo_creating_home', repo_name=c.repo_name)
576 check_url = url('repo_creating_home', repo_name=c.repo_name)
577 return redirect(check_url)
577 return redirect(check_url)
578
578
579 self.rhodecode_db_repo = db_repo
579 self.rhodecode_db_repo = db_repo
580
580
581 missing_requirements = False
581 missing_requirements = False
582 try:
582 try:
583 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
583 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
584 except RepositoryRequirementError as e:
584 except RepositoryRequirementError as e:
585 missing_requirements = True
585 missing_requirements = True
586 self._handle_missing_requirements(e)
586 self._handle_missing_requirements(e)
587
587
588 if self.rhodecode_repo is None and not missing_requirements:
588 if self.rhodecode_repo is None and not missing_requirements:
589 log.error('%s this repository is present in database but it '
589 log.error('%s this repository is present in database but it '
590 'cannot be created as an scm instance', c.repo_name)
590 'cannot be created as an scm instance', c.repo_name)
591
591
592 h.flash(_(
592 h.flash(_(
593 "The repository at %(repo_name)s cannot be located.") %
593 "The repository at %(repo_name)s cannot be located.") %
594 {'repo_name': c.repo_name},
594 {'repo_name': c.repo_name},
595 category='error', ignore_duplicate=True)
595 category='error', ignore_duplicate=True)
596 redirect(h.route_path('home'))
596 redirect(h.route_path('home'))
597
597
598 # update last change according to VCS data
598 # update last change according to VCS data
599 if not missing_requirements:
599 if not missing_requirements:
600 commit = db_repo.get_commit(
600 commit = db_repo.get_commit(
601 pre_load=["author", "date", "message", "parents"])
601 pre_load=["author", "date", "message", "parents"])
602 db_repo.update_commit_cache(commit)
602 db_repo.update_commit_cache(commit)
603
603
604 # Prepare context
604 # Prepare context
605 c.rhodecode_db_repo = db_repo
605 c.rhodecode_db_repo = db_repo
606 c.rhodecode_repo = self.rhodecode_repo
606 c.rhodecode_repo = self.rhodecode_repo
607 c.repository_requirements_missing = missing_requirements
607 c.repository_requirements_missing = missing_requirements
608
608
609 self._update_global_counters(self.scm_model, db_repo)
609 self._update_global_counters(self.scm_model, db_repo)
610
610
611 def _update_global_counters(self, scm_model, db_repo):
611 def _update_global_counters(self, scm_model, db_repo):
612 """
612 """
613 Base variables that are exposed to every page of repository
613 Base variables that are exposed to every page of repository
614 """
614 """
615 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
615 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
616
616
617 def _handle_missing_requirements(self, error):
617 def _handle_missing_requirements(self, error):
618 self.rhodecode_repo = None
618 self.rhodecode_repo = None
619 log.error(
619 log.error(
620 'Requirements are missing for repository %s: %s',
620 'Requirements are missing for repository %s: %s',
621 c.repo_name, error.message)
621 c.repo_name, error.message)
622
622
623 summary_url = h.route_path('repo_summary', repo_name=c.repo_name)
623 summary_url = h.route_path('repo_summary', repo_name=c.repo_name)
624 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
624 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
625 settings_update_url = url('repo', repo_name=c.repo_name)
625 settings_update_url = url('repo', repo_name=c.repo_name)
626 path = request.path
626 path = request.path
627 should_redirect = (
627 should_redirect = (
628 path not in (summary_url, settings_update_url)
628 path not in (summary_url, settings_update_url)
629 and '/settings' not in path or path == statistics_url
629 and '/settings' not in path or path == statistics_url
630 )
630 )
631 if should_redirect:
631 if should_redirect:
632 redirect(summary_url)
632 redirect(summary_url)
General Comments 0
You need to be logged in to leave comments. Login now