]>
git.gir.st - subscriptionfeed.git/blob - app/__init__.py
4 from flask
import Flask
6 from . common
. common
import *
7 from . common
. user
import init_login
10 app
. secret_key
= base64
. b64decode ( cf
[ 'frontend' ]. get ( 'secret_key' , '' )) or \
11 secrets
. token_bytes ( 16 ) # development fallback; CSRF/cookies won't persist.
14 for name
in cf
[ 'frontend' ][ 'modules' ]. split ( ',' ):
15 blueprint
= importlib
. import_module ( '.' + name
, __name__
)
16 app
. register_blueprint ( blueprint
. frontend
)
18 # TODO: move this somewhere else
19 @app . template_global ()
20 def querystring_page ( fields
):
21 tmp
= dict ( request
. args
)
22 for field
, what
in fields
. items ():
23 if type ( what
) is tuple :
24 ( plusminus
, default
) = what
25 tmp
[ field
] = int ( tmp
. get ( field
, str ( default
))) + plusminus
26 elif type ( what
) is type ( None ):
27 if field
in tmp
: del tmp
[ field
]
30 from werkzeug
. urls
import url_encode
31 return url_encode ( tmp
)
33 # TODO: should this go somewhere else?
34 # This error handler logs requests to external apis, and POST data. this makes debugging of api responses easier, as the request can be reconstructed and replayed.
35 from flask
import g
, request
36 from werkzeug
. exceptions
import InternalServerError
37 @app . errorhandler ( InternalServerError
)
39 if request
. method
== "POST" :
40 app
. logger
. error ( request
. data
)
41 if 'api_requests' in g
:
42 app
. logger
. error ( g
. api_requests
)
45 # TODO: build a proper flask extension
46 # Magic CSRF protection: This modifies outgoing HTML responses and injects a csrf token into all forms.
47 # All post requests are then checked if they contain the valid token.
49 # - knobs: mimetypes, http methods, form field name, token generator
50 # - inject a http header into all responses (that could be used by apis)
51 # - allow csrf token to be passed in http header, json, ...
52 # - a decorator on routes to opt out of verification or output munging
53 # https://stackoverflow.com/questions/19574694/flask-hit-decorator-before-before-request-signal-fires
54 # - allow specifying hmac message contents (currently request.remote_addr)
57 from flask
import request
58 from werkzeug
. exceptions
import BadRequest
59 from html
. parser
import HTMLParser
60 @app . template_global ()
62 # TODO: will fail behind reverse proxy (remote_addr always localhost)
63 return hmac
. new ( app
. secret_key
, request
. remote_addr
. encode ( 'ascii' ), hashlib
. sha256
). hexdigest ()
65 def add_csrf_protection ( response
):
66 if response
. mimetype
== "text/html" :
67 csrf_elem
= f
'<input type="hidden" name="csrf" value="{csrf_token()}"/>'
68 new_response
= add_csrf ( response
. get_data (). decode ( 'utf-8' ), csrf_elem
)
69 response
. set_data ( new_response
. encode ( 'utf-8' ))
72 def verify_csrf_protection ():
73 if request
. method
== "POST" and request
. form
. get ( 'csrf' ) != csrf_token ():
74 raise BadRequest ( "CSRF validation failed" )
75 request
. form
= request
. form
. copy () # make it mutable
76 request
. form
. poplist ( 'csrf' ) # remove our csrf again
77 def add_csrf ( html_in
, csrf_elem
):
78 class FindForms ( HTMLParser
):
79 def __init__ ( self
, html
):
81 self
. forms
= [] # tuples of (line_number, tag_offset, tag_length)
83 def handle_starttag ( self
, tag
, attrs
):
84 line
, offset
= self
. getpos ()
85 if tag
== "form" and dict ( attrs
). get ( 'method' , '' ). upper () == "POST" :
86 self
. forms
. append (( line
, offset
, len ( self
. get_starttag_text ())))
87 lines
= html_in
. splitlines ( keepends
= True )
88 # Note: going in reverse, to not invalidate offsets:
89 for line
, offset
, length
in reversed ( FindForms ( html_in
). forms
):
91 lines
[ line
- 1 ] = l
[: offset
+ length
] + csrf_elem
+ l
[ offset
+ length
:]