def test_get_config_defaults(dummy_request, config_key, default_value): config = get_config(dummy_request.registry) keys = config_key.split('.') if len(keys) == 1: assert default_value == getattr(config, keys[0]) else: assert default_value == getattr(getattr(config, keys[0]), keys[1])
def test_get_config_keys(dummy_request): config = get_config(dummy_request.registry) expected_keys = ( 'proto_header', 'ignore_paths', # tweens 'ssl_redirect', 'hsts_support', 'csp_coverage', ) assert expected_keys == tuple(config._asdict().keys())
def tween(handler, registry): r"""Sets HTTP Strict Transport Security Header as configured. About details of HSTS, see below: * https://hstspreload.org/ * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/\ Strict-Transport-Security#Preloading_Strict_Transport_Security """ config = get_config(registry) hsts_support = config.hsts_support ignore_paths = config.ignore_paths if hsts_support.ignore_paths: ignore_paths = hsts_support.ignore_paths proto_header = config.proto_header if hsts_support.proto_header: proto_header = hsts_support.proto_header tween_name = 'hsts_support' def _hsts_support_tween(req): if not hsts_support.enabled: return handler(req) # ignore if apply_path_filter(req, ignore_paths): logger.info('(%s) Ignore path %s', tween_name, req.path) return handler(req) criteria = build_criteria(req, proto_header=proto_header) secure = all(criteria) if not secure: # sets the header only for https logger.warning('(%s) Insecure request %s', tween_name, req.url) return handler(req) res = handler(req) if HEADER_KEY not in res.headers: # ignore if already exists res.headers[HEADER_KEY] = build_hsts_header(hsts_support) return res return _hsts_support_tween
def test_hsts_tween_with_ssl_request_plus_extra_header_check( mocker, dummy_request): from pyramid_secure_response import hsts_support mocker.spy(hsts_support, 'apply_path_filter') mocker.spy(hsts_support, 'build_criteria') from pyramid.response import Response from pyramid_secure_response.hsts_support import ( apply_path_filter, build_criteria, ) from pyramid_secure_response.util import get_config dummy_request.url = 'https://example.org/' dummy_request.headers['X-Forwarded-Proto'] = 'https' dummy_request.registry.settings = { 'pyramid_secure_response.hsts_support.enabled': 'True', 'pyramid_secure_response.hsts_support.max_age': '604800', # 1 week 'pyramid_secure_response.hsts_support.include_subdomains': 'True', 'pyramid_secure_response.hsts_support.preload': 'True', 'pyramid_secure_response.hsts_support.proto_header': 'X-Forwarded-Proto', 'pyramid_secure_response.hsts_support.ignore_paths': '\n', } handler_stub = mocker.stub(name='handler_stub') handler_stub.return_value = Response(status=200) hsts_support_tween = tween(handler_stub, dummy_request.registry) res = hsts_support_tween(dummy_request) # pylint: disable=no-member assert 1 == handler_stub.call_count assert 1 == apply_path_filter.call_count apply_path_filter.assert_called_once_with(dummy_request, tuple()) assert 1 == build_criteria.call_count config = get_config(dummy_request.registry) build_criteria.assert_called_once_with( dummy_request, proto_header=config.hsts_support.proto_header) assert 'Strict-Transport-Security' in res.headers assert 'max-age=604800; includeSubDomains; preload' == \ res.headers['Strict-Transport-Security']
def test_build_csp_header(directives, header, dummy_request): from pyramid_secure_response.util import get_config from pyramid_secure_response.csp_coverage import build_csp_header dummy_request.url = 'http://example.org/' settings = { 'pyramid_secure_response.csp_coverage.enabled': 'True', } for key, value in directives.items(): settings['pyramid_secure_response.csp_coverage.{:s}'.format( key)] = value dummy_request.registry.settings = settings config = get_config(dummy_request.registry) # NOTE: # [fetch] # - connect_src # - default_src # - font_src # - frame_src # - img_src # - manifest_src # - media_src # - object_src # - script_src # - style_src # - worker_src # [document] # - base_uri # - plugin_types # - sandbox # [navigation] # - form_action # - frame_ancestors # [reporting] # - report_uri # - report_to # [other] # - block_all_mixed_content # - referrer # - require_sri_for # - upgrade_insecure_requests assert header == build_csp_header(config.csp_coverage)
def tween(handler, registry): """Redirects insecure HTTP request as configured. This tween does not handle request if insecure request comes. """ config = get_config(registry) ssl_redirect = config.ssl_redirect ignore_paths = config.ignore_paths if ssl_redirect.ignore_paths: ignore_paths = ssl_redirect.ignore_paths proto_header = config.proto_header if ssl_redirect.proto_header: proto_header = ssl_redirect.proto_header tween_name = 'ssl_redirect' def _ssl_redirect_tween(req): if not ssl_redirect.enabled: return handler(req) # ignore if apply_path_filter(req, ignore_paths): logger.info('(%s) Ignore path %s', tween_name, req.path) return handler(req) criteria = build_criteria(req, proto_header=proto_header) secure = all(criteria) if secure: return handler(req) logger.warning('(%s) Insecure request %s', tween_name, req.url) raise HTTPMovedPermanently(location='https://{:s}{:s}'.format( req.host, req.path)) return _ssl_redirect_tween
def tween(handler, registry): r"""Sets Content Security Policy Header as configured. About details of CSP, see below: * https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/\ Content-Security-Policy """ config = get_config(registry) csp_coverage = config.csp_coverage ignore_paths = config.ignore_paths if csp_coverage.ignore_paths: ignore_paths = csp_coverage.ignore_paths tween_name = 'csp_coverage' def _csp_coverage_tween(req): if not config.csp_coverage.enabled: return handler(req) if apply_path_filter(req, ignore_paths): logger.info('(%s) Ignore path %s', tween_name, req.path) return handler(req) res = handler(req) if HEADER_KEY not in res.headers: # ignore if already exists header_value = build_csp_header(csp_coverage) if header_value: res.headers[HEADER_KEY] = header_value return res return _csp_coverage_tween