-
-
Notifications
You must be signed in to change notification settings - Fork 52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add CSRF protection #27 #38
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok so the only reference of request should be getting the input and the cookie. Make all new methods relating to CSRF inside it's own class. There should really only be 3 new additions to the codebase.
- a CSRF class
- a CSRF service provider
- a CSRF middleware
masonite/request.py
Outdated
# Generate csrf token | ||
self.generate_csrf_token() | ||
|
||
def generate_csrf_token(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok so let's move all these csrf methods out of the request class and into a new Csrf
class.
# Share token csrf | ||
token = Request.get_cookie('csrftoken') | ||
ViewClass.share( | ||
"<input type='hidden' name='csrftoken' value='" + token + "' />" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be inside it's own CSRFProvider
file. This should also be:
ViewClass.share('csrf_field', "<input type='hidden' name='csrf_token' value='{0}' />".format(token))
then inside the template, we can do:
<form action="" method="">
{{ csrf_field }}
<input type=.....
</form>
def __init__(self, Request): | ||
self.request = Request | ||
|
||
def befofe(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make sure the typo in befofe
is before
|
||
exempt = [] | ||
|
||
def __init__(self, Request): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When you add the CSRFProvider
then you'll need to also but CSRF
in this constructor
request = self.request | ||
if request.is_post(): | ||
token = request.input('csrftoken') | ||
if not request.verify_csrf_token(token) and not self.__in_except(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When you move these verify methods into it's own CSRF
class make sure you use that here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me. I'll test it out by hand in a few hours. Just some small changes I see here
masonite/providers/CsrfProvider.py
Outdated
|
||
def register(self): | ||
request = self.app.make('Request') | ||
self.app.bind('Request', request) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove the self.app.bind('Request', request)
the request is already binded to the container in the AppProvider
You're overwriting it here
|
||
def before(self): | ||
if self.request.is_post(): | ||
token = self.request.input('csrftoken') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Notice that you're getting the .input('csrftoken')
but inside the input on line 19 of CsrfProvider
it is csrf_token
. I'd go with the underscore one
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This works really really well with a few minor changes. Remember that middleware also runs on every request (with the exception of Route Middleware). But all HTTP middleware runs on every request so we can use this to our advantage. Instead of making the provider run on every request AND the middleware run on every request, we can move the logic into the middleware.
I like the idea of making the providers stackable and would hate for the documentation to say "make sure you place this provider between 2 providers" which is easy enough but what if other third parties also want that too and now we have to place it between a third party provider AND a one of ours. It just gets really messy that way so let's make providers able to stack and be placed at the bottom and still work where applicable.
Only the core providers that actually build the framework (the first 5) need to run on every request and therefore need to be at the top.
Like always if you have any questions at all about the changes please let me know I'll be happy to answer them
and not self.__in_except()): | ||
raise InvalidCSRFToken("Invalid CSRF token.") | ||
else: | ||
self.csrf.generate_token() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be .generate_csrf_token()
masonite/auth/Csrf.py
Outdated
Generate token for csrf protection | ||
""" | ||
|
||
token = binascii.b2a_hex(os.urandom(15)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is going to return a byte string. We need a unicode string in order my make a cookie so this needs to be changed to:
token = bytes(binascii.b2a_hex(os.urandom(15))).decode('utf-8')
this will return a unicode string
masonite/auth/Csrf.py
Outdated
""" | ||
|
||
token = binascii.b2a_hex(os.urandom(15)) | ||
self.request.cookie('csrftoken', token) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's call the csrf token csrf_token
. Change where needed in the code. This way when I check my browser it should say csrf_token
. Just a little cleaner as well as centralizes the naming scheme (since we now have csrftoken
and csrf_token
running around the code)
masonite/auth/Csrf.py
Outdated
Verify if csrf token is valid | ||
""" | ||
|
||
if self.request.get_cookie('csrftoken') == token: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
csrf_token
masonite/providers/CsrfProvider.py
Outdated
|
||
def boot(self, View, ViewClass, Request): | ||
# Share token csrf | ||
token = Request.get_cookie('csrftoken') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove this. see comment below
masonite/providers/CsrfProvider.py
Outdated
# Share token csrf | ||
token = Request.get_cookie('csrftoken') | ||
|
||
ViewClass.share({'csrf_field': "<input type='hidden' name='csrf_token' value='{0}' />".format(token)}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok let's move this share inside the middleware. If it's running on every request anyway then it should be fine. Since we are moving it into the middleware, we can remove everything inside this boot method. Since there is nothing in the boot method we can set wsgi = False
and now we don't have this running on every request since we're just using this provider to load things into the container. The middleware will be the main thing that fires on every request as usual.
self.request = Request | ||
self.csrf = CSRF | ||
|
||
def before(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so while testing, my middleware ended up looking like this:
from masonite.exceptions import InvalidCSRFToken
class CsrfMiddleware(object):
"""
Verify csrf token middleware
"""
exempt = []
def __init__(self, Request, CSRF, ViewClass):
self.request = Request
self.csrf = CSRF
self.view = ViewClass
def before(self):
token = self.__verify_csrf_token()
self.view.share(
{'csrf_field': "<input type='hidden' name='csrf_token' value='{0}' />".format(token)})
def after(self):
pass
def __in_except(self):
"""
Determine if the request has a URI that should pass
through CSRF verification.
"""
if self.request.path in self.exempt:
return True
else:
return False
def __verify_csrf_token(self):
if self.request.is_post():
token = self.request.input('csrf_token')
if not self.csrf.verify_csrf_token(token) and not self.__in_except():
raise InvalidCSRFToken("Invalid CSRF token.")
else:
token = self.csrf.generate_csrf_token()
return token
I basically just copied and moved all the logic into it's own private method and then copied and pasted the viewclass.share method.
No description provided.