Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10e740da2b | ||
|
|
ba6893ca0f | ||
|
|
a99b658c3f | ||
|
|
aeffcf82ba |
@@ -1,5 +1,9 @@
|
||||
# Microdot change log
|
||||
|
||||
**Release 2.5.1** - 2025-12-21
|
||||
|
||||
- CSRF: accept cross-site request if origin is in the CORS allowed origin list ([commit](https://github.com/miguelgrinberg/microdot/commit/ba6893ca0fb3c3dd18cf934f8eee893cc2a10daa))
|
||||
|
||||
**Release 2.5.0** - 2025-12-21
|
||||
|
||||
- CSRF protection [#335](https://github.com/miguelgrinberg/microdot/issues/335) ([commit](https://github.com/miguelgrinberg/microdot/commit/0bae4c9477e9fdb231d1979cc6ed26c31e12b1aa))
|
||||
|
||||
@@ -36,7 +36,7 @@ MicroPython and CPython:
|
||||
|
||||
- Authentication support, similar to [Flask-Login](https://github.com/maxcountryman/flask-login) for Flask (**Added in version 2.1**)
|
||||
- Support for forms encoded in `multipart/form-data` format (**Added in version 2.2**)
|
||||
- CSRF protection extension
|
||||
- CSRF protection extension (**Added in version 2.5**)
|
||||
- Pub/sub mini-framework for WebSocket and SSE
|
||||
- OpenAPI integration, similar to [APIFairy](https://github.com/miguelgrinberg/apifairy) for Flask
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "microdot"
|
||||
version = "2.5.0"
|
||||
version = "2.5.1"
|
||||
authors = [
|
||||
{ name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" },
|
||||
]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from microdot.microdot import Microdot, Request, Response, abort, redirect, \
|
||||
send_file, URLPattern, AsyncBytesIO, iscoroutine # noqa: F401
|
||||
|
||||
__version__ = '2.5.0'
|
||||
__version__ = '2.5.1'
|
||||
|
||||
@@ -56,6 +56,7 @@ class CSRF:
|
||||
) or request.route in self.protected_routes:
|
||||
allow = False
|
||||
sfs = request.headers.get('Sec-Fetch-Site')
|
||||
origin = request.headers.get('Origin')
|
||||
if sfs:
|
||||
# if the Sec-Fetch-Site header was given, ensure it is not
|
||||
# cross-site
|
||||
@@ -63,14 +64,11 @@ class CSRF:
|
||||
allow = True
|
||||
elif sfs == 'same-site' and self.allow_subdomains:
|
||||
allow = True
|
||||
elif self.cors and self.cors.allowed_origins != '*':
|
||||
# if there is no Sec-Fetch-Site header but we have a list
|
||||
# of allowed origins, then we can validate the origin
|
||||
origin = request.headers.get('Origin')
|
||||
if origin is None:
|
||||
# origin wasn't given so this isn't a browser
|
||||
allow = True
|
||||
elif not self.allow_subdomains:
|
||||
if not allow and origin and self.cors and \
|
||||
self.cors.allowed_origins != '*':
|
||||
# if we have a list of allowed origins, then we can
|
||||
# validate the origin
|
||||
if not self.allow_subdomains:
|
||||
allow = origin in self.cors.allowed_origins
|
||||
else:
|
||||
origin_scheme, origin_host = origin.split('://', 1)
|
||||
@@ -83,7 +81,7 @@ class CSRF:
|
||||
):
|
||||
allow = True
|
||||
break
|
||||
else:
|
||||
if not allow and not sfs and not origin:
|
||||
allow = True # no headers to check
|
||||
|
||||
if not allow:
|
||||
|
||||
@@ -203,10 +203,6 @@ class TestCSRF(unittest.TestCase):
|
||||
|
||||
res = self._run(client.get('/'))
|
||||
self.assertEqual(res.status_code, 204)
|
||||
res = self._run(client.get(
|
||||
'/', headers={'Origin': 'foo.com'}
|
||||
))
|
||||
self.assertEqual(res.status_code, 204)
|
||||
res = self._run(client.get(
|
||||
'/', headers={'Origin': 'http://foo.com'}
|
||||
))
|
||||
@@ -230,6 +226,26 @@ class TestCSRF(unittest.TestCase):
|
||||
'/submit', headers={'Origin': 'http://bar.com:8888'}
|
||||
))
|
||||
self.assertEqual(res.status_code, 403)
|
||||
res = self._run(client.post(
|
||||
'/submit', headers={
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
'Origin': 'https://bar.com:8888',
|
||||
},
|
||||
))
|
||||
self.assertEqual(res.status_code, 204)
|
||||
res = self._run(client.post(
|
||||
'/submit', headers={
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
'Origin': 'https://bar.com:8889',
|
||||
},
|
||||
))
|
||||
self.assertEqual(res.status_code, 403)
|
||||
res = self._run(client.post(
|
||||
'/submit', headers={
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
},
|
||||
))
|
||||
self.assertEqual(res.status_code, 403)
|
||||
res = self._run(client.post(
|
||||
'/submit', headers={'Origin': 'https://x.y.bar.com:8888'}
|
||||
))
|
||||
@@ -257,10 +273,6 @@ class TestCSRF(unittest.TestCase):
|
||||
|
||||
res = self._run(client.get('/'))
|
||||
self.assertEqual(res.status_code, 204)
|
||||
res = self._run(client.get(
|
||||
'/', headers={'Origin': 'foo.com'}
|
||||
))
|
||||
self.assertEqual(res.status_code, 204)
|
||||
res = self._run(client.get(
|
||||
'/', headers={'Origin': 'http://foo.com'}
|
||||
))
|
||||
|
||||
Reference in New Issue
Block a user