From 773ea437dbe8dd49939aaa3895fc47e41c939a40 Mon Sep 17 00:00:00 2001 From: Carsten Ehbrecht Date: Thu, 8 Mar 2018 16:42:00 +0100 Subject: [PATCH] Fix #25: using x509 certificate for access control --- .travis.yml | 2 +- CHANGES.rst | 12 +++++ README.rst | 16 ++++-- buildout.cfg | 7 ++- custom.cfg.example | 12 +---- docs/source/changes.rst | 1 + docs/source/conf.py | 1 - docs/source/index.rst | 23 +-------- docs/source/tutorial.rst | 41 ++++++++++++++- environment.yml | 2 +- templates/nginx.conf | 17 +++++-- twitcher/__init__.py | 3 -- twitcher/datatype.py | 18 ++++--- twitcher/owsproxy.py | 4 -- twitcher/owsrequest.py | 1 + twitcher/owssecurity.py | 50 +++++++++++++------ twitcher/store/base.py | 2 +- twitcher/store/memory.py | 2 +- twitcher/store/mongodb.py | 2 +- .../tests/functional/test_rpcinterface_app.py | 2 +- twitcher/tests/store/test_memory.py | 2 +- twitcher/tests/store/test_mongodb.py | 10 ++-- twitcher/tests/test_api.py | 2 +- twitcher/tests/test_datatype.py | 4 +- twitcher/tests/test_owsrequest_wps.py | 24 +++------ twitcher/tests/test_owssecurity.py | 37 +++++++------- twitcher/twitcherctl.py | 4 +- twitcher/wps.cfg | 2 +- 28 files changed, 179 insertions(+), 124 deletions(-) create mode 100644 docs/source/changes.rst diff --git a/.travis.yml b/.travis.yml index 3239efc9..ec61e7f5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,9 +14,9 @@ install: before_script: - sleep 5 script: -# - make docs - make testall - make pep8 + - make docs #after_success: # - coveralls matrix: diff --git a/CHANGES.rst b/CHANGES.rst index 90dd5be4..d4a3c918 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,18 @@ Changes ******* +current +======= + +* pep8 +* removed unused ``c4i`` option. +* added ``auth`` option to set authentication method. +* updated docs for usage of x509 certificates. + +New Features: + +* Feature #25: using x509 certificates for service authentication. + 0.3.5 (2018-03-01) ================== diff --git a/README.rst b/README.rst index 04fe3f0b..a55145f6 100644 --- a/README.rst +++ b/README.rst @@ -22,14 +22,24 @@ Twitcher: A simple OWS Security Proxy Twitcher (the bird-watcher) *a birdwatcher mainly interested in catching sight of rare birds.* (`Leo `_). -Twitcher is a security proxy for Web Processing Services (WPS). The execution of a WPS process is blocked by the proxy. The proxy service provides access tokens (uuid, Macaroons) which needs to be used to run a WPS process. The access tokens are valid only for a short period of time. +Twitcher is a security proxy for Web Processing Services (WPS). The execution of a WPS process is blocked by the proxy. +The proxy service provides access tokens (uuid, Macaroons) which needs to be used to run a WPS process. +The access tokens are valid only for a short period of time. +In addition one can also use X.509 certificates for WPS client authentication. -The implementation is not restricted to WPS services. It will be extended to more OWS services like WMS (Web Map Service) and CSW (Catalogue Service for the Web) and might also be used for Thredds catalog services. +The implementation is not restricted to WPS services. +It will be extended to more OWS services like WMS (Web Map Service) and CSW (Catalogue Service for the Web) +and might also be used for Thredds catalog services. + +Twitcher extensions: + +* `Magpie`_ is an AuthN/AuthZ service provided by the `PAVICS`_ project. Twitcher is a **prototype** implemented in Python with the `Pyramid`_ web framework. Twitcher is part of the `Birdhouse`_ project. The documentation is on `ReadTheDocs`_. .. _Pyramid: http://www.pylonsproject.org -.. _Birdhouse: http://bird-house.github.io .. _ReadTheDocs: http://twitcher.readthedocs.io/en/latest/ +.. _Magpie: https://github.com/Ouranosinc/Magpie +.. _PAVICS: https://ouranosinc.github.io/pavics-sdi/index.html diff --git a/buildout.cfg b/buildout.cfg index 36abe149..005728e0 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -126,7 +126,8 @@ etc-user = ${deployment:etc-user} input = ${buildout:directory}/templates/nginx.conf socket = ${config:socket} hostname = ${settings:hostname} -https_port = ${settings:https-port} +https-port = ${settings:https-port} +ssl-verify-client = optional [pytest] recipe = zc.recipe.egg @@ -141,9 +142,11 @@ eggs = sphinx ${twitcher:eggs} +[noversions] + [versions] birdhousebuilder.recipe.mongodb = 0.4.0 -birdhousebuilder.recipe.nginx = 0.3.6 +birdhousebuilder.recipe.nginx = 0.3.7 buildout.locallib = 0.3.1 collective.recipe.environment = 1.1.0 collective.recipe.template = 2.0 diff --git a/custom.cfg.example b/custom.cfg.example index 9a8687fa..6f7ac0d6 100644 --- a/custom.cfg.example +++ b/custom.cfg.example @@ -1,20 +1,10 @@ [buildout] extends = buildout.cfg +## Development options # versions = noversions [settings] hostname = localhost -http-port = 8083 https-port = 5000 log-level = WARN -username = -password = -workdir = -ows-security = true -ows-proxy = true -rpcinterface = true -wps = true -wps-cfg = - -[noversions] diff --git a/docs/source/changes.rst b/docs/source/changes.rst new file mode 100644 index 00000000..d76c92b6 --- /dev/null +++ b/docs/source/changes.rst @@ -0,0 +1 @@ +.. include:: ../../CHANGES.rst diff --git a/docs/source/conf.py b/docs/source/conf.py index cf00be8c..a67f6a8f 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -335,5 +335,4 @@ .. _icclim: http://icclim.readthedocs.io/en/latest/ .. _PyWPS: http://pywps.org/ .. _dispel4py: https://github.com/dispel4py/dispel4py -.. _esgf-pyclient: https://github.com/ESGF/esgf-pyclient """ diff --git a/docs/source/index.rst b/docs/source/index.rst index 816e6312..0b60bf65 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,21 +1,4 @@ -===================================== -Twitcher: A simple OWS Security Proxy -===================================== - -.. image:: https://travis-ci.org/bird-house/twitcher.svg?branch=master - :target: https://travis-ci.org/bird-house/twitcher - :alt: Travis Build - -Twitcher (the bird-watcher) - *a birdwatcher mainly interested in catching sight of rare birds.* (`Leo `_). - -Twitcher is a security proxy for Web Processing Services (WPS). The execution of a WPS process is blocked by the proxy. The proxy service provides access tokens (uuid, Macaroons) which needs to be used to run a WPS process. The access tokens are valid only for a short period of time. - -The implementation is not restricted to WPS services. It will be extended to more OWS services like WMS (Web Map Service) and CSW (Catalogue Service for the Web) and might also be used for Thredds catalog services. - -Twitcher is a **prototype** implemented in Python with the `Pyramid`_ web framework. - -Twitcher is part of the `Birdhouse`_ project. The documentation is on `ReadTheDocs`_. +.. include:: ../../README.rst .. toctree:: :maxdepth: 1 @@ -26,7 +9,5 @@ Twitcher is part of the `Birdhouse`_ project. The documentation is on `ReadTheDo running tutorial api + changes appendix - -.. _Pyramid: http://pylonsproject.org -.. _ReadTheDocs: http://twitcher.readthedocs.io/en/latest/ diff --git a/docs/source/tutorial.rst b/docs/source/tutorial.rst index 9da8c364..af013c88 100644 --- a/docs/source/tutorial.rst +++ b/docs/source/tutorial.rst @@ -211,7 +211,7 @@ Run an ``Exceute`` request: .. code-block:: sh - $ curl -k "https://localhost:5000/ows/wps?service=wps&request=execute&identifier=hello&version=1.0.0" + $ curl -k "https://localhost:5000/ows/proxy/emu?service=wps&request=execute&identifier=hello&version=1.0.0&datainputs=name=tux" Now you should get an XML error response with a message that you need to provide an access token (see section above). @@ -241,6 +241,41 @@ In the following example we provide the token as HTTP parameter: If you have set enviroment variables with your access token then they will *not* be available in the external service. +Use x509 certificates to control client access +================================================== + +Since version 0.3.6 Twitcher is prepared to use x509 certificates for control client access. +By default it is configured to accept x509 proxy certificates from `ESGF`_. + +Register the Emu WPS service at the Twitcher ``OWSProxy`` with ``auth`` option ``cert``: + +.. code-block:: sh + + $ bin/twitcherctl -k register --name emu --auth cert http://localhost:8094/wps + +The ``GetCapabilities`` and ``DescribeProcess`` requests are not blocked: + +.. code-block:: sh + + $ curl -k "https://localhost:5000/ows/proxy/emu?service=wps&request=getcapabilities" + $ curl -k "https://localhost:5000/ows/proxy/emu?service=wps&request=describeprocess&identifier=hello&version=1.0.0" + +When you run an ``Exceute`` request without a certificate you should get an exception report: + +.. code-block:: sh + + $ curl -k "https://localhost:5000/ows/proxy/emu?service=wps&request=execute&identifier=hello&version=1.0.0&datainputs=name=tux" + +Now you should get an XML error response with a message that you need to provide a valid X509 certificate. + +Get a valid proxy certificate from ESGF, you may use the `esgf-pyclient`_ to run a myproxy logon. +Let's say your proxy certificate is ``cert.pem``, then run the exceute request again using this certificate: + +.. code-block:: sh + + $ curl --cert cert.pem --key cert.pem -k "https://localhost:5000/ows/proxy/emu?service=wps&request=execute&identifier=hello&version=1.0.0&datainputs=name=tux" + + Use Birdy WPS command line client to run a Process ================================================== @@ -358,3 +393,7 @@ If you don't provide a token or the token is invalid then you will get an error owslib.wps.WPSException : {'locator': 'AccessForbidden', 'code': 'NoApplicableCode', 'text': 'Access token is required to access this service.'} WARNING:Error: code=NoApplicableCode, locator=AccessForbidden, text=Access token is required to access this service. + + +.. _ESGF: https://esgf.llnl.gov/ +.. _esgf-pyclient: https://github.com/ESGF/esgf-pyclient diff --git a/environment.yml b/environment.yml index 5b1ce362..091d3331 100644 --- a/environment.yml +++ b/environment.yml @@ -41,4 +41,4 @@ dependencies: - pip: - genshi==0.7 - pyramid-rpc - - sphinx-autoapi==0.4.0 + - sphinx-autoapi diff --git a/templates/nginx.conf b/templates/nginx.conf index 6e330559..4e5361cd 100644 --- a/templates/nginx.conf +++ b/templates/nginx.conf @@ -1,11 +1,11 @@ -# Phoenix: a pyramid web frontend for WPS +# Phoenix: a pyramid web frontend for WPS upstream twitcher { server unix://${socket} fail_timeout=0; } # https server # http://nginx.org/en/docs/http/configuring_https_servers.html -server +server { listen ${https_port} ssl; server_name ${hostname}; @@ -13,11 +13,15 @@ server ssl_certificate_key cert.pem; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers HIGH:!aNULL:!MD5; - #ssl_session_cache shared:SSL:1m; - #ssl_session_timeout 1m; + ssl_session_cache shared:SSL:1m; + ssl_session_timeout 1m; + ssl_client_certificate ${ssl_client_certificate}; + #ssl_crl ca.crl; + ssl_verify_client ${ssl_verify_client}; + ssl_verify_depth 2; # app - location / + location / { proxy_pass http://twitcher; proxy_set_header X-Forwarded-Ssl on; @@ -25,6 +29,9 @@ server proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Protocol $scheme; + #proxy_set_header X-SSL-Client-Cert $ssl_client_cert; + proxy_set_header X-SSL-Client-Verify $ssl_client_verify; + proxy_set_header X-SSL-Client-S-DN $ssl_client_s_dn; proxy_redirect off; } diff --git a/twitcher/__init__.py b/twitcher/__init__.py index ac9da9a5..dc3ea74c 100644 --- a/twitcher/__init__.py +++ b/twitcher/__init__.py @@ -1,6 +1,3 @@ -import logging -logger = logging.getLogger(__name__) - __version__ = '0.3.5' diff --git a/twitcher/datatype.py b/twitcher/datatype.py index f8727a72..131a4387 100644 --- a/twitcher/datatype.py +++ b/twitcher/datatype.py @@ -8,10 +8,6 @@ from twitcher.exceptions import AccessTokenNotFound -import logging -logger = logging.getLogger(__name__) - - class Service(dict): """ Dictionary that contains OWS services. It always has ``'url'`` key. @@ -39,16 +35,22 @@ def type(self): @property def public(self): """Flag if service has public access.""" + # TODO: public access can be set via auth parameter. return self.get('public', False) @property - def c4i(self): - """Flag if service is by climate4impact.""" - return self.get('c4i', False) + def auth(self): + """Authentication method: public, token, cert.""" + return self.get('auth', 'token') @property def params(self): - return {'url': self.url, 'name': self.name, 'type': self.type, 'public': self.public, 'c4i': self.c4i} + return { + 'url': self.url, + 'name': self.name, + 'type': self.type, + 'public': self.public, + 'auth': self.auth} def __str__(self): return self.name diff --git a/twitcher/owsproxy.py b/twitcher/owsproxy.py index 28ef2c7a..4ec11e6f 100644 --- a/twitcher/owsproxy.py +++ b/twitcher/owsproxy.py @@ -54,10 +54,6 @@ def _send_request(request, service, extra_path=None, request_params=None): url = service['url'] if extra_path: url += '/' + extra_path - if service.get('c4i', False): - if 'C4I-Access-Token' in request.headers: - LOGGER.debug('using c4i token') - url += '/' + request.headers['C4I-Access-Token'] if request_params: url += '?' + request_params LOGGER.debug('url = %s', url) diff --git a/twitcher/owsrequest.py b/twitcher/owsrequest.py index 88fca368..2db85c77 100644 --- a/twitcher/owsrequest.py +++ b/twitcher/owsrequest.py @@ -44,6 +44,7 @@ def service(self): @property def request(self): + # TODO: same name for service request and HTTP request is confusing. return self.parser.params['request'] @property diff --git a/twitcher/owssecurity.py b/twitcher/owssecurity.py index 6c4ff67d..1d42bb00 100644 --- a/twitcher/owssecurity.py +++ b/twitcher/owssecurity.py @@ -9,6 +9,7 @@ from twitcher.utils import parse_service_name from twitcher.owsrequest import OWSRequest from twitcher.esgf import fetch_certificate, ESGF_CREDENTIALS +from twitcher.datatype import Service import logging LOGGER = logging.getLogger("TWITCHER") @@ -49,32 +50,51 @@ def prepare_headers(self, request, access_token): LOGGER.debug("Prepared request headers.") return request + def verify_access(self, request, service): + # TODO: public service access handling is confusing. + try: + if service.auth == 'cert': + self._verify_cert(request) + else: # token + self._verify_access_token(request) + except OWSAccessForbidden: + if not service.public: + raise + + def _verify_access_token(self, request): + try: + # try to get access_token ... if no access restrictions then don't complain. + token = self.get_token_param(request) + access_token = self.tokenstore.fetch_by_token(token) + if access_token.is_expired(): + raise OWSAccessForbidden("Access token is expired.") + # update request with data from access token + # request.environ.update(access_token.data) + # TODO: is this realy the way we want to do this? + request = self.prepare_headers(request, access_token) + except AccessTokenNotFound: + raise OWSAccessForbidden("Access token is required to access this service.") + + def _verify_cert(self, request): + # LOGGER.debug('+++ request headers=%s', request.headers.keys()) + if not request.headers.get('X-Ssl-Client-Verify', '') == 'SUCCESS': + raise OWSAccessForbidden("A valid X.509 client certificate is needed.") + def check_request(self, request): if request.path.startswith(protected_path): - # TODO: fix this code + # TODO: refactor this code try: service_name = parse_service_name(request.path) service = self.servicestore.fetch_by_name(service_name) - is_public = service.public if service.public is True: LOGGER.warn('public access for service %s', service_name) except ServiceNotFound: - is_public = False + # TODO: why not raising an exception? + service = Service(url='unregistered', public=False, auth='token') LOGGER.warn("Service not registered.") ows_request = OWSRequest(request) if not ows_request.service_allowed(): raise OWSInvalidParameterValue( "service %s not supported" % ows_request.service, value="service") if not ows_request.public_access(): - try: - # try to get access_token ... if no access restrictions then don't complain. - token = self.get_token_param(request) - access_token = self.tokenstore.fetch_by_token(token) - if access_token.is_expired() and not is_public: - raise OWSAccessForbidden("Access token is expired.") - # update request with data from access token - # request.environ.update(access_token.data) - request = self.prepare_headers(request, access_token) - except AccessTokenNotFound: - if not is_public: - raise OWSAccessForbidden("Access token is required to access this service.") + self.verify_access(request, service) diff --git a/twitcher/store/base.py b/twitcher/store/base.py index 1a0d6e40..47d3c314 100644 --- a/twitcher/store/base.py +++ b/twitcher/store/base.py @@ -51,7 +51,7 @@ class ServiceStore(object): def save_service(self, service, overwrite=True): """ - Stores an OWS service with given name in storage. + Stores an OWS service in storage. :param service: An instance of :class:`twitcher.datatype.Service`. """ diff --git a/twitcher/store/memory.py b/twitcher/store/memory.py index f156e59c..e039c022 100644 --- a/twitcher/store/memory.py +++ b/twitcher/store/memory.py @@ -95,7 +95,7 @@ def save_service(self, service, overwrite=True): name=name, type=service.type, public=service.public, - c4i=service.c4i)) + auth=service.auth)) return self.fetch_by_url(url=service_url) def delete_service(self, name): diff --git a/twitcher/store/mongodb.py b/twitcher/store/mongodb.py index d669f94c..194c570a 100644 --- a/twitcher/store/mongodb.py +++ b/twitcher/store/mongodb.py @@ -78,7 +78,7 @@ def save_service(self, service, overwrite=True): name=name, type=service.type, public=service.public, - c4i=service.c4i)) + auth=service.auth)) return self.fetch_by_url(url=service_url) def delete_service(self, name): diff --git a/twitcher/tests/functional/test_rpcinterface_app.py b/twitcher/tests/functional/test_rpcinterface_app.py index 36aa9f05..adb6ae02 100644 --- a/twitcher/tests/functional/test_rpcinterface_app.py +++ b/twitcher/tests/functional/test_rpcinterface_app.py @@ -54,7 +54,7 @@ def test_generate_token_and_revoke_it(self): @pytest.mark.online def test_register_service_and_unregister_it(self): service = {'url': 'http://localhost/wps', 'name': 'test_emu', - 'type': 'wps', 'public': False, 'c4i': False} + 'type': 'wps', 'public': False, 'auth': 'token'} # register resp = self._callFUT('register_service', ( service['url'], diff --git a/twitcher/tests/store/test_memory.py b/twitcher/tests/store/test_memory.py index 61d19707..b9f50f83 100644 --- a/twitcher/tests/store/test_memory.py +++ b/twitcher/tests/store/test_memory.py @@ -26,8 +26,8 @@ class MemoryServiceStoreTestCase(unittest.TestCase): def setUp(self): self.service_data = {'url': 'http://localhost:8094/wps', 'name': 'emu', - 'c4i': False, 'public': False, + 'auth': 'token', 'type': 'WPS', } self.test_store = MemoryServiceStore() diff --git a/twitcher/tests/store/test_mongodb.py b/twitcher/tests/store/test_mongodb.py index 129ab263..386d3462 100644 --- a/twitcher/tests/store/test_mongodb.py +++ b/twitcher/tests/store/test_mongodb.py @@ -33,6 +33,7 @@ def test_save_token(self): collection_mock.insert_one.assert_called_with(self.access_token) + from twitcher.datatype import Service from twitcher.store.mongodb import MongodbServiceStore @@ -40,10 +41,11 @@ def test_save_token(self): class MongodbServiceStoreTestCase(unittest.TestCase): def setUp(self): self.service = dict(name="loving_flamingo", url="http://somewhere.over.the/ocean", type="wps", - public=False, c4i=False) + public=False, auth='token') self.service_public = dict(name="open_pingu", url="http://somewhere.in.the/deep_ocean", type="wps", - public=True, c4i=False) - self.service_special = dict(url="http://wonderload", name="A special Name", type='wps') + public=True, auth='token') + self.service_special = dict(url="http://wonderload", name="A special Name", type='wps', + auth='token') def test_fetch_by_name(self): collection_mock = mock.Mock(spec=["find_one"]) @@ -74,7 +76,7 @@ def test_save_service_with_special_name(self): store.save_service(Service(self.service_special)) collection_mock.insert_one.assert_called_with({ - 'url': 'http://wonderload', 'type': 'wps', 'name': 'a_special_name', 'public': False, 'c4i': False}) + 'url': 'http://wonderload', 'type': 'wps', 'name': 'a_special_name', 'public': False, 'auth': 'token'}) def test_save_service_public(self): collection_mock = mock.Mock(spec=["insert_one", "find_one", "count"]) diff --git a/twitcher/tests/test_api.py b/twitcher/tests/test_api.py index 085d4334..515bb102 100644 --- a/twitcher/tests/test_api.py +++ b/twitcher/tests/test_api.py @@ -50,7 +50,7 @@ def setUp(self): def test_register_service_and_unregister_it(self): service = {'url': 'http://localhost/wps', 'name': 'test_emu', - 'type': 'wps', 'public': False, 'c4i': False} + 'type': 'wps', 'public': False, 'auth': 'token'} # register resp = self.reg.register_service( service['url'], diff --git a/twitcher/tests/test_datatype.py b/twitcher/tests/test_datatype.py index 508a5542..a39adabc 100644 --- a/twitcher/tests/test_datatype.py +++ b/twitcher/tests/test_datatype.py @@ -54,8 +54,8 @@ def test_service_with_name(self): def test_service_params(self): service = Service(url='http://nowhere/wps', name="test_wps") - assert service.params == {'c4i': False, - 'name': 'test_wps', + assert service.params == {'name': 'test_wps', 'public': False, + 'auth': 'token', 'type': 'WPS', 'url': 'http://nowhere/wps'} diff --git a/twitcher/tests/test_owsrequest_wps.py b/twitcher/tests/test_owsrequest_wps.py index b5d9b09d..4d32b754 100644 --- a/twitcher/tests/test_owsrequest_wps.py +++ b/twitcher/tests/test_owsrequest_wps.py @@ -13,11 +13,9 @@ class OWSRequestWpsTestCase(unittest.TestCase): def setUp(self): self.config = testing.setUp() - def tearDown(self): testing.tearDown() - def test_get_getcaps_request(self): params = dict(request="GetCapabilities", service="WPS") request = DummyRequest(params=params) @@ -25,7 +23,6 @@ def test_get_getcaps_request(self): assert ows_req.request == 'getcapabilities' assert ows_req.service == 'wps' - def test_get_describeprocess_request(self): params = dict(request="DescribeProcess", service="wps", version="1.0.0") request = DummyRequest(params=params) @@ -34,7 +31,6 @@ def test_get_describeprocess_request(self): assert ows_req.service == 'wps' assert ows_req.version == '1.0.0' - def test_get_execute_request(self): params = dict(request="execute", service="Wps", version="1.0.0") request = DummyRequest(params=params) @@ -43,35 +39,30 @@ def test_get_execute_request(self): assert ows_req.service == 'wps' assert ows_req.version == '1.0.0' - def test_get_false_request(self): params = dict(request="tellmemore", service="Wps", version="1.0.0") request = DummyRequest(params=params) with pytest.raises(OWSInvalidParameterValue) as e_info: ows_req = OWSRequest(request) - def test_get_missing_request(self): params = dict(service="wps", version="1.0.0") request = DummyRequest(params=params) with pytest.raises(OWSMissingParameterValue) as e_info: ows_req = OWSRequest(request) - def test_get_false_service(self): params = dict(request="execute", service="ATM", version="1.0.0") request = DummyRequest(params=params) with pytest.raises(OWSInvalidParameterValue) as e_info: ows_req = OWSRequest(request) - def test_get_missing_service(self): params = dict(request="Execute", version="1.0.0") request = DummyRequest(params=params) with pytest.raises(OWSMissingParameterValue) as e_info: ows_req = OWSRequest(request) - def test_post_getcaps_request(self): request = DummyRequest(post={}) request.body = """ @@ -80,7 +71,6 @@ def test_post_getcaps_request(self): assert ows_req.request == 'getcapabilities' assert ows_req.service == 'wps' - def test_post_false_request(self): request = DummyRequest(post={}) request.body = """ @@ -88,7 +78,6 @@ def test_post_false_request(self): with pytest.raises(OWSInvalidParameterValue) as e_info: ows_req = OWSRequest(request) - def test_post_false_service(self): request = DummyRequest(post={}) request.body = """ @@ -96,24 +85,27 @@ def test_post_false_service(self): with pytest.raises(OWSInvalidParameterValue) as e_info: ows_req = OWSRequest(request) - def test_post_describeprocess_request(self): request = DummyRequest(post={}) request.body = """ intersection union - """ + """ ows_req = OWSRequest(request) assert ows_req.request == 'describeprocess' assert ows_req.service == 'wps' assert ows_req.version == '1.0.0' - def test_post_execute_request(self): request = DummyRequest(post={}) request.body = """ - + Buffer @@ -143,5 +135,3 @@ def test_post_execute_request(self): assert ows_req.request == 'execute' assert ows_req.service == 'wps' assert ows_req.version == '1.0.0' - - diff --git a/twitcher/tests/test_owssecurity.py b/twitcher/tests/test_owssecurity.py index 1a34fc8f..cc21d42b 100644 --- a/twitcher/tests/test_owssecurity.py +++ b/twitcher/tests/test_owssecurity.py @@ -2,7 +2,7 @@ import unittest import mock -from pyramid.testing import DummyRequest +from pyramid.testing import DummyRequest, testConfig from twitcher.datatype import AccessToken from twitcher.datatype import Service @@ -47,17 +47,19 @@ def test_get_token_by_header(self): assert token == "54321" def test_check_request(self): - params = dict(request="Execute", service="WPS", version="1.0.0", token="cdefg") - request = DummyRequest(params=params, path='/ows/proxy/emu') - self.security.check_request(request) + with testConfig() as config: + params = dict(request="Execute", service="WPS", version="1.0.0", token="cdefg") + request = DummyRequest(params=params, path='/ows/proxy/emu') + self.security.check_request(request) def test_check_request_invalid(self): - security = OWSSecurity(tokenstore=self.empty_tokenstore, servicestore=self.servicestore) + with testConfig() as config: + security = OWSSecurity(tokenstore=self.empty_tokenstore, servicestore=self.servicestore) - params = dict(request="Execute", service="WPS", version="1.0.0", token="xyz") - request = DummyRequest(params=params, path='/ows/proxy/emu') - with pytest.raises(OWSAccessForbidden) as e_info: - security.check_request(request) + params = dict(request="Execute", service="WPS", version="1.0.0", token="xyz") + request = DummyRequest(params=params, path='/ows/proxy/emu') + with pytest.raises(OWSAccessForbidden) as e_info: + security.check_request(request) def test_check_request_allowed_caps(self): security = OWSSecurity(tokenstore=self.empty_tokenstore, servicestore=self.servicestore) @@ -74,11 +76,12 @@ def test_check_request_allowed_describeprocess(self): security.check_request(request) def test_check_request_public_access(self): - servicestore = MemoryServiceStore() - servicestore.save_service(Service( - url='http://nowhere/wps', name='test_wps', public=True)) - security = OWSSecurity(tokenstore=self.tokenstore, servicestore=servicestore) - - params = dict(request="Execute", service="WPS", version="1.0.0", token="cdefg") - request = DummyRequest(params=params, path='/ows/proxy/emu') - security.check_request(request) + with testConfig() as config: + servicestore = MemoryServiceStore() + servicestore.save_service(Service( + url='http://nowhere/wps', name='test_wps', public=True)) + security = OWSSecurity(tokenstore=self.tokenstore, servicestore=servicestore) + + params = dict(request="Execute", service="WPS", version="1.0.0", token="cdefg") + request = DummyRequest(params=params, path='/ows/proxy/emu') + security.check_request(request) diff --git a/twitcher/twitcherctl.py b/twitcher/twitcherctl.py index 94f28abd..ad57625e 100644 --- a/twitcher/twitcherctl.py +++ b/twitcher/twitcherctl.py @@ -82,6 +82,8 @@ def create_parser(self): help="Service type (wps, wms). Default: wps.") subparser.add_argument('--public', action='store_true', help="If set then service has no access restrictions.") + subparser.add_argument('--auth', default='token', + help="Authentication method (token, cert). Default: token.") # unregister subparser = subparsers.add_parser('unregister', help="Removes OWS service from the registry.") @@ -114,7 +116,7 @@ def run(self, args): elif args.cmd == 'register': result = service.register_service( url=args.url, - data={'name': args.name, 'type': args.type, 'public': args.public}) + data={'name': args.name, 'type': args.type, 'public': args.public, 'auth': args.auth}) elif args.cmd == 'unregister': result = service.unregister_service(name=args.name) elif args.cmd == 'clear': diff --git a/twitcher/wps.cfg b/twitcher/wps.cfg index 26ee1ec6..bd1478a2 100644 --- a/twitcher/wps.cfg +++ b/twitcher/wps.cfg @@ -12,4 +12,4 @@ sethomedir=true setworkdir=true [logging] -level=DEBUG +level=WARN