diff --git a/rhodecode/config/middleware.py b/rhodecode/config/middleware.py --- a/rhodecode/config/middleware.py +++ b/rhodecode/config/middleware.py @@ -43,9 +43,10 @@ from rhodecode.config import patches from rhodecode.config.routing import STATIC_FILE_PREFIX from rhodecode.config.environment import ( load_environment, load_pyramid_environment) +from rhodecode.lib.exceptions import VCSServerUnavailable +from rhodecode.lib.vcs.exceptions import VCSCommunicationError from rhodecode.lib.middleware import csrf from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled -from rhodecode.lib.middleware.disable_vcs import DisableVCSPagesWrapper from rhodecode.lib.middleware.https_fixup import HttpsFixup from rhodecode.lib.middleware.vcs import VCSMiddleware from rhodecode.lib.plugins.utils import register_rhodecode_plugin @@ -188,10 +189,6 @@ def make_not_found_view(config): pylons_app_as_view = wsgiapp(pylons_app) - # Protect from VCS Server error related pages when server is not available - if not vcs_server_enabled: - pylons_app_as_view = DisableVCSPagesWrapper(pylons_app_as_view) - def pylons_app_with_error_handler(context, request): """ Handle exceptions from rc pylons app: @@ -216,10 +213,17 @@ def make_not_found_view(config): return error_handler(response, request) except HTTPError as e: # pyramid type exceptions return error_handler(e, request) - except Exception: + except Exception as e: + log.exception(e) + if settings.get('debugtoolbar.enabled', False): raise + + if isinstance(e, VCSCommunicationError): + return error_handler(VCSServerUnavailable(), request) + return error_handler(HTTPInternalServerError(), request) + return response return pylons_app_with_error_handler diff --git a/rhodecode/lib/exceptions.py b/rhodecode/lib/exceptions.py --- a/rhodecode/lib/exceptions.py +++ b/rhodecode/lib/exceptions.py @@ -23,6 +23,7 @@ Set of custom exceptions used in RhodeCo """ from webob.exc import HTTPClientError +from pyramid.httpexceptions import HTTPBadGateway class LdapUsernameError(Exception): @@ -120,3 +121,14 @@ class NotAllowedToCreateUserError(Except class RepositoryCreationError(Exception): pass + + +class VCSServerUnavailable(HTTPBadGateway): + """ HTTP Exception class for VCS Server errors """ + code = 502 + title = 'VCS Server Error' + def __init__(self, message=''): + self.explanation = 'Could not connect to VCS Server' + if message: + self.explanation += ': ' + message + super(VCSServerUnavailable, self).__init__() diff --git a/rhodecode/lib/middleware/disable_vcs.py b/rhodecode/lib/middleware/disable_vcs.py deleted file mode 100644 --- a/rhodecode/lib/middleware/disable_vcs.py +++ /dev/null @@ -1,76 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2015-2016 RhodeCode GmbH -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License, version 3 -# (only), as published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -# This program is dual-licensed. If you wish to learn more about the -# RhodeCode Enterprise Edition, including its added features, Support services, -# and proprietary license terms, please see https://rhodecode.com/licenses/ - -""" -Disable VCS pages when VCS Server is not available -""" - -import logging -import re -from pyramid.httpexceptions import HTTPBadGateway - -log = logging.getLogger(__name__) - - -class VCSServerUnavailable(HTTPBadGateway): - """ HTTP Exception class for when VCS Server is unavailable """ - code = 502 - title = 'VCS Server Required' - explanation = 'A VCS Server is required for this action. There is currently no VCS Server configured.' - -class DisableVCSPagesWrapper(object): - """ - Pyramid view wrapper to disable all pages that require VCS Server to be - running, avoiding that errors explode to the user. - - This Wrapper should be enabled only in case VCS Server is not available - for the instance. - """ - - VCS_NOT_REQUIRED = [ - '^/$', - ('/_admin(?!/settings/mapping)(?!/my_account/repos)' - '(?!/create_repository)(?!/gists)(?!/notifications/)' - ), - ] - _REGEX_VCS_NOT_REQUIRED = [re.compile(path) for path in VCS_NOT_REQUIRED] - - def _check_vcs_requirement(self, path_info): - """ - Tries to match the current path to one of the safe URLs to be rendered. - Displays an error message in case - """ - for regex in self._REGEX_VCS_NOT_REQUIRED: - safe_url = regex.match(path_info) - if safe_url: - return True - - # Url is not safe to be rendered without VCS Server - log.debug('accessing: `%s` with VCS Server disabled', path_info) - return False - - def __init__(self, handler): - self.handler = handler - - def __call__(self, context, request): - if not self._check_vcs_requirement(request.path): - raise VCSServerUnavailable('VCS Server is not available') - - return self.handler(context, request) diff --git a/rhodecode/lib/vcs/client.py b/rhodecode/lib/vcs/client.py --- a/rhodecode/lib/vcs/client.py +++ b/rhodecode/lib/vcs/client.py @@ -305,7 +305,7 @@ def _get_proxy_method(proxy, name): try: return getattr(proxy, name) except CommunicationError: - raise CommunicationError( + raise exceptions.PyroVCSCommunicationError( 'Unable to connect to remote pyro server %s' % proxy) diff --git a/rhodecode/lib/vcs/client_http.py b/rhodecode/lib/vcs/client_http.py --- a/rhodecode/lib/vcs/client_http.py +++ b/rhodecode/lib/vcs/client_http.py @@ -36,6 +36,7 @@ import urllib2 import urlparse import uuid +import pycurl import msgpack import requests @@ -172,7 +173,11 @@ class RemoteObject(object): def _remote_call(url, payload, exceptions_map, session): - response = session.post(url, data=msgpack.packb(payload)) + try: + response = session.post(url, data=msgpack.packb(payload)) + except pycurl.error as e: + raise exceptions.HttpVCSCommunicationError(e) + response = msgpack.unpackb(response.content) error = response.get('error') if error: diff --git a/rhodecode/lib/vcs/exceptions.py b/rhodecode/lib/vcs/exceptions.py --- a/rhodecode/lib/vcs/exceptions.py +++ b/rhodecode/lib/vcs/exceptions.py @@ -24,6 +24,19 @@ Custom vcs exceptions module. import functools import urllib2 +import pycurl +from Pyro4.errors import CommunicationError + +class VCSCommunicationError(Exception): + pass + + +class PyroVCSCommunicationError(VCSCommunicationError): + pass + + +class HttpVCSCommunicationError(VCSCommunicationError): + pass class VCSError(Exception): @@ -161,7 +174,6 @@ def map_vcs_exceptions(func): try: return func(*args, **kwargs) except Exception as e: - # The error middleware adds information if it finds # __traceback_info__ in a frame object. This way the remote # traceback information is made available in error reports. @@ -182,5 +194,4 @@ def map_vcs_exceptions(func): raise _EXCEPTION_MAP[kind](*e.args) else: raise - return wrapper diff --git a/rhodecode/tests/lib/middleware/test_disable_vcs.py b/rhodecode/tests/lib/middleware/test_disable_vcs.py deleted file mode 100644 --- a/rhodecode/tests/lib/middleware/test_disable_vcs.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2010-2016 RhodeCode GmbH -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License, version 3 -# (only), as published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -# This program is dual-licensed. If you wish to learn more about the -# RhodeCode Enterprise Edition, including its added features, Support services, -# and proprietary license terms, please see https://rhodecode.com/licenses/ - -import pytest -from pyramid.response import Response -from pyramid.testing import DummyRequest -from rhodecode.lib.middleware.disable_vcs import ( - DisableVCSPagesWrapper, VCSServerUnavailable) - - -@pytest.mark.parametrize('url, should_raise', [ - ('/', False), - ('/_admin/settings', False), - ('/_admin/i_am_fine', False), - ('/_admin/settings/mappings', True), - ('/_admin/my_account/repos', True), - ('/_admin/create_repository', True), - ('/_admin/gists/1', True), - ('/_admin/notifications/1', True), -]) -def test_vcs_disabled(url, should_raise): - wrapped_view = DisableVCSPagesWrapper(pyramid_view) - request = DummyRequest(path=url) - - if should_raise: - with pytest.raises(VCSServerUnavailable): - response = wrapped_view(None, request) - else: - response = wrapped_view(None, request) - assert response.status_int == 200 - -def pyramid_view(context, request): - """ - A mock pyramid view to be used in the wrapper - """ - return Response('success') diff --git a/rhodecode/tests/lib/middleware/test_vcs_unavailable.py b/rhodecode/tests/lib/middleware/test_vcs_unavailable.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/lib/middleware/test_vcs_unavailable.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2016 RhodeCode GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License, version 3 +# (only), as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# This program is dual-licensed. If you wish to learn more about the +# RhodeCode Enterprise Edition, including its added features, Support services, +# and proprietary license terms, please see https://rhodecode.com/licenses/ + +import mock +import pytest +import rhodecode.lib.vcs.client as client + +@pytest.mark.usefixtures('autologin_user', 'app') +def test_vcs_available_returns_summary_page(app, backend): + url = '/{repo_name}'.format(repo_name=backend.repo.repo_name) + response = app.get(url) + assert response.status_code == 200 + assert 'Summary' in response.body + + +@pytest.mark.usefixtures('autologin_user', 'app') +def test_vcs_unavailable_returns_vcs_error_page(app, backend): + url = '/{repo_name}'.format(repo_name=backend.repo.repo_name) + + with mock.patch.object(client, '_get_proxy_method') as p: + p.side_effect = client.exceptions.PyroVCSCommunicationError() + response = app.get(url, expect_errors=True) + + assert response.status_code == 502 + assert 'Could not connect to VCS Server' in response.body