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