##// END OF EJS Templates
core: removed usage of global pylons config in base lib.
marcink -
r2000:9e69eb38 default
parent child Browse files
Show More
@@ -1,141 +1,142 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 import pytest
22 import pytest
23
23
24 import rhodecode
24 import rhodecode
25 from rhodecode.model.db import Repository
25 from rhodecode.model.db import Repository
26 from rhodecode.model.meta import Session
26 from rhodecode.model.meta import Session
27 from rhodecode.model.repo import RepoModel
27 from rhodecode.model.repo import RepoModel
28 from rhodecode.model.repo_group import RepoGroupModel
28 from rhodecode.model.repo_group import RepoGroupModel
29 from rhodecode.model.settings import SettingsModel
29 from rhodecode.model.settings import SettingsModel
30 from rhodecode.tests import TestController
30 from rhodecode.tests import TestController
31 from rhodecode.tests.fixture import Fixture
31 from rhodecode.tests.fixture import Fixture
32 from rhodecode.lib import helpers as h
32 from rhodecode.lib import helpers as h
33
33
34 fixture = Fixture()
34 fixture = Fixture()
35
35
36
36
37 def route_path(name, **kwargs):
37 def route_path(name, **kwargs):
38 return {
38 return {
39 'home': '/',
39 'home': '/',
40 'repo_group_home': '/{repo_group_name}'
40 'repo_group_home': '/{repo_group_name}'
41 }[name].format(**kwargs)
41 }[name].format(**kwargs)
42
42
43
43
44 class TestHomeController(TestController):
44 class TestHomeController(TestController):
45
45
46 def test_index(self):
46 def test_index(self):
47 self.log_user()
47 self.log_user()
48 response = self.app.get(route_path('home'))
48 response = self.app.get(route_path('home'))
49 # if global permission is set
49 # if global permission is set
50 response.mustcontain('Add Repository')
50 response.mustcontain('Add Repository')
51
51
52 # search for objects inside the JavaScript JSON
52 # search for objects inside the JavaScript JSON
53 for repo in Repository.getAll():
53 for repo in Repository.getAll():
54 response.mustcontain('"name_raw": "%s"' % repo.repo_name)
54 response.mustcontain('"name_raw": "%s"' % repo.repo_name)
55
55
56 def test_index_contains_statics_with_ver(self):
56 def test_index_contains_statics_with_ver(self):
57 from rhodecode.lib.base import calculate_version_hash
57 from rhodecode.lib.base import calculate_version_hash
58
58
59 self.log_user()
59 self.log_user()
60 response = self.app.get(route_path('home'))
60 response = self.app.get(route_path('home'))
61
61
62 rhodecode_version_hash = calculate_version_hash()
62 rhodecode_version_hash = calculate_version_hash(
63 {'beaker.session.secret':'test-rc-uytcxaz'})
63 response.mustcontain('style.css?ver={0}'.format(rhodecode_version_hash))
64 response.mustcontain('style.css?ver={0}'.format(rhodecode_version_hash))
64 response.mustcontain('rhodecode-components.js?ver={0}'.format(
65 response.mustcontain('rhodecode-components.js?ver={0}'.format(
65 rhodecode_version_hash))
66 rhodecode_version_hash))
66
67
67 def test_index_contains_backend_specific_details(self, backend):
68 def test_index_contains_backend_specific_details(self, backend):
68 self.log_user()
69 self.log_user()
69 response = self.app.get(route_path('home'))
70 response = self.app.get(route_path('home'))
70 tip = backend.repo.get_commit().raw_id
71 tip = backend.repo.get_commit().raw_id
71
72
72 # html in javascript variable:
73 # html in javascript variable:
73 response.mustcontain(r'<i class=\"icon-%s\"' % (backend.alias, ))
74 response.mustcontain(r'<i class=\"icon-%s\"' % (backend.alias, ))
74 response.mustcontain(r'href=\"/%s\"' % (backend.repo_name, ))
75 response.mustcontain(r'href=\"/%s\"' % (backend.repo_name, ))
75
76
76 response.mustcontain("""/%s/changeset/%s""" % (backend.repo_name, tip))
77 response.mustcontain("""/%s/changeset/%s""" % (backend.repo_name, tip))
77 response.mustcontain("""Added a symlink""")
78 response.mustcontain("""Added a symlink""")
78
79
79 def test_index_with_anonymous_access_disabled(self):
80 def test_index_with_anonymous_access_disabled(self):
80 with fixture.anon_access(False):
81 with fixture.anon_access(False):
81 response = self.app.get(route_path('home'), status=302)
82 response = self.app.get(route_path('home'), status=302)
82 assert 'login' in response.location
83 assert 'login' in response.location
83
84
84 def test_index_page_on_groups(self, autologin_user, repo_group):
85 def test_index_page_on_groups(self, autologin_user, repo_group):
85 response = self.app.get(route_path('repo_group_home', repo_group_name='gr1'))
86 response = self.app.get(route_path('repo_group_home', repo_group_name='gr1'))
86 response.mustcontain("gr1/repo_in_group")
87 response.mustcontain("gr1/repo_in_group")
87
88
88 def test_index_page_on_group_with_trailing_slash(
89 def test_index_page_on_group_with_trailing_slash(
89 self, autologin_user, repo_group):
90 self, autologin_user, repo_group):
90 response = self.app.get(route_path('repo_group_home', repo_group_name='gr1') + '/')
91 response = self.app.get(route_path('repo_group_home', repo_group_name='gr1') + '/')
91 response.mustcontain("gr1/repo_in_group")
92 response.mustcontain("gr1/repo_in_group")
92
93
93 @pytest.fixture(scope='class')
94 @pytest.fixture(scope='class')
94 def repo_group(self, request):
95 def repo_group(self, request):
95 gr = fixture.create_repo_group('gr1')
96 gr = fixture.create_repo_group('gr1')
96 fixture.create_repo(name='gr1/repo_in_group', repo_group=gr)
97 fixture.create_repo(name='gr1/repo_in_group', repo_group=gr)
97
98
98 @request.addfinalizer
99 @request.addfinalizer
99 def cleanup():
100 def cleanup():
100 RepoModel().delete('gr1/repo_in_group')
101 RepoModel().delete('gr1/repo_in_group')
101 RepoGroupModel().delete(repo_group='gr1', force_delete=True)
102 RepoGroupModel().delete(repo_group='gr1', force_delete=True)
102 Session().commit()
103 Session().commit()
103
104
104 def test_index_with_name_with_tags(self, user_util, autologin_user):
105 def test_index_with_name_with_tags(self, user_util, autologin_user):
105 user = user_util.create_user()
106 user = user_util.create_user()
106 username = user.username
107 username = user.username
107 user.name = '<img src="/image1" onload="alert(\'Hello, World!\');">'
108 user.name = '<img src="/image1" onload="alert(\'Hello, World!\');">'
108 user.lastname = '#"><img src=x onerror=prompt(document.cookie);>'
109 user.lastname = '#"><img src=x onerror=prompt(document.cookie);>'
109
110
110 Session().add(user)
111 Session().add(user)
111 Session().commit()
112 Session().commit()
112 user_util.create_repo(owner=username)
113 user_util.create_repo(owner=username)
113
114
114 response = self.app.get(route_path('home'))
115 response = self.app.get(route_path('home'))
115 response.mustcontain(h.html_escape(user.first_name))
116 response.mustcontain(h.html_escape(user.first_name))
116 response.mustcontain(h.html_escape(user.last_name))
117 response.mustcontain(h.html_escape(user.last_name))
117
118
118 @pytest.mark.parametrize("name, state", [
119 @pytest.mark.parametrize("name, state", [
119 ('Disabled', False),
120 ('Disabled', False),
120 ('Enabled', True),
121 ('Enabled', True),
121 ])
122 ])
122 def test_index_show_version(self, autologin_user, name, state):
123 def test_index_show_version(self, autologin_user, name, state):
123 version_string = 'RhodeCode Enterprise %s' % rhodecode.__version__
124 version_string = 'RhodeCode Enterprise %s' % rhodecode.__version__
124
125
125 sett = SettingsModel().create_or_update_setting(
126 sett = SettingsModel().create_or_update_setting(
126 'show_version', state, 'bool')
127 'show_version', state, 'bool')
127 Session().add(sett)
128 Session().add(sett)
128 Session().commit()
129 Session().commit()
129 SettingsModel().invalidate_settings_cache()
130 SettingsModel().invalidate_settings_cache()
130
131
131 response = self.app.get(route_path('home'))
132 response = self.app.get(route_path('home'))
132 if state is True:
133 if state is True:
133 response.mustcontain(version_string)
134 response.mustcontain(version_string)
134 if state is False:
135 if state is False:
135 response.mustcontain(no=[version_string])
136 response.mustcontain(no=[version_string])
136
137
137 def test_logout_form_contains_csrf(self, autologin_user, csrf_token):
138 def test_logout_form_contains_csrf(self, autologin_user, csrf_token):
138 response = self.app.get(route_path('home'))
139 response = self.app.get(route_path('home'))
139 assert_response = response.assert_response()
140 assert_response = response.assert_response()
140 element = assert_response.get_element('.logout #csrf_token')
141 element = assert_response.get_element('.logout #csrf_token')
141 assert element.value == csrf_token
142 assert element.value == csrf_token
@@ -1,706 +1,712 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 markupsafe
30 import markupsafe
31 import ipaddress
31 import ipaddress
32 import pyramid.threadlocal
32 import pyramid.threadlocal
33
33
34 from paste.auth.basic import AuthBasicAuthenticator
34 from paste.auth.basic import AuthBasicAuthenticator
35 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
35 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
36 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
36 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
37 from pylons import config, tmpl_context as c, request, url
37 from pylons import tmpl_context as c, request, url
38 from pylons.controllers import WSGIController
38 from pylons.controllers import WSGIController
39 from pylons.controllers.util import redirect
39 from pylons.controllers.util import redirect
40 from pylons.i18n import translation
40 from pylons.i18n import translation
41 # marcink: don't remove this import
41 # marcink: don't remove this import
42 from pylons.templating import render_mako, pylons_globals, literal, cached_template
42 from pylons.templating import render_mako, pylons_globals, literal, cached_template
43 from pylons.i18n.translation import _
43 from pylons.i18n.translation import _
44 from webob.exc import HTTPFound
44 from webob.exc import HTTPFound
45
45
46
46
47 import rhodecode
47 import rhodecode
48 from rhodecode.authentication.base import VCS_TYPE
48 from rhodecode.authentication.base import VCS_TYPE
49 from rhodecode.lib import auth, utils2
49 from rhodecode.lib import auth, utils2
50 from rhodecode.lib import helpers as h
50 from rhodecode.lib import helpers as h
51 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
51 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
52 from rhodecode.lib.exceptions import UserCreationError
52 from rhodecode.lib.exceptions import UserCreationError
53 from rhodecode.lib.utils import (
53 from rhodecode.lib.utils import (
54 get_repo_slug, set_rhodecode_config, password_changed,
54 get_repo_slug, set_rhodecode_config, password_changed,
55 get_enabled_hook_classes)
55 get_enabled_hook_classes)
56 from rhodecode.lib.utils2 import (
56 from rhodecode.lib.utils2 import (
57 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist)
57 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist)
58 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
58 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
59 from rhodecode.model import meta
59 from rhodecode.model import meta
60 from rhodecode.model.db import Repository, User, ChangesetComment
60 from rhodecode.model.db import Repository, User, ChangesetComment
61 from rhodecode.model.notification import NotificationModel
61 from rhodecode.model.notification import NotificationModel
62 from rhodecode.model.scm import ScmModel
62 from rhodecode.model.scm import ScmModel
63 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
63 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
64
64
65
65
66 log = logging.getLogger(__name__)
66 log = logging.getLogger(__name__)
67
67
68
68
69 # hack to make the migration to pyramid easier
69 # hack to make the migration to pyramid easier
70 def render(template_name, extra_vars=None, cache_key=None,
70 def render(template_name, extra_vars=None, cache_key=None,
71 cache_type=None, cache_expire=None):
71 cache_type=None, cache_expire=None):
72 """Render a template with Mako
72 """Render a template with Mako
73
73
74 Accepts the cache options ``cache_key``, ``cache_type``, and
74 Accepts the cache options ``cache_key``, ``cache_type``, and
75 ``cache_expire``.
75 ``cache_expire``.
76
76
77 """
77 """
78 # Create a render callable for the cache function
78 # Create a render callable for the cache function
79 def render_template():
79 def render_template():
80 # Pull in extra vars if needed
80 # Pull in extra vars if needed
81 globs = extra_vars or {}
81 globs = extra_vars or {}
82
82
83 # Second, get the globals
83 # Second, get the globals
84 globs.update(pylons_globals())
84 globs.update(pylons_globals())
85
85
86 globs['_ungettext'] = globs['ungettext']
86 globs['_ungettext'] = globs['ungettext']
87 # Grab a template reference
87 # Grab a template reference
88 template = globs['app_globals'].mako_lookup.get_template(template_name)
88 template = globs['app_globals'].mako_lookup.get_template(template_name)
89
89
90 return literal(template.render_unicode(**globs))
90 return literal(template.render_unicode(**globs))
91
91
92 return cached_template(template_name, render_template, cache_key=cache_key,
92 return cached_template(template_name, render_template, cache_key=cache_key,
93 cache_type=cache_type, cache_expire=cache_expire)
93 cache_type=cache_type, cache_expire=cache_expire)
94
94
95 def _filter_proxy(ip):
95 def _filter_proxy(ip):
96 """
96 """
97 Passed in IP addresses in HEADERS can be in a special format of multiple
97 Passed in IP addresses in HEADERS can be in a special format of multiple
98 ips. Those comma separated IPs are passed from various proxies in the
98 ips. Those comma separated IPs are passed from various proxies in the
99 chain of request processing. The left-most being the original client.
99 chain of request processing. The left-most being the original client.
100 We only care about the first IP which came from the org. client.
100 We only care about the first IP which came from the org. client.
101
101
102 :param ip: ip string from headers
102 :param ip: ip string from headers
103 """
103 """
104 if ',' in ip:
104 if ',' in ip:
105 _ips = ip.split(',')
105 _ips = ip.split(',')
106 _first_ip = _ips[0].strip()
106 _first_ip = _ips[0].strip()
107 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
107 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
108 return _first_ip
108 return _first_ip
109 return ip
109 return ip
110
110
111
111
112 def _filter_port(ip):
112 def _filter_port(ip):
113 """
113 """
114 Removes a port from ip, there are 4 main cases to handle here.
114 Removes a port from ip, there are 4 main cases to handle here.
115 - ipv4 eg. 127.0.0.1
115 - ipv4 eg. 127.0.0.1
116 - ipv6 eg. ::1
116 - ipv6 eg. ::1
117 - ipv4+port eg. 127.0.0.1:8080
117 - ipv4+port eg. 127.0.0.1:8080
118 - ipv6+port eg. [::1]:8080
118 - ipv6+port eg. [::1]:8080
119
119
120 :param ip:
120 :param ip:
121 """
121 """
122 def is_ipv6(ip_addr):
122 def is_ipv6(ip_addr):
123 if hasattr(socket, 'inet_pton'):
123 if hasattr(socket, 'inet_pton'):
124 try:
124 try:
125 socket.inet_pton(socket.AF_INET6, ip_addr)
125 socket.inet_pton(socket.AF_INET6, ip_addr)
126 except socket.error:
126 except socket.error:
127 return False
127 return False
128 else:
128 else:
129 # fallback to ipaddress
129 # fallback to ipaddress
130 try:
130 try:
131 ipaddress.IPv6Address(safe_unicode(ip_addr))
131 ipaddress.IPv6Address(safe_unicode(ip_addr))
132 except Exception:
132 except Exception:
133 return False
133 return False
134 return True
134 return True
135
135
136 if ':' not in ip: # must be ipv4 pure ip
136 if ':' not in ip: # must be ipv4 pure ip
137 return ip
137 return ip
138
138
139 if '[' in ip and ']' in ip: # ipv6 with port
139 if '[' in ip and ']' in ip: # ipv6 with port
140 return ip.split(']')[0][1:].lower()
140 return ip.split(']')[0][1:].lower()
141
141
142 # must be ipv6 or ipv4 with port
142 # must be ipv6 or ipv4 with port
143 if is_ipv6(ip):
143 if is_ipv6(ip):
144 return ip
144 return ip
145 else:
145 else:
146 ip, _port = ip.split(':')[:2] # means ipv4+port
146 ip, _port = ip.split(':')[:2] # means ipv4+port
147 return ip
147 return ip
148
148
149
149
150 def get_ip_addr(environ):
150 def get_ip_addr(environ):
151 proxy_key = 'HTTP_X_REAL_IP'
151 proxy_key = 'HTTP_X_REAL_IP'
152 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
152 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
153 def_key = 'REMOTE_ADDR'
153 def_key = 'REMOTE_ADDR'
154 _filters = lambda x: _filter_port(_filter_proxy(x))
154 _filters = lambda x: _filter_port(_filter_proxy(x))
155
155
156 ip = environ.get(proxy_key)
156 ip = environ.get(proxy_key)
157 if ip:
157 if ip:
158 return _filters(ip)
158 return _filters(ip)
159
159
160 ip = environ.get(proxy_key2)
160 ip = environ.get(proxy_key2)
161 if ip:
161 if ip:
162 return _filters(ip)
162 return _filters(ip)
163
163
164 ip = environ.get(def_key, '0.0.0.0')
164 ip = environ.get(def_key, '0.0.0.0')
165 return _filters(ip)
165 return _filters(ip)
166
166
167
167
168 def get_server_ip_addr(environ, log_errors=True):
168 def get_server_ip_addr(environ, log_errors=True):
169 hostname = environ.get('SERVER_NAME')
169 hostname = environ.get('SERVER_NAME')
170 try:
170 try:
171 return socket.gethostbyname(hostname)
171 return socket.gethostbyname(hostname)
172 except Exception as e:
172 except Exception as e:
173 if log_errors:
173 if log_errors:
174 # in some cases this lookup is not possible, and we don't want to
174 # in some cases this lookup is not possible, and we don't want to
175 # make it an exception in logs
175 # make it an exception in logs
176 log.exception('Could not retrieve server ip address: %s', e)
176 log.exception('Could not retrieve server ip address: %s', e)
177 return hostname
177 return hostname
178
178
179
179
180 def get_server_port(environ):
180 def get_server_port(environ):
181 return environ.get('SERVER_PORT')
181 return environ.get('SERVER_PORT')
182
182
183
183
184 def get_access_path(environ):
184 def get_access_path(environ):
185 path = environ.get('PATH_INFO')
185 path = environ.get('PATH_INFO')
186 org_req = environ.get('pylons.original_request')
186 org_req = environ.get('pylons.original_request')
187 if org_req:
187 if org_req:
188 path = org_req.environ.get('PATH_INFO')
188 path = org_req.environ.get('PATH_INFO')
189 return path
189 return path
190
190
191
191
192 def get_user_agent(environ):
192 def get_user_agent(environ):
193 return environ.get('HTTP_USER_AGENT')
193 return environ.get('HTTP_USER_AGENT')
194
194
195
195
196 def vcs_operation_context(
196 def vcs_operation_context(
197 environ, repo_name, username, action, scm, check_locking=True,
197 environ, repo_name, username, action, scm, check_locking=True,
198 is_shadow_repo=False):
198 is_shadow_repo=False):
199 """
199 """
200 Generate the context for a vcs operation, e.g. push or pull.
200 Generate the context for a vcs operation, e.g. push or pull.
201
201
202 This context is passed over the layers so that hooks triggered by the
202 This context is passed over the layers so that hooks triggered by the
203 vcs operation know details like the user, the user's IP address etc.
203 vcs operation know details like the user, the user's IP address etc.
204
204
205 :param check_locking: Allows to switch of the computation of the locking
205 :param check_locking: Allows to switch of the computation of the locking
206 data. This serves mainly the need of the simplevcs middleware to be
206 data. This serves mainly the need of the simplevcs middleware to be
207 able to disable this for certain operations.
207 able to disable this for certain operations.
208
208
209 """
209 """
210 # Tri-state value: False: unlock, None: nothing, True: lock
210 # Tri-state value: False: unlock, None: nothing, True: lock
211 make_lock = None
211 make_lock = None
212 locked_by = [None, None, None]
212 locked_by = [None, None, None]
213 is_anonymous = username == User.DEFAULT_USER
213 is_anonymous = username == User.DEFAULT_USER
214 if not is_anonymous and check_locking:
214 if not is_anonymous and check_locking:
215 log.debug('Checking locking on repository "%s"', repo_name)
215 log.debug('Checking locking on repository "%s"', repo_name)
216 user = User.get_by_username(username)
216 user = User.get_by_username(username)
217 repo = Repository.get_by_repo_name(repo_name)
217 repo = Repository.get_by_repo_name(repo_name)
218 make_lock, __, locked_by = repo.get_locking_state(
218 make_lock, __, locked_by = repo.get_locking_state(
219 action, user.user_id)
219 action, user.user_id)
220
220
221 settings_model = VcsSettingsModel(repo=repo_name)
221 settings_model = VcsSettingsModel(repo=repo_name)
222 ui_settings = settings_model.get_ui_settings()
222 ui_settings = settings_model.get_ui_settings()
223
223
224 extras = {
224 extras = {
225 'ip': get_ip_addr(environ),
225 'ip': get_ip_addr(environ),
226 'username': username,
226 'username': username,
227 'action': action,
227 'action': action,
228 'repository': repo_name,
228 'repository': repo_name,
229 'scm': scm,
229 'scm': scm,
230 'config': rhodecode.CONFIG['__file__'],
230 'config': rhodecode.CONFIG['__file__'],
231 'make_lock': make_lock,
231 'make_lock': make_lock,
232 'locked_by': locked_by,
232 'locked_by': locked_by,
233 'server_url': utils2.get_server_url(environ),
233 'server_url': utils2.get_server_url(environ),
234 'user_agent': get_user_agent(environ),
234 'user_agent': get_user_agent(environ),
235 'hooks': get_enabled_hook_classes(ui_settings),
235 'hooks': get_enabled_hook_classes(ui_settings),
236 'is_shadow_repo': is_shadow_repo,
236 'is_shadow_repo': is_shadow_repo,
237 }
237 }
238 return extras
238 return extras
239
239
240
240
241 class BasicAuth(AuthBasicAuthenticator):
241 class BasicAuth(AuthBasicAuthenticator):
242
242
243 def __init__(self, realm, authfunc, registry, auth_http_code=None,
243 def __init__(self, realm, authfunc, registry, auth_http_code=None,
244 initial_call_detection=False, acl_repo_name=None):
244 initial_call_detection=False, acl_repo_name=None):
245 self.realm = realm
245 self.realm = realm
246 self.initial_call = initial_call_detection
246 self.initial_call = initial_call_detection
247 self.authfunc = authfunc
247 self.authfunc = authfunc
248 self.registry = registry
248 self.registry = registry
249 self.acl_repo_name = acl_repo_name
249 self.acl_repo_name = acl_repo_name
250 self._rc_auth_http_code = auth_http_code
250 self._rc_auth_http_code = auth_http_code
251
251
252 def _get_response_from_code(self, http_code):
252 def _get_response_from_code(self, http_code):
253 try:
253 try:
254 return get_exception(safe_int(http_code))
254 return get_exception(safe_int(http_code))
255 except Exception:
255 except Exception:
256 log.exception('Failed to fetch response for code %s' % http_code)
256 log.exception('Failed to fetch response for code %s' % http_code)
257 return HTTPForbidden
257 return HTTPForbidden
258
258
259 def build_authentication(self):
259 def build_authentication(self):
260 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
260 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
261 if self._rc_auth_http_code and not self.initial_call:
261 if self._rc_auth_http_code and not self.initial_call:
262 # return alternative HTTP code if alternative http return code
262 # return alternative HTTP code if alternative http return code
263 # is specified in RhodeCode config, but ONLY if it's not the
263 # is specified in RhodeCode config, but ONLY if it's not the
264 # FIRST call
264 # FIRST call
265 custom_response_klass = self._get_response_from_code(
265 custom_response_klass = self._get_response_from_code(
266 self._rc_auth_http_code)
266 self._rc_auth_http_code)
267 return custom_response_klass(headers=head)
267 return custom_response_klass(headers=head)
268 return HTTPUnauthorized(headers=head)
268 return HTTPUnauthorized(headers=head)
269
269
270 def authenticate(self, environ):
270 def authenticate(self, environ):
271 authorization = AUTHORIZATION(environ)
271 authorization = AUTHORIZATION(environ)
272 if not authorization:
272 if not authorization:
273 return self.build_authentication()
273 return self.build_authentication()
274 (authmeth, auth) = authorization.split(' ', 1)
274 (authmeth, auth) = authorization.split(' ', 1)
275 if 'basic' != authmeth.lower():
275 if 'basic' != authmeth.lower():
276 return self.build_authentication()
276 return self.build_authentication()
277 auth = auth.strip().decode('base64')
277 auth = auth.strip().decode('base64')
278 _parts = auth.split(':', 1)
278 _parts = auth.split(':', 1)
279 if len(_parts) == 2:
279 if len(_parts) == 2:
280 username, password = _parts
280 username, password = _parts
281 if self.authfunc(
281 if self.authfunc(
282 username, password, environ, VCS_TYPE,
282 username, password, environ, VCS_TYPE,
283 registry=self.registry, acl_repo_name=self.acl_repo_name):
283 registry=self.registry, acl_repo_name=self.acl_repo_name):
284 return username
284 return username
285 if username and password:
285 if username and password:
286 # we mark that we actually executed authentication once, at
286 # we mark that we actually executed authentication once, at
287 # that point we can use the alternative auth code
287 # that point we can use the alternative auth code
288 self.initial_call = False
288 self.initial_call = False
289
289
290 return self.build_authentication()
290 return self.build_authentication()
291
291
292 __call__ = authenticate
292 __call__ = authenticate
293
293
294
294
295 def calculate_version_hash():
295 def calculate_version_hash(config):
296 return md5(
296 return md5(
297 config.get('beaker.session.secret', '') +
297 config.get('beaker.session.secret', '') +
298 rhodecode.__version__)[:8]
298 rhodecode.__version__)[:8]
299
299
300
300
301 def get_current_lang(request):
301 def get_current_lang(request):
302 # NOTE(marcink): remove after pyramid move
302 # NOTE(marcink): remove after pyramid move
303 try:
303 try:
304 return translation.get_lang()[0]
304 return translation.get_lang()[0]
305 except:
305 except:
306 pass
306 pass
307
307
308 return getattr(request, '_LOCALE_', request.locale_name)
308 return getattr(request, '_LOCALE_', request.locale_name)
309
309
310
310
311 def attach_context_attributes(context, request, user_id):
311 def attach_context_attributes(context, request, user_id):
312 """
312 """
313 Attach variables into template context called `c`, please note that
313 Attach variables into template context called `c`, please note that
314 request could be pylons or pyramid request in here.
314 request could be pylons or pyramid request in here.
315 """
315 """
316 # NOTE(marcink): remove check after pyramid migration
317 if hasattr(request, 'registry'):
318 config = request.registry.settings
319 else:
320 from pylons import config
316
321
317 rc_config = SettingsModel().get_all_settings(cache=True)
322 rc_config = SettingsModel().get_all_settings(cache=True)
318
323
319 context.rhodecode_version = rhodecode.__version__
324 context.rhodecode_version = rhodecode.__version__
320 context.rhodecode_edition = config.get('rhodecode.edition')
325 context.rhodecode_edition = config.get('rhodecode.edition')
321 # unique secret + version does not leak the version but keep consistency
326 # unique secret + version does not leak the version but keep consistency
322 context.rhodecode_version_hash = calculate_version_hash()
327 context.rhodecode_version_hash = calculate_version_hash(config)
323
328
324 # Default language set for the incoming request
329 # Default language set for the incoming request
325 context.language = get_current_lang(request)
330 context.language = get_current_lang(request)
326
331
327 # Visual options
332 # Visual options
328 context.visual = AttributeDict({})
333 context.visual = AttributeDict({})
329
334
330 # DB stored Visual Items
335 # DB stored Visual Items
331 context.visual.show_public_icon = str2bool(
336 context.visual.show_public_icon = str2bool(
332 rc_config.get('rhodecode_show_public_icon'))
337 rc_config.get('rhodecode_show_public_icon'))
333 context.visual.show_private_icon = str2bool(
338 context.visual.show_private_icon = str2bool(
334 rc_config.get('rhodecode_show_private_icon'))
339 rc_config.get('rhodecode_show_private_icon'))
335 context.visual.stylify_metatags = str2bool(
340 context.visual.stylify_metatags = str2bool(
336 rc_config.get('rhodecode_stylify_metatags'))
341 rc_config.get('rhodecode_stylify_metatags'))
337 context.visual.dashboard_items = safe_int(
342 context.visual.dashboard_items = safe_int(
338 rc_config.get('rhodecode_dashboard_items', 100))
343 rc_config.get('rhodecode_dashboard_items', 100))
339 context.visual.admin_grid_items = safe_int(
344 context.visual.admin_grid_items = safe_int(
340 rc_config.get('rhodecode_admin_grid_items', 100))
345 rc_config.get('rhodecode_admin_grid_items', 100))
341 context.visual.repository_fields = str2bool(
346 context.visual.repository_fields = str2bool(
342 rc_config.get('rhodecode_repository_fields'))
347 rc_config.get('rhodecode_repository_fields'))
343 context.visual.show_version = str2bool(
348 context.visual.show_version = str2bool(
344 rc_config.get('rhodecode_show_version'))
349 rc_config.get('rhodecode_show_version'))
345 context.visual.use_gravatar = str2bool(
350 context.visual.use_gravatar = str2bool(
346 rc_config.get('rhodecode_use_gravatar'))
351 rc_config.get('rhodecode_use_gravatar'))
347 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
352 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
348 context.visual.default_renderer = rc_config.get(
353 context.visual.default_renderer = rc_config.get(
349 'rhodecode_markup_renderer', 'rst')
354 'rhodecode_markup_renderer', 'rst')
350 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
355 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
351 context.visual.rhodecode_support_url = \
356 context.visual.rhodecode_support_url = \
352 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
357 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
353
358
354 context.visual.affected_files_cut_off = 60
359 context.visual.affected_files_cut_off = 60
355
360
356 context.pre_code = rc_config.get('rhodecode_pre_code')
361 context.pre_code = rc_config.get('rhodecode_pre_code')
357 context.post_code = rc_config.get('rhodecode_post_code')
362 context.post_code = rc_config.get('rhodecode_post_code')
358 context.rhodecode_name = rc_config.get('rhodecode_title')
363 context.rhodecode_name = rc_config.get('rhodecode_title')
359 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
364 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
360 # if we have specified default_encoding in the request, it has more
365 # if we have specified default_encoding in the request, it has more
361 # priority
366 # priority
362 if request.GET.get('default_encoding'):
367 if request.GET.get('default_encoding'):
363 context.default_encodings.insert(0, request.GET.get('default_encoding'))
368 context.default_encodings.insert(0, request.GET.get('default_encoding'))
364 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
369 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
365
370
366 # INI stored
371 # INI stored
367 context.labs_active = str2bool(
372 context.labs_active = str2bool(
368 config.get('labs_settings_active', 'false'))
373 config.get('labs_settings_active', 'false'))
369 context.visual.allow_repo_location_change = str2bool(
374 context.visual.allow_repo_location_change = str2bool(
370 config.get('allow_repo_location_change', True))
375 config.get('allow_repo_location_change', True))
371 context.visual.allow_custom_hooks_settings = str2bool(
376 context.visual.allow_custom_hooks_settings = str2bool(
372 config.get('allow_custom_hooks_settings', True))
377 config.get('allow_custom_hooks_settings', True))
373 context.debug_style = str2bool(config.get('debug_style', False))
378 context.debug_style = str2bool(config.get('debug_style', False))
374
379
375 context.rhodecode_instanceid = config.get('instance_id')
380 context.rhodecode_instanceid = config.get('instance_id')
376
381
377 context.visual.cut_off_limit_diff = safe_int(
382 context.visual.cut_off_limit_diff = safe_int(
378 config.get('cut_off_limit_diff'))
383 config.get('cut_off_limit_diff'))
379 context.visual.cut_off_limit_file = safe_int(
384 context.visual.cut_off_limit_file = safe_int(
380 config.get('cut_off_limit_file'))
385 config.get('cut_off_limit_file'))
381
386
382 # AppEnlight
387 # AppEnlight
383 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
388 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
384 context.appenlight_api_public_key = config.get(
389 context.appenlight_api_public_key = config.get(
385 'appenlight.api_public_key', '')
390 'appenlight.api_public_key', '')
386 context.appenlight_server_url = config.get('appenlight.server_url', '')
391 context.appenlight_server_url = config.get('appenlight.server_url', '')
387
392
388 # JS template context
393 # JS template context
389 context.template_context = {
394 context.template_context = {
390 'repo_name': None,
395 'repo_name': None,
391 'repo_type': None,
396 'repo_type': None,
392 'repo_landing_commit': None,
397 'repo_landing_commit': None,
393 'rhodecode_user': {
398 'rhodecode_user': {
394 'username': None,
399 'username': None,
395 'email': None,
400 'email': None,
396 'notification_status': False
401 'notification_status': False
397 },
402 },
398 'visual': {
403 'visual': {
399 'default_renderer': None
404 'default_renderer': None
400 },
405 },
401 'commit_data': {
406 'commit_data': {
402 'commit_id': None
407 'commit_id': None
403 },
408 },
404 'pull_request_data': {'pull_request_id': None},
409 'pull_request_data': {'pull_request_id': None},
405 'timeago': {
410 'timeago': {
406 'refresh_time': 120 * 1000,
411 'refresh_time': 120 * 1000,
407 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
412 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
408 },
413 },
409 'pylons_dispatch': {
414 'pylons_dispatch': {
410 # 'controller': request.environ['pylons.routes_dict']['controller'],
415 # 'controller': request.environ['pylons.routes_dict']['controller'],
411 # 'action': request.environ['pylons.routes_dict']['action'],
416 # 'action': request.environ['pylons.routes_dict']['action'],
412 },
417 },
413 'pyramid_dispatch': {
418 'pyramid_dispatch': {
414
419
415 },
420 },
416 'extra': {'plugins': {}}
421 'extra': {'plugins': {}}
417 }
422 }
418 # END CONFIG VARS
423 # END CONFIG VARS
419
424
420 # TODO: This dosn't work when called from pylons compatibility tween.
425 # TODO: This dosn't work when called from pylons compatibility tween.
421 # Fix this and remove it from base controller.
426 # Fix this and remove it from base controller.
422 # context.repo_name = get_repo_slug(request) # can be empty
427 # context.repo_name = get_repo_slug(request) # can be empty
423
428
424 diffmode = 'sideside'
429 diffmode = 'sideside'
425 if request.GET.get('diffmode'):
430 if request.GET.get('diffmode'):
426 if request.GET['diffmode'] == 'unified':
431 if request.GET['diffmode'] == 'unified':
427 diffmode = 'unified'
432 diffmode = 'unified'
428 elif request.session.get('diffmode'):
433 elif request.session.get('diffmode'):
429 diffmode = request.session['diffmode']
434 diffmode = request.session['diffmode']
430
435
431 context.diffmode = diffmode
436 context.diffmode = diffmode
432
437
433 if request.session.get('diffmode') != diffmode:
438 if request.session.get('diffmode') != diffmode:
434 request.session['diffmode'] = diffmode
439 request.session['diffmode'] = diffmode
435
440
436 context.csrf_token = auth.get_csrf_token(session=request.session)
441 context.csrf_token = auth.get_csrf_token(session=request.session)
437 context.backends = rhodecode.BACKENDS.keys()
442 context.backends = rhodecode.BACKENDS.keys()
438 context.backends.sort()
443 context.backends.sort()
439 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
444 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
440
445
441 # NOTE(marcink): when migrated to pyramid we don't need to set this anymore,
446 # NOTE(marcink): when migrated to pyramid we don't need to set this anymore,
442 # given request will ALWAYS be pyramid one
447 # given request will ALWAYS be pyramid one
443 pyramid_request = pyramid.threadlocal.get_current_request()
448 pyramid_request = pyramid.threadlocal.get_current_request()
444 context.pyramid_request = pyramid_request
449 context.pyramid_request = pyramid_request
445
450
446 # web case
451 # web case
447 if hasattr(pyramid_request, 'user'):
452 if hasattr(pyramid_request, 'user'):
448 context.auth_user = pyramid_request.user
453 context.auth_user = pyramid_request.user
449 context.rhodecode_user = pyramid_request.user
454 context.rhodecode_user = pyramid_request.user
450
455
451 # api case
456 # api case
452 if hasattr(pyramid_request, 'rpc_user'):
457 if hasattr(pyramid_request, 'rpc_user'):
453 context.auth_user = pyramid_request.rpc_user
458 context.auth_user = pyramid_request.rpc_user
454 context.rhodecode_user = pyramid_request.rpc_user
459 context.rhodecode_user = pyramid_request.rpc_user
455
460
456 # attach the whole call context to the request
461 # attach the whole call context to the request
457 request.call_context = context
462 request.call_context = context
458
463
459
464
460 def get_auth_user(request):
465 def get_auth_user(request):
461 environ = request.environ
466 environ = request.environ
462 session = request.session
467 session = request.session
463
468
464 ip_addr = get_ip_addr(environ)
469 ip_addr = get_ip_addr(environ)
465 # make sure that we update permissions each time we call controller
470 # make sure that we update permissions each time we call controller
466 _auth_token = (request.GET.get('auth_token', '') or
471 _auth_token = (request.GET.get('auth_token', '') or
467 request.GET.get('api_key', ''))
472 request.GET.get('api_key', ''))
468
473
469 if _auth_token:
474 if _auth_token:
470 # when using API_KEY we assume user exists, and
475 # when using API_KEY we assume user exists, and
471 # doesn't need auth based on cookies.
476 # doesn't need auth based on cookies.
472 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
477 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
473 authenticated = False
478 authenticated = False
474 else:
479 else:
475 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
480 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
476 try:
481 try:
477 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
482 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
478 ip_addr=ip_addr)
483 ip_addr=ip_addr)
479 except UserCreationError as e:
484 except UserCreationError as e:
480 h.flash(e, 'error')
485 h.flash(e, 'error')
481 # container auth or other auth functions that create users
486 # container auth or other auth functions that create users
482 # on the fly can throw this exception signaling that there's
487 # on the fly can throw this exception signaling that there's
483 # issue with user creation, explanation should be provided
488 # issue with user creation, explanation should be provided
484 # in Exception itself. We then create a simple blank
489 # in Exception itself. We then create a simple blank
485 # AuthUser
490 # AuthUser
486 auth_user = AuthUser(ip_addr=ip_addr)
491 auth_user = AuthUser(ip_addr=ip_addr)
487
492
488 if password_changed(auth_user, session):
493 if password_changed(auth_user, session):
489 session.invalidate()
494 session.invalidate()
490 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
495 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
491 auth_user = AuthUser(ip_addr=ip_addr)
496 auth_user = AuthUser(ip_addr=ip_addr)
492
497
493 authenticated = cookie_store.get('is_authenticated')
498 authenticated = cookie_store.get('is_authenticated')
494
499
495 if not auth_user.is_authenticated and auth_user.is_user_object:
500 if not auth_user.is_authenticated and auth_user.is_user_object:
496 # user is not authenticated and not empty
501 # user is not authenticated and not empty
497 auth_user.set_authenticated(authenticated)
502 auth_user.set_authenticated(authenticated)
498
503
499 return auth_user
504 return auth_user
500
505
501
506
502 class BaseController(WSGIController):
507 class BaseController(WSGIController):
503
508
504 def __before__(self):
509 def __before__(self):
505 """
510 """
506 __before__ is called before controller methods and after __call__
511 __before__ is called before controller methods and after __call__
507 """
512 """
508 # on each call propagate settings calls into global settings.
513 # on each call propagate settings calls into global settings.
514 from pylons import config
509 set_rhodecode_config(config)
515 set_rhodecode_config(config)
510 attach_context_attributes(c, request, self._rhodecode_user.user_id)
516 attach_context_attributes(c, request, self._rhodecode_user.user_id)
511
517
512 # TODO: Remove this when fixed in attach_context_attributes()
518 # TODO: Remove this when fixed in attach_context_attributes()
513 c.repo_name = get_repo_slug(request) # can be empty
519 c.repo_name = get_repo_slug(request) # can be empty
514
520
515 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
521 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
516 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
522 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
517 self.sa = meta.Session
523 self.sa = meta.Session
518 self.scm_model = ScmModel(self.sa)
524 self.scm_model = ScmModel(self.sa)
519
525
520 # set user language
526 # set user language
521 user_lang = getattr(c.pyramid_request, '_LOCALE_', None)
527 user_lang = getattr(c.pyramid_request, '_LOCALE_', None)
522 if user_lang:
528 if user_lang:
523 translation.set_lang(user_lang)
529 translation.set_lang(user_lang)
524 log.debug('set language to %s for user %s',
530 log.debug('set language to %s for user %s',
525 user_lang, self._rhodecode_user)
531 user_lang, self._rhodecode_user)
526
532
527 def _dispatch_redirect(self, with_url, environ, start_response):
533 def _dispatch_redirect(self, with_url, environ, start_response):
528 resp = HTTPFound(with_url)
534 resp = HTTPFound(with_url)
529 environ['SCRIPT_NAME'] = '' # handle prefix middleware
535 environ['SCRIPT_NAME'] = '' # handle prefix middleware
530 environ['PATH_INFO'] = with_url
536 environ['PATH_INFO'] = with_url
531 return resp(environ, start_response)
537 return resp(environ, start_response)
532
538
533 def __call__(self, environ, start_response):
539 def __call__(self, environ, start_response):
534 """Invoke the Controller"""
540 """Invoke the Controller"""
535 # WSGIController.__call__ dispatches to the Controller method
541 # WSGIController.__call__ dispatches to the Controller method
536 # the request is routed to. This routing information is
542 # the request is routed to. This routing information is
537 # available in environ['pylons.routes_dict']
543 # available in environ['pylons.routes_dict']
538 from rhodecode.lib import helpers as h
544 from rhodecode.lib import helpers as h
539
545
540 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
546 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
541 if environ.get('debugtoolbar.wants_pylons_context', False):
547 if environ.get('debugtoolbar.wants_pylons_context', False):
542 environ['debugtoolbar.pylons_context'] = c._current_obj()
548 environ['debugtoolbar.pylons_context'] = c._current_obj()
543
549
544 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
550 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
545 environ['pylons.routes_dict']['action']])
551 environ['pylons.routes_dict']['action']])
546
552
547 self.rc_config = SettingsModel().get_all_settings(cache=True)
553 self.rc_config = SettingsModel().get_all_settings(cache=True)
548 self.ip_addr = get_ip_addr(environ)
554 self.ip_addr = get_ip_addr(environ)
549
555
550 # The rhodecode auth user is looked up and passed through the
556 # The rhodecode auth user is looked up and passed through the
551 # environ by the pylons compatibility tween in pyramid.
557 # environ by the pylons compatibility tween in pyramid.
552 # So we can just grab it from there.
558 # So we can just grab it from there.
553 auth_user = environ['rc_auth_user']
559 auth_user = environ['rc_auth_user']
554
560
555 # set globals for auth user
561 # set globals for auth user
556 request.user = auth_user
562 request.user = auth_user
557 self._rhodecode_user = auth_user
563 self._rhodecode_user = auth_user
558
564
559 log.info('IP: %s User: %s accessed %s [%s]' % (
565 log.info('IP: %s User: %s accessed %s [%s]' % (
560 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
566 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
561 _route_name)
567 _route_name)
562 )
568 )
563
569
564 user_obj = auth_user.get_instance()
570 user_obj = auth_user.get_instance()
565 if user_obj and user_obj.user_data.get('force_password_change'):
571 if user_obj and user_obj.user_data.get('force_password_change'):
566 h.flash('You are required to change your password', 'warning',
572 h.flash('You are required to change your password', 'warning',
567 ignore_duplicate=True)
573 ignore_duplicate=True)
568 return self._dispatch_redirect(
574 return self._dispatch_redirect(
569 url('my_account_password'), environ, start_response)
575 url('my_account_password'), environ, start_response)
570
576
571 return WSGIController.__call__(self, environ, start_response)
577 return WSGIController.__call__(self, environ, start_response)
572
578
573
579
574 def h_filter(s):
580 def h_filter(s):
575 """
581 """
576 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
582 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
577 we wrap this with additional functionality that converts None to empty
583 we wrap this with additional functionality that converts None to empty
578 strings
584 strings
579 """
585 """
580 if s is None:
586 if s is None:
581 return markupsafe.Markup()
587 return markupsafe.Markup()
582 return markupsafe.escape(s)
588 return markupsafe.escape(s)
583
589
584
590
585 def add_events_routes(config):
591 def add_events_routes(config):
586 """
592 """
587 Adds routing that can be used in events. Because some events are triggered
593 Adds routing that can be used in events. Because some events are triggered
588 outside of pyramid context, we need to bootstrap request with some
594 outside of pyramid context, we need to bootstrap request with some
589 routing registered
595 routing registered
590 """
596 """
591 config.add_route(name='home', pattern='/')
597 config.add_route(name='home', pattern='/')
592
598
593 config.add_route(name='repo_summary', pattern='/{repo_name}')
599 config.add_route(name='repo_summary', pattern='/{repo_name}')
594 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
600 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
595 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
601 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
596
602
597 config.add_route(name='pullrequest_show',
603 config.add_route(name='pullrequest_show',
598 pattern='/{repo_name}/pull-request/{pull_request_id}')
604 pattern='/{repo_name}/pull-request/{pull_request_id}')
599 config.add_route(name='pull_requests_global',
605 config.add_route(name='pull_requests_global',
600 pattern='/pull-request/{pull_request_id}')
606 pattern='/pull-request/{pull_request_id}')
601
607
602 config.add_route(name='repo_commit',
608 config.add_route(name='repo_commit',
603 pattern='/{repo_name}/changeset/{commit_id}')
609 pattern='/{repo_name}/changeset/{commit_id}')
604 config.add_route(name='repo_files',
610 config.add_route(name='repo_files',
605 pattern='/{repo_name}/files/{commit_id}/{f_path}')
611 pattern='/{repo_name}/files/{commit_id}/{f_path}')
606
612
607
613
608 def bootstrap_request(**kwargs):
614 def bootstrap_request(**kwargs):
609 import pyramid.testing
615 import pyramid.testing
610 request = pyramid.testing.DummyRequest(**kwargs)
616 request = pyramid.testing.DummyRequest(**kwargs)
611 request.application_url = kwargs.pop('application_url', 'http://example.com')
617 request.application_url = kwargs.pop('application_url', 'http://example.com')
612 request.host = kwargs.pop('host', 'example.com:80')
618 request.host = kwargs.pop('host', 'example.com:80')
613 request.domain = kwargs.pop('domain', 'example.com')
619 request.domain = kwargs.pop('domain', 'example.com')
614
620
615 config = pyramid.testing.setUp(request=request)
621 config = pyramid.testing.setUp(request=request)
616 add_events_routes(config)
622 add_events_routes(config)
617
623
618
624
619 class BaseRepoController(BaseController):
625 class BaseRepoController(BaseController):
620 """
626 """
621 Base class for controllers responsible for loading all needed data for
627 Base class for controllers responsible for loading all needed data for
622 repository loaded items are
628 repository loaded items are
623
629
624 c.rhodecode_repo: instance of scm repository
630 c.rhodecode_repo: instance of scm repository
625 c.rhodecode_db_repo: instance of db
631 c.rhodecode_db_repo: instance of db
626 c.repository_requirements_missing: shows that repository specific data
632 c.repository_requirements_missing: shows that repository specific data
627 could not be displayed due to the missing requirements
633 could not be displayed due to the missing requirements
628 c.repository_pull_requests: show number of open pull requests
634 c.repository_pull_requests: show number of open pull requests
629 """
635 """
630
636
631 def __before__(self):
637 def __before__(self):
632 super(BaseRepoController, self).__before__()
638 super(BaseRepoController, self).__before__()
633 if c.repo_name: # extracted from routes
639 if c.repo_name: # extracted from routes
634 db_repo = Repository.get_by_repo_name(c.repo_name)
640 db_repo = Repository.get_by_repo_name(c.repo_name)
635 if not db_repo:
641 if not db_repo:
636 return
642 return
637
643
638 log.debug(
644 log.debug(
639 'Found repository in database %s with state `%s`',
645 'Found repository in database %s with state `%s`',
640 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
646 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
641 route = getattr(request.environ.get('routes.route'), 'name', '')
647 route = getattr(request.environ.get('routes.route'), 'name', '')
642
648
643 # allow to delete repos that are somehow damages in filesystem
649 # allow to delete repos that are somehow damages in filesystem
644 if route in ['delete_repo']:
650 if route in ['delete_repo']:
645 return
651 return
646
652
647 if db_repo.repo_state in [Repository.STATE_PENDING]:
653 if db_repo.repo_state in [Repository.STATE_PENDING]:
648 if route in ['repo_creating_home']:
654 if route in ['repo_creating_home']:
649 return
655 return
650 check_url = url('repo_creating_home', repo_name=c.repo_name)
656 check_url = url('repo_creating_home', repo_name=c.repo_name)
651 return redirect(check_url)
657 return redirect(check_url)
652
658
653 self.rhodecode_db_repo = db_repo
659 self.rhodecode_db_repo = db_repo
654
660
655 missing_requirements = False
661 missing_requirements = False
656 try:
662 try:
657 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
663 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
658 except RepositoryRequirementError as e:
664 except RepositoryRequirementError as e:
659 missing_requirements = True
665 missing_requirements = True
660 self._handle_missing_requirements(e)
666 self._handle_missing_requirements(e)
661
667
662 if self.rhodecode_repo is None and not missing_requirements:
668 if self.rhodecode_repo is None and not missing_requirements:
663 log.error('%s this repository is present in database but it '
669 log.error('%s this repository is present in database but it '
664 'cannot be created as an scm instance', c.repo_name)
670 'cannot be created as an scm instance', c.repo_name)
665
671
666 h.flash(_(
672 h.flash(_(
667 "The repository at %(repo_name)s cannot be located.") %
673 "The repository at %(repo_name)s cannot be located.") %
668 {'repo_name': c.repo_name},
674 {'repo_name': c.repo_name},
669 category='error', ignore_duplicate=True)
675 category='error', ignore_duplicate=True)
670 redirect(h.route_path('home'))
676 redirect(h.route_path('home'))
671
677
672 # update last change according to VCS data
678 # update last change according to VCS data
673 if not missing_requirements:
679 if not missing_requirements:
674 commit = db_repo.get_commit(
680 commit = db_repo.get_commit(
675 pre_load=["author", "date", "message", "parents"])
681 pre_load=["author", "date", "message", "parents"])
676 db_repo.update_commit_cache(commit)
682 db_repo.update_commit_cache(commit)
677
683
678 # Prepare context
684 # Prepare context
679 c.rhodecode_db_repo = db_repo
685 c.rhodecode_db_repo = db_repo
680 c.rhodecode_repo = self.rhodecode_repo
686 c.rhodecode_repo = self.rhodecode_repo
681 c.repository_requirements_missing = missing_requirements
687 c.repository_requirements_missing = missing_requirements
682
688
683 self._update_global_counters(self.scm_model, db_repo)
689 self._update_global_counters(self.scm_model, db_repo)
684
690
685 def _update_global_counters(self, scm_model, db_repo):
691 def _update_global_counters(self, scm_model, db_repo):
686 """
692 """
687 Base variables that are exposed to every page of repository
693 Base variables that are exposed to every page of repository
688 """
694 """
689 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
695 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
690
696
691 def _handle_missing_requirements(self, error):
697 def _handle_missing_requirements(self, error):
692 self.rhodecode_repo = None
698 self.rhodecode_repo = None
693 log.error(
699 log.error(
694 'Requirements are missing for repository %s: %s',
700 'Requirements are missing for repository %s: %s',
695 c.repo_name, error.message)
701 c.repo_name, error.message)
696
702
697 summary_url = h.route_path('repo_summary', repo_name=c.repo_name)
703 summary_url = h.route_path('repo_summary', repo_name=c.repo_name)
698 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
704 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
699 settings_update_url = url('repo', repo_name=c.repo_name)
705 settings_update_url = url('repo', repo_name=c.repo_name)
700 path = request.path
706 path = request.path
701 should_redirect = (
707 should_redirect = (
702 path not in (summary_url, settings_update_url)
708 path not in (summary_url, settings_update_url)
703 and '/settings' not in path or path == statistics_url
709 and '/settings' not in path or path == statistics_url
704 )
710 )
705 if should_redirect:
711 if should_redirect:
706 redirect(summary_url)
712 redirect(summary_url)
@@ -1,156 +1,154 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 application's model objects
22 The application's model objects
23
23
24 :example:
24 :example:
25
25
26 .. code-block:: python
26 .. code-block:: python
27
27
28 from paste.deploy import appconfig
28 from paste.deploy import appconfig
29 from pylons import config
29 from pylons import config
30 from sqlalchemy import engine_from_config
30 from sqlalchemy import engine_from_config
31 from rhodecode.config.environment import load_environment
31 from rhodecode.config.environment import load_environment
32
32
33 conf = appconfig('config:development.ini', relative_to = './../../')
33 conf = appconfig('config:development.ini', relative_to = './../../')
34 load_environment(conf.global_conf, conf.local_conf)
34 load_environment(conf.global_conf, conf.local_conf)
35
35
36 engine = engine_from_config(config, 'sqlalchemy.')
36 engine = engine_from_config(config, 'sqlalchemy.')
37 init_model(engine)
37 init_model(engine)
38 # RUN YOUR CODE HERE
38 # RUN YOUR CODE HERE
39
39
40 """
40 """
41
41
42
42
43 import logging
43 import logging
44
44
45 from pylons import config
46 from pyramid.threadlocal import get_current_registry
47
48 from rhodecode.model import meta, db
45 from rhodecode.model import meta, db
49 from rhodecode.lib.utils2 import obfuscate_url_pw, get_encryption_key
46 from rhodecode.lib.utils2 import obfuscate_url_pw, get_encryption_key
50
47
51 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
52
49
53
50
54 def init_model(engine, encryption_key=None):
51 def init_model(engine, encryption_key=None):
55 """
52 """
56 Initializes db session, bind the engine with the metadata,
53 Initializes db session, bind the engine with the metadata,
57 Call this before using any of the tables or classes in the model,
54 Call this before using any of the tables or classes in the model,
58 preferably once in application start
55 preferably once in application start
59
56
60 :param engine: engine to bind to
57 :param engine: engine to bind to
61 """
58 """
62 engine_str = obfuscate_url_pw(str(engine.url))
59 engine_str = obfuscate_url_pw(str(engine.url))
63 log.info("initializing db for %s", engine_str)
60 log.info("initializing db for %s", engine_str)
64 meta.Base.metadata.bind = engine
61 meta.Base.metadata.bind = engine
65 db.ENCRYPTION_KEY = encryption_key
62 db.ENCRYPTION_KEY = encryption_key
66
63
67
64
68 def init_model_encryption(migration_models):
65 def init_model_encryption(migration_models):
66 from pylons import config
69 migration_models.ENCRYPTION_KEY = get_encryption_key(config)
67 migration_models.ENCRYPTION_KEY = get_encryption_key(config)
70 db.ENCRYPTION_KEY = get_encryption_key(config)
68 db.ENCRYPTION_KEY = get_encryption_key(config)
71
69
72
70
73 class BaseModel(object):
71 class BaseModel(object):
74 """
72 """
75 Base Model for all RhodeCode models, it adds sql alchemy session
73 Base Model for all RhodeCode models, it adds sql alchemy session
76 into instance of model
74 into instance of model
77
75
78 :param sa: If passed it reuses this session instead of creating a new one
76 :param sa: If passed it reuses this session instead of creating a new one
79 """
77 """
80
78
81 cls = None # override in child class
79 cls = None # override in child class
82
80
83 def __init__(self, sa=None):
81 def __init__(self, sa=None):
84 if sa is not None:
82 if sa is not None:
85 self.sa = sa
83 self.sa = sa
86 else:
84 else:
87 self.sa = meta.Session()
85 self.sa = meta.Session()
88
86
89 def _get_instance(self, cls, instance, callback=None):
87 def _get_instance(self, cls, instance, callback=None):
90 """
88 """
91 Gets instance of given cls using some simple lookup mechanism.
89 Gets instance of given cls using some simple lookup mechanism.
92
90
93 :param cls: classes to fetch
91 :param cls: classes to fetch
94 :param instance: int or Instance
92 :param instance: int or Instance
95 :param callback: callback to call if all lookups failed
93 :param callback: callback to call if all lookups failed
96 """
94 """
97
95
98 if isinstance(instance, cls):
96 if isinstance(instance, cls):
99 return instance
97 return instance
100 elif isinstance(instance, (int, long)):
98 elif isinstance(instance, (int, long)):
101 if isinstance(cls, tuple):
99 if isinstance(cls, tuple):
102 # if we pass multi instances we pick first to .get()
100 # if we pass multi instances we pick first to .get()
103 cls = cls[0]
101 cls = cls[0]
104 return cls.get(instance)
102 return cls.get(instance)
105 else:
103 else:
106 if instance:
104 if instance:
107 if callback is None:
105 if callback is None:
108 raise Exception(
106 raise Exception(
109 'given object must be int, long or Instance of %s '
107 'given object must be int, long or Instance of %s '
110 'got %s, no callback provided' % (cls, type(instance))
108 'got %s, no callback provided' % (cls, type(instance))
111 )
109 )
112 else:
110 else:
113 return callback(instance)
111 return callback(instance)
114
112
115 def _get_user(self, user):
113 def _get_user(self, user):
116 """
114 """
117 Helper method to get user by ID, or username fallback
115 Helper method to get user by ID, or username fallback
118
116
119 :param user: UserID, username, or User instance
117 :param user: UserID, username, or User instance
120 """
118 """
121 return self._get_instance(
119 return self._get_instance(
122 db.User, user, callback=db.User.get_by_username)
120 db.User, user, callback=db.User.get_by_username)
123
121
124 def _get_user_group(self, user_group):
122 def _get_user_group(self, user_group):
125 """
123 """
126 Helper method to get user by ID, or username fallback
124 Helper method to get user by ID, or username fallback
127
125
128 :param user_group: UserGroupID, user_group_name, or UserGroup instance
126 :param user_group: UserGroupID, user_group_name, or UserGroup instance
129 """
127 """
130 return self._get_instance(
128 return self._get_instance(
131 db.UserGroup, user_group, callback=db.UserGroup.get_by_group_name)
129 db.UserGroup, user_group, callback=db.UserGroup.get_by_group_name)
132
130
133 def _get_repo(self, repository):
131 def _get_repo(self, repository):
134 """
132 """
135 Helper method to get repository by ID, or repository name
133 Helper method to get repository by ID, or repository name
136
134
137 :param repository: RepoID, repository name or Repository Instance
135 :param repository: RepoID, repository name or Repository Instance
138 """
136 """
139 return self._get_instance(
137 return self._get_instance(
140 db.Repository, repository, callback=db.Repository.get_by_repo_name)
138 db.Repository, repository, callback=db.Repository.get_by_repo_name)
141
139
142 def _get_perm(self, permission):
140 def _get_perm(self, permission):
143 """
141 """
144 Helper method to get permission by ID, or permission name
142 Helper method to get permission by ID, or permission name
145
143
146 :param permission: PermissionID, permission_name or Permission instance
144 :param permission: PermissionID, permission_name or Permission instance
147 """
145 """
148 return self._get_instance(
146 return self._get_instance(
149 db.Permission, permission, callback=db.Permission.get_by_key)
147 db.Permission, permission, callback=db.Permission.get_by_key)
150
148
151 @classmethod
149 @classmethod
152 def get_all(cls):
150 def get_all(cls):
153 """
151 """
154 Returns all instances of what is defined in `cls` class variable
152 Returns all instances of what is defined in `cls` class variable
155 """
153 """
156 return cls.cls.getAll()
154 return cls.cls.getAll()
General Comments 0
You need to be logged in to leave comments. Login now