Skip to content
GitLab
Menu
Why GitLab
Pricing
Contact Sales
Explore
Why GitLab
Pricing
Contact Sales
Explore
Sign in
Get free trial
Commits on Source (4)
Add HTTP CSP Header support (new tween)
· 38c572fc
Yasha
authored
Jan 13, 2018
38c572fc
Merge branch 'csp-coverage-tween' into 'master'
· bae5d7b2
Yasha
authored
Jan 16, 2018
Csp coverage tween See merge request
!2
bae5d7b2
Update CHANGELOG
· 11d9f01c
Yasha
authored
Jan 16, 2018
11d9f01c
Bump version v0.1.1
· 9159b90c
Yasha
authored
Jan 16, 2018
9159b90c
Hide whitespace changes
Inline
Side-by-side
CHANGELOG
View file @
9159b90c
Tue 16 Jan 2018 Yasuhiro Asaka <yasuhiro.asaka@grauwoelfchen.net>
* Bump version v0.1.1
* Add csp_coverage (HTTP CSP Header support) tween
Tue 09 Jan 2018 Yasuhiro Asaka <yasuhiro.asaka@grauwoelfchen.net>
* Bump version v0.1.0
* Make configuration as separated by tween
...
...
README.rst
View file @
9159b90c
...
...
@@ -14,14 +14,15 @@ Pyramid Secure Response
:alt: Version
`pyramid_secure_response`_ handles
insecure
request to provide secure response.
`pyramid_secure_response`_ handles request to provide secure response.
* redirects http as https
* sets HSTS Header to response
* sets CSP Header to response
For response headers (
hsts
), the tweens do not set anything if that
already exist in your response. This means you can set
by yourself as you need
(e.g. at view action)
For response headers (
HSTS, CSP
), the tweens do not set anything if that
already exist in your response. This means you can set
these values by yourself
as you need
(e.g. at view action)
Repository
...
...
@@ -41,14 +42,16 @@ Install
Features
--------
``pyramid_secure_response`` has
2
tweens:
``pyramid_secure_response`` has
3
tweens:
* HTTP Redirecton (`ssl_redirect`_, +http+ request will be redirected as
+https+ on same host)
* HSTS Support (`hsts_support`_, The ``HTTP Strict Transport Security`` will be
set in response)
* CSP Coverage ( `csp_coverage`_, The ``Content Security Policy`` will be set
in response)
With
an
additional feature.
With
few
additional feature
s
.
* Ignore path filter (matched paths with ``str.startswith()`` will be ignored)
...
...
@@ -69,8 +72,8 @@ like `PasteDeploy`_ config file.
pyramid.includes =
pyramid_secure_response
Python
~~~~
~~
Code
~~~~
Or you can include in python code.
...
...
@@ -84,6 +87,7 @@ It's also available to add tween(s) directly, as you need.
config.add_tween('pyramid_secure_response.ssl_redirect.tween')
config.add_tween('pyramid_secure_response.hsts_support.tween')
config.add_tween('pyramid_secure_response.csp_coverage.tween')
You may want to add also kwargs ``under`` or ``over``. (
See `pyramid.config.Configurator.add_tween`_.)
...
...
@@ -96,6 +100,8 @@ By default, *ssl_redirect* tween will be handled before another tweens.
over=tweens.MAIN)
config.add_tween('pyramid_secure_response.hsts_support.tween',
over=tweens.MAIN, under='pyramid_secure_response.ssl_redirect.tween')
config.add_tween('pyramid_secure_response.csp_coverage.tween',
over=tweens.MAIN, under='pyramid_secure_response.ssl_redirect.tween')
Configuration
*************
...
...
@@ -111,6 +117,10 @@ For example:
pyramid_secure_response.hsts_support.include_subdomains = True
pyramid_secure_response.hsts_support.preload = True
pyramid_secure_response.csp_coverage.enabled = True
pyramid_secure_response.csp_coverage.script_src = https://example.com
pyramid_secure_response.csp_coverage.default_src = self
# fallback (global)
pyramid_secure_response.proto_header = X-Forwarded-Proto
pyramid_secure_response.ignore_paths =
...
...
@@ -120,7 +130,8 @@ For example:
Default values
**************
(ssl_redirect)
ssl_redirect
~~~~~~~~~~~~
+--------------+----------------+--------+-------------------------+
| Key | Value (INI) | Type | Note |
...
...
@@ -129,16 +140,17 @@ Default values
| | | | tween |
+--------------+----------------+--------+-------------------------+
| proto_header | ``''`` | *str* | An header like |
| | | | *X-Forwarded-Proto*
.
|
| | | | *X-Forwarded-Proto*
|
| | | | Checked in criteria as |
| | | | ``'https'``
,
if exists
.
|
| | | | ``'https'`` if exists
|
+--------------+----------------+--------+-------------------------+
| ignore_paths | ``''`` | *list* | Splittable string like |
| | | | *\n/path\n/path\n*
.
|
| | | | Skiped
,
if matched
.
|
| | | | *\n/path\n/path\n*
|
| | | | Skip
p
ed if matched
|
+--------------+----------------+--------+-------------------------+
(hsts_support)
hsts_support
~~~~~~~~~~~~
+--------------------+----------------+--------+-------------------------+
| Key | Value (INI) | Type | Note |
...
...
@@ -156,15 +168,115 @@ Default values
| | | | HSTS Header |
+--------------------+----------------+--------+-------------------------+
| proto_header | ``''`` | *str* | An header like |
| | | | *X-Forwarded-Proto*
.
|
| | | | *X-Forwarded-Proto*
|
| | | | Checked in criteria as |
| | | | ``'https'``
,
if exists
.
|
| | | | ``'https'`` if exists
|
+--------------------+----------------+--------+-------------------------+
| ignore_paths | ``''`` | *list* | Splittable string like |
| | | | *\n/path\n/path\n*
.
|
| | | | Skiped
,
if matched
.
|
| | | | *\n/path\n/path\n*
|
| | | | Skip
p
ed if matched
|
+--------------------+----------------+--------+-------------------------+
csp_coverage
~~~~~~~~~~~~
+---------------------------+-------------+--------+----------------------------------+
| Key | Value (INI) | Type | Note |
+===========================+=============+========+==================================+
| enabled | ``'True'`` | *bool* | Enable ``csp_coverage`` tween |
+---------------------------+-------------+--------+----------------------------------+
| child_src | ``''`` | *str* | ``child-src`` fetch directive |
| | | | <source> (deprecated) |
+---------------------------+-------------+--------+----------------------------------+
| connect_src | ``''`` | *str* | ``connect-src`` fetch directive |
| | | | <source> |
+---------------------------+-------------+--------+----------------------------------+
| default_src | ``''`` | *str* | ``default-src`` fetch directive |
| | | | <source> |
+---------------------------+-------------+--------+----------------------------------+
| font_src | ``''`` | *str* | ``font-src`` fetch directive |
| | | | <source> |
+---------------------------+-------------+--------+----------------------------------+
| frame_src | ``''`` | *str* | ``frame-src`` fetch directive |
| | | | <source> |
+---------------------------+-------------+--------+----------------------------------+
| img_src | ``''`` | *str* | ``img-src`` fetch directive |
| | | | <source> |
+---------------------------+-------------+--------+----------------------------------+
| manifest_src | ``''`` | *str* | ``manifest-src`` fetch directive |
| | | | <source> |
+---------------------------+-------------+--------+----------------------------------+
| media_src | ``''`` | *str* | ``media-src`` fetch directive |
| | | | <source> |
+---------------------------+-------------+--------+----------------------------------+
| object_src | ``''`` | *str* | ``object-src`` fetch directive |
| | | | <source> |
+---------------------------+-------------+--------+----------------------------------+
| script_src | ``''`` | *str* | ``script-src`` fetch directive |
| | | | <source> |
+---------------------------+-------------+--------+----------------------------------+
| style_src | ``''`` | *str* | ``style-src`` fetch directive |
| | | | <source> |
+---------------------------+-------------+--------+----------------------------------+
| worker_src | ``''`` | *str* | ``worker-src`` fetch directive |
| | | | <source> |
+---------------------------+-------------+--------+----------------------------------+
| base_uri | ``''`` | *str* | ``base-uri`` document directive |
| | | | <source> |
+---------------------------+-------------+--------+----------------------------------+
| plugin_types | ``''`` | *str* | ``plugin-types`` document |
| | | | directive <type>/<subtype> |
+---------------------------+-------------+--------+----------------------------------+
| sandbox | ``''`` | *str* | ``sandbox`` document directive |
| | | | <value> |
+---------------------------+-------------+--------+----------------------------------+
| form_action | ``''`` | *str* | ``form-action`` navigation |
| | | | directive <source> |
+---------------------------+-------------+--------+----------------------------------+
| frame_ancestors | ``''`` | *str* | ``frame-ancestors`` navigation |
| | | | directive <source> |
+---------------------------+-------------+--------+----------------------------------+
| report_uri | ``''`` | *str* | ``report_uri`` reporting |
| | | | directive <uri> |
+---------------------------+-------------+--------+----------------------------------+
| report_to | ``''`` | *str* | ``report_to`` reporting |
| | | | directive <uri> |
+---------------------------+-------------+--------+----------------------------------+
| block_all_mixed_content | ``'False'`` | *bool* | ``block_all_mixed_content`` |
| | | | directive value |
+---------------------------+-------------+--------+----------------------------------+
| referrer | ``''`` | *str* | ``referrer`` directive value |
| | | | such as ``"origin"`` (obsolete) |
+---------------------------+-------------+--------+----------------------------------+
| require_sri_for | ``''`` | *str* | ``require_sri_for`` directive |
| | | | value |
+---------------------------+-------------+--------+----------------------------------+
| upgrade_insecure_requests | ``'False'`` | *bool* | ``upgrade_insecure_requests`` |
| | | | directive value |
+---------------------------+-------------+--------+----------------------------------+
| ignore_paths | ``''`` | *list* | Splittable string like |
| | | | *\n/path\n/path\n*. Skipped, if |
| | | | matched |
+---------------------------+-------------+--------+----------------------------------+
Note
****
Format
~~~~~~
For some policy values `<source>`, `<type>/<subtype>`, `<value>` and `<uri>`,
you don't need single quote for values. See above example section.
Syntax
~~~~~~
pyramid_secure_response moment does not validate value which is set by you, if
its syntax is valid or not, for now. Please check that yourself ;)
Fallback (global)
~~~~~~~~~~~~~~~~~
These values are like a global variables. If exist, its are also applied
to all tweens as fallback (If same keys are already exist for the tweens, it
will be taken priority, over these values).
...
...
@@ -173,15 +285,21 @@ will be taken priority, over these values).
| Key | Value (INI) | Type | Note |
+===============+================+========+=========================+
| proto_header | ``''`` | *str* | An header like |
| | | | *X-Forwarded-Proto*
.
|
| | | | *X-Forwarded-Proto*
|
| | | | Checked in criteria as |
| | | | ``'https'``
,
if exists
.
|
| | | | ``'https'`` if exists
|
+---------------+----------------+--------+-------------------------+
| ignore_paths | ``''`` | *list* | Splittable string like |
| | | | *\n/path\n/path\n*
.
|
| | | | Skiped
,
if matched
.
|
| | | | *\n/path\n/path\n*
|
| | | | Skip
p
ed if matched
|
+---------------+----------------+--------+-------------------------+
Links
~~~~~
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
Development
...
...
pyramid_secure_response/__init__.py
View file @
9159b90c
...
...
@@ -5,7 +5,7 @@ __all__ = (
'
__name__
'
,
# pylint: disable=undefined-all-variable
)
__version__
=
'
0.1.
0
'
__version__
=
'
0.1.
1
'
def
includeme
(
config
):
...
...
pyramid_secure_response/csp_coverage.py
0 → 100644
View file @
9159b90c
import
re
from
pyramid_secure_response.util
import
(
logger
,
apply_path_filter
,
get_config
,
)
HEADER_KEY
=
'
Content-Security-Policy
'
# MIME types
# * https://developer.mozilla.org/en-US/docs/Web/HTTP/\
# Basics_of_HTTP/MIME_types/Complete_list_of_MIME_types
# * https://www.iana.org/assignments/media-types/media-types.xhtml
MIME_TYPE_PATTERN
=
re
.
compile
(
r
'
\A[-\w]+\/[-\w]+\Z
'
)
# SCHEME
# colon at the end is required
SCHEME_VALUES
=
(
'
blob:
'
,
'
data:
'
,
'
filesystem:
'
,
'
http:
'
,
'
https:
'
,
'
mediastream:
'
,
)
# sandbox
# this directive is not supported in <meta> element and
# 'Content-Security-Policy-Report-Only'.
SANDBOX_VALUES
=
(
'
allow-forms
'
,
'
allow-modals
'
,
'
allow-orientation-lock
'
,
'
allow-pointer-lock
'
,
'
allow-popups
'
,
'
allow-popups-to-escape-sandbox
'
,
'
allow-presentation
'
,
'
allow-same-origin
'
,
'
allow-scripts
'
,
'
allow-top-navigation
'
,
)
# directives
NO_QUOTE_DIRECTIVES
=
(
'
report-uri
'
,
'
report-to
'
,
'
plugin-types
'
)
BOOLEAN_DIRECTIVES
=
(
'
block-all-mixed-content
'
,
'
upgrade-insecure-requests
'
)
def
_build_csp_header_value
(
directive
,
texts
):
"""
Creates CSP Header values for directive from texts.
Returns empty string if does not valid directive or texts is given.
"""
if
texts
:
if
directive
in
BOOLEAN_DIRECTIVES
:
# bool value
if
texts
.
lower
()
==
'
true
'
:
return
'
{0:s}
'
.
format
(
directive
)
else
:
values
=
[]
for
text
in
texts
.
split
(
'
'
):
# schemes <scheme-source>, mime type <type>/<subtype> and
# sandbox <value> shouldn't be surrounded with single
# quotes
if
not
text
.
startswith
(
SCHEME_VALUES
)
and
\
text
not
in
SANDBOX_VALUES
and
\
(
directive
not
in
NO_QUOTE_DIRECTIVES
and
not
MIME_TYPE_PATTERN
.
match
(
text
))
and
\
'
\'
'
not
in
text
:
text
=
"'
{:s}
'"
.
format
(
text
)
values
.
append
(
text
)
return
'
{0:s} {1:s}
'
.
format
(
directive
,
'
'
.
join
(
values
))
return
''
def
build_csp_header
(
config
):
# type: (Union[namedtuple, dict]) -> str
"""
Returns CSP Header values.
"""
policy_text
=
''
config_dict
=
config
# namedtuple
if
isinstance
(
config
,
tuple
)
and
hasattr
(
config
,
'
_asdict
'
):
config_dict
=
config
.
_asdict
()
if
not
isinstance
(
config_dict
,
dict
):
raise
ValueError
for
name
in
config_dict
:
if
name
in
(
'
enabled
'
,
'
ignore_paths
'
):
# not directive
continue
value
=
_build_csp_header_value
(
name
.
replace
(
'
_
'
,
'
-
'
),
str
(
config_dict
[
name
]))
if
value
:
policy_text
+=
'
; {:s}
'
.
format
(
value
)
return
policy_text
[
2
:]
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
pyramid_secure_response/util.py
View file @
9159b90c
...
...
@@ -64,6 +64,44 @@ def get_config(registry): # type: (Registry) -> namedtuple
(
'
preload
'
,
True
),
),
registry
=
registry
)
# Content Security Policy (csp_coverage.xxx)
csp_coverage
=
_build_config
(
prefix
=
'
csp_coverage
'
,
defaults
=
(
(
'
enabled
'
,
True
),
(
'
ignore_paths
'
,
tuple
()),
# NOTE:
# These directives are appended into header as alphabetical
# order by directive sections.
# [fetch]
(
'
child_src
'
,
''
),
# (deprecated)
(
'
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
'
,
''
),
# (deprecated)
(
'
report_to
'
,
''
),
# [other]
(
'
block_all_mixed_content
'
,
False
),
(
'
referrer
'
,
''
),
# (obsolete)
(
'
require_sri_for
'
,
''
),
(
'
upgrade_insecure_requests
'
,
False
),
),
registry
=
registry
)
# Shared
return
_build_config
(
prefix
=
''
,
defaults
=
(
(
'
proto_header
'
,
''
),
# e.g. X-Forwarded-Proto
...
...
@@ -71,6 +109,7 @@ def get_config(registry): # type: (Registry) -> namedtuple
(
'
ssl_redirect
'
,
ssl_redirect
),
(
'
hsts_support
'
,
hsts_support
),
(
'
csp_coverage
'
,
csp_coverage
),
),
registry
=
registry
)
...
...
test/unit/csp_coverage_test.py
0 → 100644
View file @
9159b90c
import
pytest
from
pyramid_secure_response.csp_coverage
import
tween
@pytest.fixture
(
autouse
=
True
)
def
setup
():
import
logging
from
pyramid_secure_response.csp_coverage
import
logger
logger
.
setLevel
(
logging
.
ERROR
)
@pytest.mark.parametrize
(
'
directives,header
'
,
[
# [fetch directive]
({},
""
),
({
'
child_src
'
:
'
self
'
,
'
default_src
'
:
'
none
'
},
"
child-src
'
self
'
; default-src
'
none
'"
),
({
'
connect_src
'
:
'
self
'
,
'
default_src
'
:
'
none
'
},
"
connect-src
'
self
'
; default-src
'
none
'"
),
({
'
default_src
'
:
''
},
""
),
({
'
default_src
'
:
'
https:
'
},
"
default-src https:
"
),
({
'
default_src
'
:
"
self https://example.org/
"
},
"
default-src
'
self
'
https://example.org/
"
),
({
'
default_src
'
:
'
none
'
},
"
default-src
'
none
'"
),
({
'
font_src
'
:
'
unsafe-inline
'
},
"
font-src
'
unsafe-inline
'"
),
({
'
frame_src
'
:
'
unsafe-eval
'
},
"
frame-src
'
unsafe-eval
'"
),
({
'
img_src
'
:
'
strict-dynamic
'
},
"
img-src
'
strict-dynamic
'"
),
({
'
manifest_src
'
:
'
nonce-2726c7f26c
'
},
"
manifest-src
'
nonce-2726c7f26c
'"
),
({
'
media_src
'
:
(
'
sha256-076c8f1ca6979ef156b510a121b69b626
'
'
5011597557ca2971db5ad5a2743545f
'
)},
"
media-src
'
{:s}
'"
.
format
(
'
sha256-076c8f1ca6979ef156b510a121b69b626
'
'
5011597557ca2971db5ad5a2743545f
'
)),
({
'
object_src
'
:
'
*
'
},
"
object-src
'
*
'"
),
({
'
script_src
'
:
'
https://example.org/
'
},
"
script-src https://example.org/
"
),
({
'
style_src
'
:
'
self
'
},
"
style-src
'
self
'"
),
({
'
worker_src
'
:
'
self https://example.org/ https://example.com/
'
},
"
worker-src
'
self
'
https://example.org/ https://example.com/
"
),
# [document directive]
({
'
base_uri
'
:
'
self
'
,
'
default_src
'
:
'
none
'
},
"
default-src
'
none
'
; base-uri
'
self
'"
),
({
'
plugin_types
'
:
'
application/x-schockwave-flash
'
,
'
object_src
'
:
''
},
"
plugin-types application/x-schockwave-flash
"
),
({
'
plugin_types
'
:
'
application/xhtml+xml
'
,
'
object_src
'
:
''
},
"
plugin-types application/xhtml+xml
"
),
({
'
plugin_types
'
:
'
application/vnd.mozilla.xul+xml
'
,
'
object_src
'
:
''
},
"
plugin-types application/vnd.mozilla.xul+xml
"
),
({
'
plugin_types
'
:
'
video/3gpp2
'
,
'
object_src
'
:
''
},
"
plugin-types video/3gpp2
"
),
({
'
sandbox
'
:
'
allow-forms
'
},
"
sandbox allow-forms
"
),
# [navigation directive]
({
'
form_action
'
:
'
self
'
},
"
form-action
'
self
'"
),
({
'
frame_ancestors
'
:
'
self
'
},
"
frame-ancestors
'
self
'"
),
({
'
default_src
'
:
'
https:
'
,
'
report_uri
'
:
'
/csp-violation-report-endpoint/
'
},
"
default-src https:; report-uri /csp-violation-report-endpoint/
"
),
({
'
default_src
'
:
'
https:
'
,
'
report_to
'
:
'
/csp-violation-report-endpoint/
'
},
"
default-src https:; report-to /csp-violation-report-endpoint/
"
),
# - block_all_mixed_content
# - referrer
# - require_sri_for
# - upgrade_insecure_requests
])
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
test_build_csp_header_config_argument
():
from
collections
import
namedtuple
from
pyramid_secure_response.csp_coverage
import
build_csp_header
with
pytest
.
raises
(
ValueError
):
build_csp_header
(
'
not dict or namedtuple
'
)
with
pytest
.
raises
(
ValueError
):
build_csp_header
(
tuple
())
assert
''
==
build_csp_header
({})
# pylint: disable=invalid-name
Config
=
namedtuple
(
'
Config
'
,
'
enabled
'
)
assert
''
==
build_csp_header
(
Config
(
True
))
def
test_csp_coverage_tween_with_disabled
(
mocker
,
dummy_request
):
mocker
.
patch
(
'
pyramid_secure_response.csp_coverage.apply_path_filter
'
,
return_value
=
True
)
from
pyramid.response
import
Response
from
pyramid_secure_response.csp_coverage
import
(
apply_path_filter
,
)
dummy_request
.
registry
.
settings
=
{
'
pyramid_secure_response.csp_coverage.enabled
'
:
'
False
'
}
handler_stub
=
mocker
.
stub
(
name
=
'
handler_stub
'
)
handler_stub
.
return_value
=
Response
(
status
=
200
)
csp_coverage_tween
=
tween
(
handler_stub
,
dummy_request
.
registry
)
res
=
csp_coverage_tween
(
dummy_request
)
# pylint: disable=no-member
assert
1
==
handler_stub
.
call_count
assert
0
==
apply_path_filter
.
call_count
assert
'
Content-Security-Policy
'
not
in
res
.
headers
def
test_csp_coverage_tween_with_ignored_path
(
mocker
,
dummy_request
):
mocker
.
patch
(
'
pyramid_secure_response.csp_coverage.apply_path_filter
'
,
return_value
=
True
)
from
pyramid.response
import
Response
from
pyramid_secure_response.csp_coverage
import
(
apply_path_filter
,
)
dummy_request
.
path
=
'
/humans.txt
'
dummy_request
.
registry
.
settings
=
{
'
pyramid_secure_response.csp_coverage.enabled
'
:
'
True
'
,
'
pyramid_secure_response.csp_coverage.ignore_paths
'
:
'
\n
/humans.txt
\n
'
}
handler_stub
=
mocker
.
stub
(
name
=
'
handler_stub
'
)
handler_stub
.
return_value
=
Response
(
status
=
200
)
csp_coverage_tween
=
tween
(
handler_stub
,
dummy_request
.
registry
)
res
=
csp_coverage_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
,
(
'
/humans.txt
'
,))
assert
'
Content-Security-Policy
'
not
in
res
.
headers
def
test_csp_coverage_with_default_values
(
mocker
,
dummy_request
):
from
pyramid_secure_response
import
csp_coverage
mocker
.
spy
(
csp_coverage
,
'
apply_path_filter
'
)
from
pyramid.response
import
Response
from
pyramid_secure_response.csp_coverage
import
(
apply_path_filter
,
)
dummy_request
.
url
=
'
http://example.org/
'
dummy_request
.
registry
.
settings
=
{
'
pyramid_secure_response.csp_coverage.enabled
'
:
'
True
'
,
'
pyramid_secure_response.csp_coverage.ignore_paths
'
:
'
\n
'
,
}
handler_stub
=
mocker
.
stub
(
name
=
'
handler_stub
'
)
handler_stub
.
return_value
=
Response
(
status
=
200
)
csp_coverage_tween
=
tween
(
handler_stub
,
dummy_request
.
registry
)
res
=
csp_coverage_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
())
# does not set if header is empty
assert
'
Content-Security-Policy
'
not
in
res
.
headers
def
test_csp_coverage_tween_default_src_with_host_source
(
mocker
,
dummy_request
):
from
pyramid_secure_response
import
csp_coverage
mocker
.
spy
(
csp_coverage
,
'
apply_path_filter
'
)
from
pyramid.response
import
Response
from
pyramid_secure_response.csp_coverage
import
(
apply_path_filter
,
)
dummy_request
.
url
=
'
https://example.org/
'
dummy_request
.
registry
.
settings
=
{
'
pyramid_secure_response.csp_coverage.enabled
'
:
'
True
'
,
'
pyramid_secure_response.csp_coverage.ignore_paths
'
:
'
\n
'
,
'
pyramid_secure_response.csp_coverage.default_src
'
:
'
https://example.org/
'
,
}
handler_stub
=
mocker
.
stub
(
name
=
'
handler_stub
'
)
handler_stub
.
return_value
=
Response
(
status
=
200
)
csp_coverage_tween
=
tween
(
handler_stub
,
dummy_request
.
registry
)
res
=
csp_coverage_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
'
Content-Security-Policy
'
in
res
.
headers
assert
'
default-src https://example.org/
'
==
\
res
.
headers
[
'
Content-Security-Policy
'
]
def
test_csp_coverage_tween_default_src_with_scheme_source
(
mocker
,
dummy_request
):
from
pyramid_secure_response
import
csp_coverage
mocker
.
spy
(
csp_coverage
,
'
apply_path_filter
'
)
from
pyramid.response
import
Response
from
pyramid_secure_response.csp_coverage
import
(
apply_path_filter
,
)
dummy_request
.
url
=
'
https://example.org/
'
dummy_request
.
registry
.
settings
=
{
'
pyramid_secure_response.csp_coverage.enabled
'
:
'
True
'
,
'
pyramid_secure_response.csp_coverage.ignore_paths
'
:
'
\n
'
,
'
pyramid_secure_response.csp_coverage.default_src
'
:
'
https:
'
,
}
handler_stub
=
mocker
.
stub
(
name
=
'
handler_stub
'
)
handler_stub
.
return_value
=
Response
(
status
=
200
)
csp_coverage_tween
=
tween
(
handler_stub
,
dummy_request
.
registry
)
res
=
csp_coverage_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
'
Content-Security-Policy
'
in
res
.
headers
assert
'
default-src https:
'
==
\
res
.
headers
[
'
Content-Security-Policy
'
]
test/unit/util_test.py
View file @
9159b90c
...
...
@@ -12,8 +12,10 @@ def test_get_config_keys(dummy_request):
expected_keys
=
(
'
proto_header
'
,
'
ignore_paths
'
,
# tweens
'
ssl_redirect
'
,
'
hsts_support
'
,
'
csp_coverage
'
,
)
assert
expected_keys
==
tuple
(
config
.
_asdict
().
keys
())
...
...