Show More
@@ -302,26 +302,26 b'' | |||||
302 | }; |
|
302 | }; | |
303 | }; |
|
303 | }; | |
304 | WebOb = super.buildPythonPackage { |
|
304 | WebOb = super.buildPythonPackage { | |
305 |
name = "WebOb-1.3 |
|
305 | name = "WebOb-1.7.3"; | |
306 | buildInputs = with self; []; |
|
306 | buildInputs = with self; []; | |
307 | doCheck = false; |
|
307 | doCheck = false; | |
308 | propagatedBuildInputs = with self; []; |
|
308 | propagatedBuildInputs = with self; []; | |
309 | src = fetchurl { |
|
309 | src = fetchurl { | |
310 |
url = "https://pypi.python.org/packages/ |
|
310 | url = "https://pypi.python.org/packages/46/87/2f96d8d43b2078fae6e1d33fa86b95c228cebed060f4e3c7576cc44ea83b/WebOb-1.7.3.tar.gz"; | |
311 | md5 = "20918251c5726956ba8fef22d1556177"; |
|
311 | md5 = "350028baffc508e3d23c078118e35316"; | |
312 | }; |
|
312 | }; | |
313 | meta = { |
|
313 | meta = { | |
314 | license = [ pkgs.lib.licenses.mit ]; |
|
314 | license = [ pkgs.lib.licenses.mit ]; | |
315 | }; |
|
315 | }; | |
316 | }; |
|
316 | }; | |
317 | WebTest = super.buildPythonPackage { |
|
317 | WebTest = super.buildPythonPackage { | |
318 |
name = "WebTest- |
|
318 | name = "WebTest-2.0.27"; | |
319 | buildInputs = with self; []; |
|
319 | buildInputs = with self; []; | |
320 | doCheck = false; |
|
320 | doCheck = false; | |
321 | propagatedBuildInputs = with self; [WebOb]; |
|
321 | propagatedBuildInputs = with self; [six WebOb waitress beautifulsoup4]; | |
322 | src = fetchurl { |
|
322 | src = fetchurl { | |
323 | url = "https://pypi.python.org/packages/51/3d/84fd0f628df10b30c7db87895f56d0158e5411206b721ca903cb51bfd948/WebTest-1.4.3.zip"; |
|
323 | url = "https://pypi.python.org/packages/80/fa/ca3a759985c72e3a124cbca3e1f8a2e931a07ffd31fd45d8f7bf21cb95cf/WebTest-2.0.27.tar.gz"; | |
324 | md5 = "631ce728bed92c681a4020a36adbc353"; |
|
324 | md5 = "54e6515ac71c51b6fc90179483c749ad"; | |
325 | }; |
|
325 | }; | |
326 | meta = { |
|
326 | meta = { | |
327 | license = [ pkgs.lib.licenses.mit ]; |
|
327 | license = [ pkgs.lib.licenses.mit ]; | |
@@ -431,6 +431,19 b'' | |||||
431 | license = [ pkgs.lib.licenses.mit ]; |
|
431 | license = [ pkgs.lib.licenses.mit ]; | |
432 | }; |
|
432 | }; | |
433 | }; |
|
433 | }; | |
|
434 | beautifulsoup4 = super.buildPythonPackage { | |||
|
435 | name = "beautifulsoup4-4.6.0"; | |||
|
436 | buildInputs = with self; []; | |||
|
437 | doCheck = false; | |||
|
438 | propagatedBuildInputs = with self; []; | |||
|
439 | src = fetchurl { | |||
|
440 | url = "https://pypi.python.org/packages/fa/8d/1d14391fdaed5abada4e0f63543fef49b8331a34ca60c88bd521bcf7f782/beautifulsoup4-4.6.0.tar.gz"; | |||
|
441 | md5 = "c17714d0f91a23b708a592cb3c697728"; | |||
|
442 | }; | |||
|
443 | meta = { | |||
|
444 | license = [ pkgs.lib.licenses.mit ]; | |||
|
445 | }; | |||
|
446 | }; | |||
434 | bleach = super.buildPythonPackage { |
|
447 | bleach = super.buildPythonPackage { | |
435 | name = "bleach-1.5.0"; |
|
448 | name = "bleach-1.5.0"; | |
436 | buildInputs = with self; []; |
|
449 | buildInputs = with self; []; | |
@@ -990,6 +1003,19 b'' | |||||
990 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
1003 | license = [ pkgs.lib.licenses.bsdOriginal ]; | |
991 | }; |
|
1004 | }; | |
992 | }; |
|
1005 | }; | |
|
1006 | hupper = super.buildPythonPackage { | |||
|
1007 | name = "hupper-1.0"; | |||
|
1008 | buildInputs = with self; []; | |||
|
1009 | doCheck = false; | |||
|
1010 | propagatedBuildInputs = with self; []; | |||
|
1011 | src = fetchurl { | |||
|
1012 | url = "https://pypi.python.org/packages/2e/07/df892c564dc09bb3cf6f6deb976c26adf9117db75ba218cb4353dbc9d826/hupper-1.0.tar.gz"; | |||
|
1013 | md5 = "26e77da7d5ac5858f59af050d1a6eb5a"; | |||
|
1014 | }; | |||
|
1015 | meta = { | |||
|
1016 | license = [ pkgs.lib.licenses.mit ]; | |||
|
1017 | }; | |||
|
1018 | }; | |||
993 | kombu = super.buildPythonPackage { |
|
1019 | kombu = super.buildPythonPackage { | |
994 | name = "kombu-1.5.1"; |
|
1020 | name = "kombu-1.5.1"; | |
995 | buildInputs = with self; []; |
|
1021 | buildInputs = with self; []; | |
@@ -1211,6 +1237,32 b'' | |||||
1211 | license = [ pkgs.lib.licenses.mit ]; |
|
1237 | license = [ pkgs.lib.licenses.mit ]; | |
1212 | }; |
|
1238 | }; | |
1213 | }; |
|
1239 | }; | |
|
1240 | plaster = super.buildPythonPackage { | |||
|
1241 | name = "plaster-0.5"; | |||
|
1242 | buildInputs = with self; []; | |||
|
1243 | doCheck = false; | |||
|
1244 | propagatedBuildInputs = with self; [setuptools]; | |||
|
1245 | src = fetchurl { | |||
|
1246 | url = "https://pypi.python.org/packages/99/b3/d7ca1fe31d2b56dba68a238721fda6820770f9c2a3de17a582d4b5b2edcc/plaster-0.5.tar.gz"; | |||
|
1247 | md5 = "c59345a67a860cfcaa1bd6a81451399d"; | |||
|
1248 | }; | |||
|
1249 | meta = { | |||
|
1250 | license = [ pkgs.lib.licenses.mit ]; | |||
|
1251 | }; | |||
|
1252 | }; | |||
|
1253 | plaster-pastedeploy = super.buildPythonPackage { | |||
|
1254 | name = "plaster-pastedeploy-0.4.1"; | |||
|
1255 | buildInputs = with self; []; | |||
|
1256 | doCheck = false; | |||
|
1257 | propagatedBuildInputs = with self; [PasteDeploy plaster]; | |||
|
1258 | src = fetchurl { | |||
|
1259 | url = "https://pypi.python.org/packages/9d/6e/f8be01ed41c94e6c54ac97cf2eb142a702aae0c8cce31c846f785e525b40/plaster_pastedeploy-0.4.1.tar.gz"; | |||
|
1260 | md5 = "f48d5344b922e56c4978eebf1cd2e0d3"; | |||
|
1261 | }; | |||
|
1262 | meta = { | |||
|
1263 | license = [ pkgs.lib.licenses.mit ]; | |||
|
1264 | }; | |||
|
1265 | }; | |||
1214 | prompt-toolkit = super.buildPythonPackage { |
|
1266 | prompt-toolkit = super.buildPythonPackage { | |
1215 | name = "prompt-toolkit-1.0.14"; |
|
1267 | name = "prompt-toolkit-1.0.14"; | |
1216 | buildInputs = with self; []; |
|
1268 | buildInputs = with self; []; | |
@@ -1368,13 +1420,13 b'' | |||||
1368 | }; |
|
1420 | }; | |
1369 | }; |
|
1421 | }; | |
1370 | pyramid = super.buildPythonPackage { |
|
1422 | pyramid = super.buildPythonPackage { | |
1371 |
name = "pyramid-1. |
|
1423 | name = "pyramid-1.9"; | |
1372 | buildInputs = with self; []; |
|
1424 | buildInputs = with self; []; | |
1373 | doCheck = false; |
|
1425 | doCheck = false; | |
1374 | propagatedBuildInputs = with self; [setuptools WebOb repoze.lru zope.interface zope.deprecation venusian translationstring PasteDeploy]; |
|
1426 | propagatedBuildInputs = with self; [setuptools WebOb repoze.lru zope.interface zope.deprecation venusian translationstring PasteDeploy plaster plaster-pastedeploy hupper]; | |
1375 | src = fetchurl { |
|
1427 | src = fetchurl { | |
1376 |
url = "https://pypi.python.org/packages/33 |
|
1428 | url = "https://pypi.python.org/packages/b0/73/715321e129334f3e41430bede877620175a63ed075fd5d1fd2c25b7cb121/pyramid-1.9.tar.gz"; | |
1377 | md5 = "6ef1dfdcff9136d04490410757c4c446"; |
|
1429 | md5 = "aa6c7c568f83151af51eb053ac633bc4"; | |
1378 | }; |
|
1430 | }; | |
1379 | meta = { |
|
1431 | meta = { | |
1380 | license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ]; |
|
1432 | license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ]; |
@@ -58,7 +58,7 b' pyramid-beaker==0.8' | |||||
58 | pyramid-debugtoolbar==3.0.5 |
|
58 | pyramid-debugtoolbar==3.0.5 | |
59 | pyramid-jinja2==2.5 |
|
59 | pyramid-jinja2==2.5 | |
60 | pyramid-mako==1.0.2 |
|
60 | pyramid-mako==1.0.2 | |
61 |
pyramid==1. |
|
61 | pyramid==1.9.0 | |
62 | pysqlite==2.8.3 |
|
62 | pysqlite==2.8.3 | |
63 | python-dateutil==2.1 |
|
63 | python-dateutil==2.1 | |
64 | python-ldap==2.4.40 |
|
64 | python-ldap==2.4.40 | |
@@ -86,7 +86,7 b' venusian==1.1.0' | |||||
86 | WebError==0.10.3 |
|
86 | WebError==0.10.3 | |
87 | WebHelpers2==2.0 |
|
87 | WebHelpers2==2.0 | |
88 | WebHelpers==1.3 |
|
88 | WebHelpers==1.3 | |
89 |
WebOb==1. |
|
89 | WebOb==1.7.3 | |
90 | Whoosh==2.7.4 |
|
90 | Whoosh==2.7.4 | |
91 | wsgiref==0.1.2 |
|
91 | wsgiref==0.1.2 | |
92 | zope.cachedescriptors==4.0.0 |
|
92 | zope.cachedescriptors==4.0.0 |
@@ -10,6 +10,6 b' gprof2dot==2016.10.13' | |||||
10 | pytest-timeout==1.2.0 |
|
10 | pytest-timeout==1.2.0 | |
11 |
|
11 | |||
12 | mock==1.0.1 |
|
12 | mock==1.0.1 | |
13 |
WebTest== |
|
13 | WebTest==2.0.27 | |
14 | cov-core==1.15.0 |
|
14 | cov-core==1.15.0 | |
15 | coverage==3.7.1 |
|
15 | coverage==3.7.1 |
@@ -111,14 +111,6 b' def make_app(global_conf, static_files=T' | |||||
111 |
|
111 | |||
112 | # The Pylons WSGI app |
|
112 | # The Pylons WSGI app | |
113 | app = PylonsApp(config=config) |
|
113 | app = PylonsApp(config=config) | |
114 | if rhodecode.is_test: |
|
|||
115 | app = csrf.CSRFDetector(app) |
|
|||
116 |
|
||||
117 | expected_origin = config.get('expected_origin') |
|
|||
118 | if expected_origin: |
|
|||
119 | # The API can be accessed from other Origins. |
|
|||
120 | app = csrf.OriginChecker(app, expected_origin, |
|
|||
121 | skip_urls=[routes.util.url_for('api')]) |
|
|||
122 |
|
114 | |||
123 | # Establish the Registry for this application |
|
115 | # Establish the Registry for this application | |
124 | app = RegistryManager(app) |
|
116 | app = RegistryManager(app) |
@@ -1105,6 +1105,9 b' def get_csrf_token(session=None, force_n' | |||||
1105 | :param save_if_missing: save the newly generated token if it's missing in |
|
1105 | :param save_if_missing: save the newly generated token if it's missing in | |
1106 | session |
|
1106 | session | |
1107 | """ |
|
1107 | """ | |
|
1108 | # NOTE(marcink): probably should be replaced with below one from pyramid 1.9 | |||
|
1109 | # from pyramid.csrf import get_csrf_token | |||
|
1110 | ||||
1108 | if not session: |
|
1111 | if not session: | |
1109 | from pylons import session |
|
1112 | from pylons import session | |
1110 |
|
1113 |
@@ -33,7 +33,7 b' import pyramid.threadlocal' | |||||
33 | from paste.auth.basic import AuthBasicAuthenticator |
|
33 | from paste.auth.basic import AuthBasicAuthenticator | |
34 | from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception |
|
34 | from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception | |
35 | from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION |
|
35 | from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION | |
36 |
from pylons import config, tmpl_context as c, request, |
|
36 | from pylons import config, tmpl_context as c, request, url | |
37 | from pylons.controllers import WSGIController |
|
37 | from pylons.controllers import WSGIController | |
38 | from pylons.controllers.util import redirect |
|
38 | from pylons.controllers.util import redirect | |
39 | from pylons.i18n import translation |
|
39 | from pylons.i18n import translation | |
@@ -403,11 +403,25 b' def attach_context_attributes(context, r' | |||||
403 | if request.session.get('diffmode') != diffmode: |
|
403 | if request.session.get('diffmode') != diffmode: | |
404 | request.session['diffmode'] = diffmode |
|
404 | request.session['diffmode'] = diffmode | |
405 |
|
405 | |||
406 | context.csrf_token = auth.get_csrf_token() |
|
406 | context.csrf_token = auth.get_csrf_token(session=request.session) | |
407 | context.backends = rhodecode.BACKENDS.keys() |
|
407 | context.backends = rhodecode.BACKENDS.keys() | |
408 | context.backends.sort() |
|
408 | context.backends.sort() | |
409 | context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id) |
|
409 | context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id) | |
410 | context.pyramid_request = pyramid.threadlocal.get_current_request() |
|
410 | ||
|
411 | # NOTE(marcink): when migrated to pyramid we don't need to set this anymore, | |||
|
412 | # given request will ALWAYS be pyramid one | |||
|
413 | pyramid_request = pyramid.threadlocal.get_current_request() | |||
|
414 | context.pyramid_request = pyramid_request | |||
|
415 | ||||
|
416 | # web case | |||
|
417 | if hasattr(pyramid_request, 'user'): | |||
|
418 | context.auth_user = pyramid_request.user | |||
|
419 | context.rhodecode_user = pyramid_request.user | |||
|
420 | ||||
|
421 | # api case | |||
|
422 | if hasattr(pyramid_request, 'rpc_user'): | |||
|
423 | context.auth_user = pyramid_request.rpc_user | |||
|
424 | context.rhodecode_user = pyramid_request.rpc_user | |||
411 |
|
425 | |||
412 | # attach the whole call context to the request |
|
426 | # attach the whole call context to the request | |
413 | request.call_context = context |
|
427 | request.call_context = context | |
@@ -463,7 +477,7 b' class BaseController(WSGIController):' | |||||
463 | """ |
|
477 | """ | |
464 | # on each call propagate settings calls into global settings. |
|
478 | # on each call propagate settings calls into global settings. | |
465 | set_rhodecode_config(config) |
|
479 | set_rhodecode_config(config) | |
466 |
attach_context_attributes(c, request, |
|
480 | attach_context_attributes(c, request, self._rhodecode_user.user_id) | |
467 |
|
481 | |||
468 | # TODO: Remove this when fixed in attach_context_attributes() |
|
482 | # TODO: Remove this when fixed in attach_context_attributes() | |
469 | c.repo_name = get_repo_slug(request) # can be empty |
|
483 | c.repo_name = get_repo_slug(request) # can be empty | |
@@ -510,7 +524,7 b' class BaseController(WSGIController):' | |||||
510 |
|
524 | |||
511 | # set globals for auth user |
|
525 | # set globals for auth user | |
512 | request.user = auth_user |
|
526 | request.user = auth_user | |
513 |
|
|
527 | self._rhodecode_user = auth_user | |
514 |
|
528 | |||
515 | log.info('IP: %s User: %s accessed %s [%s]' % ( |
|
529 | log.info('IP: %s User: %s accessed %s [%s]' % ( | |
516 | self.ip_addr, auth_user, safe_unicode(get_access_path(environ)), |
|
530 | self.ip_addr, auth_user, safe_unicode(get_access_path(environ)), |
@@ -48,10 +48,11 b' log = logging.getLogger(__name__)' | |||||
48 |
|
48 | |||
49 |
|
49 | |||
50 | def add_renderer_globals(event): |
|
50 | def add_renderer_globals(event): | |
|
51 | from rhodecode.lib import helpers | |||
|
52 | ||||
|
53 | # NOTE(marcink): | |||
51 | # Put pylons stuff into the context. This will be removed as soon as |
|
54 | # Put pylons stuff into the context. This will be removed as soon as | |
52 | # migration to pyramid is finished. |
|
55 | # migration to pyramid is finished. | |
53 | conf = pylons.config._current_obj() |
|
|||
54 | event['h'] = conf.get('pylons.h') |
|
|||
55 | event['c'] = pylons.tmpl_context |
|
56 | event['c'] = pylons.tmpl_context | |
56 | event['url'] = pylons.url |
|
57 | event['url'] = pylons.url | |
57 |
|
58 | |||
@@ -62,6 +63,7 b' def add_renderer_globals(event):' | |||||
62 | # Add Pyramid translation as '_' to context |
|
63 | # Add Pyramid translation as '_' to context | |
63 | event['_'] = request.translate |
|
64 | event['_'] = request.translate | |
64 | event['_ungettext'] = request.plularize |
|
65 | event['_ungettext'] = request.plularize | |
|
66 | event['h'] = helpers | |||
65 |
|
67 | |||
66 |
|
68 | |||
67 | def add_localizer(event): |
|
69 | def add_localizer(event): |
@@ -414,15 +414,15 b' class TestRepositoryArchival(object):' | |||||
414 | fname=fname)) |
|
414 | fname=fname)) | |
415 |
|
415 | |||
416 | assert response.status == '200 OK' |
|
416 | assert response.status == '200 OK' | |
417 |
headers = |
|
417 | headers = [ | |
418 |
'Pragma' |
|
418 | ('Pragma', 'no-cache'), | |
419 |
'Cache-Control' |
|
419 | ('Cache-Control', 'no-cache'), | |
420 |
'Content-Disposition' |
|
420 | ('Content-Disposition', 'attachment; filename=%s' % filename), | |
421 |
'Content-Type' |
|
421 | ('Content-Type', '%s' % mime_type), | |
422 |
|
|
422 | ] | |
423 | if 'Set-Cookie' in response.response.headers: |
|
423 | if 'Set-Cookie' in response.response.headers: | |
424 | del response.response.headers['Set-Cookie'] |
|
424 | del response.response.headers['Set-Cookie'] | |
425 | assert response.response.headers == headers |
|
425 | assert response.response.headers.items() == headers | |
426 |
|
426 | |||
427 | def test_archival_wrong_ext(self, backend): |
|
427 | def test_archival_wrong_ext(self, backend): | |
428 | backend.enable_downloads() |
|
428 | backend.enable_downloads() |
@@ -56,7 +56,10 b' class StubVCSController(simplevcs.Simple' | |||||
56 |
|
56 | |||
57 | def _create_wsgi_app(self, repo_path, repo_name, config): |
|
57 | def _create_wsgi_app(self, repo_path, repo_name, config): | |
58 | def fake_app(environ, start_response): |
|
58 | def fake_app(environ, start_response): | |
59 | start_response('200 OK', []) |
|
59 | headers = [ | |
|
60 | ('Http-Accept', 'application/mercurial') | |||
|
61 | ] | |||
|
62 | start_response('200 OK', headers) | |||
60 | return self.stub_response_body |
|
63 | return self.stub_response_body | |
61 | return fake_app |
|
64 | return fake_app | |
62 |
|
65 | |||
@@ -92,8 +95,6 b' def _remove_default_user_from_query_cach' | |||||
92 | Session().expire(user) |
|
95 | Session().expire(user) | |
93 |
|
96 | |||
94 |
|
97 | |||
95 |
|
||||
96 |
|
||||
97 | def test_handles_exceptions_during_permissions_checks( |
|
98 | def test_handles_exceptions_during_permissions_checks( | |
98 | vcscontroller, disable_anonymous_user): |
|
99 | vcscontroller, disable_anonymous_user): | |
99 | user_and_pass = '%s:%s' % (TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS) |
|
100 | user_and_pass = '%s:%s' % (TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS) | |
@@ -220,6 +221,7 b' class TestShadowRepoExposure(object):' | |||||
220 | controller.stub_response_body = 'dummy body value' |
|
221 | controller.stub_response_body = 'dummy body value' | |
221 | environ_stub = { |
|
222 | environ_stub = { | |
222 | 'HTTP_HOST': 'test.example.com', |
|
223 | 'HTTP_HOST': 'test.example.com', | |
|
224 | 'HTTP_ACCEPT': 'application/mercurial', | |||
223 | 'REQUEST_METHOD': 'GET', |
|
225 | 'REQUEST_METHOD': 'GET', | |
224 | 'wsgi.url_scheme': 'http', |
|
226 | 'wsgi.url_scheme': 'http', | |
225 | } |
|
227 | } | |
@@ -241,6 +243,7 b' class TestShadowRepoExposure(object):' | |||||
241 | controller.stub_response_body = 'dummy body value' |
|
243 | controller.stub_response_body = 'dummy body value' | |
242 | environ_stub = { |
|
244 | environ_stub = { | |
243 | 'HTTP_HOST': 'test.example.com', |
|
245 | 'HTTP_HOST': 'test.example.com', | |
|
246 | 'HTTP_ACCEPT': 'application/mercurial', | |||
244 | 'REQUEST_METHOD': 'GET', |
|
247 | 'REQUEST_METHOD': 'GET', | |
245 | 'wsgi.url_scheme': 'http', |
|
248 | 'wsgi.url_scheme': 'http', | |
246 | } |
|
249 | } |
@@ -29,9 +29,10 b' from lxml.html import fromstring, tostri' | |||||
29 | from lxml.cssselect import CSSSelector |
|
29 | from lxml.cssselect import CSSSelector | |
30 | from urlparse import urlparse, parse_qsl |
|
30 | from urlparse import urlparse, parse_qsl | |
31 | from urllib import unquote_plus |
|
31 | from urllib import unquote_plus | |
|
32 | import webob | |||
32 |
|
33 | |||
33 | from webtest.app import ( |
|
34 | from webtest.app import TestResponse, TestApp, string_types | |
34 | Request, TestResponse, TestApp, print_stderr, string_types) |
|
35 | from webtest.compat import print_stderr | |
35 |
|
36 | |||
36 | import pytest |
|
37 | import pytest | |
37 | import rc_testdata |
|
38 | import rc_testdata | |
@@ -103,7 +104,7 b' class CustomTestResponse(TestResponse):' | |||||
103 | return self.request.environ['beaker.session'] |
|
104 | return self.request.environ['beaker.session'] | |
104 |
|
105 | |||
105 |
|
106 | |||
106 | class TestRequest(Request): |
|
107 | class TestRequest(webob.BaseRequest): | |
107 |
|
108 | |||
108 | # for py.test |
|
109 | # for py.test | |
109 | disabled = True |
|
110 | disabled = True |
General Comments 0
You need to be logged in to leave comments.
Login now