Skip to content

Commit

Permalink
Merge pull request #510 from MasoniteFramework/develop
Browse files Browse the repository at this point in the history
Next Minor
  • Loading branch information
josephmancuso committed Dec 23, 2018
2 parents f56944a + 38e99d7 commit 79dfc1f
Show file tree
Hide file tree
Showing 14 changed files with 143 additions and 14 deletions.
15 changes: 14 additions & 1 deletion config/middleware.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
""" Middleware Configuration Settings """

from masonite.middleware import ResponseMiddleware
from masonite.middleware import ResponseMiddleware, SecureHeadersMiddleware

from app.http.middleware.AddAttributeMiddleware import AddAttributeMiddleware
from app.http.middleware.AuthenticationMiddleware import AuthenticationMiddleware
Expand All @@ -25,6 +25,7 @@
# todo
# CsrfMiddleware,
ResponseMiddleware,
SecureHeadersMiddleware,
]

"""
Expand All @@ -50,3 +51,15 @@
AddAttributeMiddleware,
]
}

"""Secure Headers to use in masonite.middlware.SecureHeadersMiddleware"""

SECURE_HEADERS = {
'Strict-Transport-Security': 'max-age=63072000; includeSubdomains',
'X-Frame-Options': 'SAMEORIGIN',
'X-XSS-Protection': '1; mode=block',
'X-Content-Type-Options': 'sniff-test',
'Referrer-Policy': 'no-referrer, strict-origin-when-cross-origin',
'Cache-control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
}
6 changes: 4 additions & 2 deletions masonite/drivers/BaseUploadDriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
class BaseUploadDriver(BaseDriver):
"""Base class that all upload drivers inherit from."""

accept_file_types = None
accept_file_types = ('jpg', 'jpeg', 'png', 'gif', 'bmp')

def accept(self, *args, **kwargs):
"""Set file types to accept before uploading.
Expand All @@ -31,7 +31,9 @@ def validate_extension(self, filename):
"""
if self.accept_file_types is not None:
if not filename.endswith(self.accept_file_types):
raise FileTypeException("The extension file not is valid.")
raise FileTypeException("The file extension is not supported.")

return True

def get_location(self, location=None):
"""Get the location of where to upload.
Expand Down
2 changes: 1 addition & 1 deletion masonite/drivers/UploadS3Driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ def store(self, fileitem, filename=None, location=None):
Returns:
string -- Returns the file name just saved.
"""

driver = self.upload.driver('disk')
driver.accept_file_types = self.accept_file_types
driver.store(fileitem, location)
file_location = driver.file_location

Expand Down
2 changes: 1 addition & 1 deletion masonite/helpers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .static import static
from .password import password
from .validator import validate
from .misc import random_string, dot
from .misc import random_string, dot, clean_request_input
from .Extendable import Extendable
from .time import cookie_expire_time
13 changes: 13 additions & 0 deletions masonite/helpers/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,16 @@ def dot(data, compile_to=None):
compiling += notation
compiling += dot_split[1]
return compiling


def clean_request_input(value):
import html

if isinstance(value, str):
return html.escape(value)
elif isinstance(value, list):
return [html.escape(x) for x in value]
elif isinstance(value, int):
return value
elif isinstance(value, dict):
return {key: html.escape(val) for (key, val) in value.items()}
2 changes: 1 addition & 1 deletion masonite/info.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Module for specifying the Masonite version in a central location."""

VERSION = '2.1.2'
VERSION = '2.1.3'
42 changes: 42 additions & 0 deletions masonite/middleware/SecureHeadersMiddleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""SecureHeaders Middleware."""

from masonite.request import Request


class SecureHeadersMiddleware:
"""SecureHeaders Middleware."""

def __init__(self, request: Request):
"""Inject Any Dependencies From The Service Container.
Arguments:
Request {masonite.request.Request} -- The Masonite request object
"""
self.request = request
self.headers = {
'Strict-Transport-Security': 'max-age=63072000; includeSubdomains',
'X-Frame-Options': 'SAMEORIGIN',
'X-XSS-Protection': '1; mode=block',
'X-Content-Type-Options': 'nosniff',
'Referrer-Policy': 'no-referrer, strict-origin-when-cross-origin',
'Cache-control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
}

def before(self):
"""Run This Middleware Before The Route Executes.
"""
pass

def after(self):
"""Run This Middleware After The Route Executes.
"""
from config import middleware

try:
# Try importing secure headers if they exist in the config file
self.headers.update(middleware.SECURE_HEADERS)
except AttributeError:
pass

self.request.header(self.headers)
1 change: 1 addition & 0 deletions masonite/middleware/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .CsrfMiddleware import CsrfMiddleware
from .MaintenanceModeMiddleware import MaintenanceModeMiddleware
from .ResponseMiddleware import ResponseMiddleware
from .SecureHeadersMiddleware import SecureHeadersMiddleware
2 changes: 1 addition & 1 deletion masonite/providers/StatusCodeProvider.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ def boot(self):
'code': request.get_status_code()
})

response.view(rendered_view)
response.view(rendered_view, status=request.get_status())
4 changes: 2 additions & 2 deletions masonite/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from config import application
from masonite.auth.Sign import Sign
from masonite.exceptions import InvalidHTTPStatusCode
from masonite.helpers import dot
from masonite.helpers import dot, clean_request_input
from masonite.helpers.Extendable import Extendable
from masonite.helpers.routes import compile_route_to_regex
from masonite.helpers.status import response_statuses
Expand Down Expand Up @@ -213,7 +213,7 @@ def _set_standardized_request_variables(self, variables):

try:
for name in variables.keys():
value = self._get_standardized_value(variables[name])
value = clean_request_input(self._get_standardized_value(variables[name]))
self.request_variables[name.replace('[]', '')] = value
except TypeError:
self.request_variables = {}
Expand Down
19 changes: 19 additions & 0 deletions tests/helpers/test_clean_request_input.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from masonite.helpers import clean_request_input

class TestCleanRequestInput:

def test_can_clean_string(self):
assert clean_request_input('<img """><script>alert(\'hey\')</script>">') == '&lt;img &quot;&quot;&quot;&gt;&lt;script&gt;alert(&#x27;hey&#x27;)&lt;/script&gt;&quot;&gt;'

def test_can_clean_list(self):
assert clean_request_input(
['<img """><script>alert(\'hey\')</script>">', '<img """><script>alert(\'hey\')</script>">']
) == [
'&lt;img &quot;&quot;&quot;&gt;&lt;script&gt;alert(&#x27;hey&#x27;)&lt;/script&gt;&quot;&gt;',
'&lt;img &quot;&quot;&quot;&gt;&lt;script&gt;alert(&#x27;hey&#x27;)&lt;/script&gt;&quot;&gt;'
]

def test_can_clean_dictionary(self):
assert clean_request_input(
{'key': '<img """><script>alert(\'hey\')</script>">'}
) == {'key': '&lt;img &quot;&quot;&quot;&gt;&lt;script&gt;alert(&#x27;hey&#x27;)&lt;/script&gt;&quot;&gt;'}
29 changes: 29 additions & 0 deletions tests/middleware/test_secure_headers_middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
""" Test Maintenance Mode Midddleware """
import os

from masonite.app import App
from masonite.request import Request
from masonite.middleware import SecureHeadersMiddleware
from masonite.testsuite import generate_wsgi, TestSuite

from config import middleware


class TestSecureHeadersMiddleware:

def setup_method(self):
self.request = Request(generate_wsgi())
self.middleware = SecureHeadersMiddleware(self.request)
self.app = TestSuite().create_container().container
self.app.bind('Request', self.request.load_app(self.app))
self.request = self.app.make('Request')

def test_secure_headers_middleware(self):
self.middleware.after()
assert self.request.header('Strict-Transport-Security') == 'max-age=63072000; includeSubdomains'
assert self.request.header('X-Frame-Options') == 'SAMEORIGIN'

def test_secure_headers_gets_middleware_from_the_config(self):
self.request = self.app.make('Request')
self.middleware.after()
assert self.request.header('X-Content-Type-Options') == 'sniff-test'
3 changes: 1 addition & 2 deletions tests/test_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,8 +412,7 @@ def test_request_gets_all_headers(self):
request = app.make('Request').load_app(app)

request.header('TEST1', 'set_this_item')
request.header('TEST2', 'set_this_item', http_prefix=None)
assert request.get_headers() == [('TEST1', 'set_this_item'), ('TEST2', 'set_this_item')]
assert request.get_headers() == [('TEST1', 'set_this_item')]

def test_request_sets_str_status_code(self):
app = App()
Expand Down
17 changes: 14 additions & 3 deletions tests/test_upload_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def setup_method(self):
self.app.bind('Test', object)
self.app.bind('StorageConfig', storage)
self.app.bind('UploadDiskDriver', UploadDiskDriver)
self.app.bind('UploadS3Driver', UploadS3Driver)
self.app.bind('Application', application)
self.app.bind('UploadManager', UploadManager().load_container(self.app))

Expand All @@ -47,6 +48,10 @@ def test_upload_manager_throws_error_with_incorrect_file_type(self):
with pytest.raises(UnacceptableDriverType):
self.app.make('UploadManager').driver(static)

def test_upload_manager_changes_accepted_files(self):
self.app.make('UploadManager').driver('disk').accept('yml').accept_file_types == ('yml')
self.app.make('UploadManager').driver('s3').accept('yml').accept_file_types == ('yml')

def test_upload_manager_raises_driver_not_found_error(self):
self.app = App()
self.app.bind('Test', object)
Expand Down Expand Up @@ -78,14 +83,14 @@ def test_upload_file_with_location(self):
This test is responsible for checking if you upload a file correctly.
"""

assert UploadManager(self.app).driver('disk').store(ImageMock(), 'uploads')
assert UploadManager(self.app).driver('disk').store(ImageMock(), location='uploads')

def test_upload_file_with_location_from_driver(self):
"""
This test is responsible for checking if you upload a file correctly.
"""

assert UploadManager(self.app).driver('disk').store(ImageMock(), 'disk.uploading')
assert UploadManager(self.app).driver('disk').store(ImageMock(), location='disk.uploading')

def test_upload_manage_accept_files(self):
"""
Expand All @@ -104,6 +109,12 @@ def test_upload_manage_accept_files_error(self):
def test_upload_with_new_filename(self):
assert self.app.make('UploadManager').driver('disk').store(ImageMock(), filename='newname.jpg') == 'newname.jpg'

def test_upload_manager_validates_file_ext(self):
"""
This test is responsible for checking if you upload
a file correctly with a valid extension.
"""
assert UploadManager(self.app).driver('disk').accept('jpg', 'png').validate_extension('test.png')

class ImageMock():
"""
Expand Down Expand Up @@ -140,7 +151,7 @@ def test_upload_file_for_s3(self):
assert len(self.app.make('Upload').driver('s3').store(ImageMock())) == 29

def test_upload_open_file_for_s3(self):
assert self.app.make('Upload').driver('s3').store(open('.travis.yml'))
assert self.app.make('Upload').driver('s3').accept('yml').store(open('.travis.yml'))

def test_upload_manage_accept_files(self):
"""
Expand Down

0 comments on commit 79dfc1f

Please sign in to comment.