@ -31,6 +31,30 @@ class JSON(Response):
return super ( JSON , self ) . __init__ ( * args , content_type = ' application/json ' )
return super ( JSON , self ) . __init__ ( * args , content_type = ' application/json ' )
def xhr ( func ) :
""" A decorator to check for CSRF on POST/PUT/DELETE using a <form>
element and JS to execute automatically ( see #40 for a proof-of-concept).
When an attacker uses a < form > to downvote a comment , the browser * should *
add a ` Content - Type : . . . ` header with three possible values :
* application / x - www - form - urlencoded
* multipart / form - data
* text / plain
If the header is not sent or requests ` application / json ` , the request is
not forged ( XHR is restricted by CORS separately ) .
"""
def dec ( self , env , req , * args , * * kwargs ) :
if req . content_type and not req . content_type . startswith ( " application/json " ) :
raise Forbidden ( " CSRF " )
return func ( self , env , req , * args , * * kwargs )
return dec
class API ( object ) :
class API ( object ) :
FIELDS = set ( [ ' id ' , ' parent ' , ' text ' , ' author ' , ' website ' , ' email ' ,
FIELDS = set ( [ ' id ' , ' parent ' , ' text ' , ' author ' , ' website ' , ' email ' ,
@ -47,6 +71,7 @@ class API(object):
( ' edit ' , ( ' PUT ' , ' /id/<int:id> ' ) ) ,
( ' edit ' , ( ' PUT ' , ' /id/<int:id> ' ) ) ,
( ' delete ' , ( ' DELETE ' , ' /id/<int:id> ' ) ) ,
( ' delete ' , ( ' DELETE ' , ' /id/<int:id> ' ) ) ,
( ' delete ' , ( ' GET ' , ' /id/<int:id>/delete/<string:key> ' ) ) ,
( ' delete ' , ( ' GET ' , ' /id/<int:id>/delete/<string:key> ' ) ) ,
( ' activate ' , ( ' GET ' , ' /id/<int:id>/activate/<string:key> ' ) ) ,
( ' like ' , ( ' POST ' , ' /id/<int:id>/like ' ) ) ,
( ' like ' , ( ' POST ' , ' /id/<int:id>/like ' ) ) ,
( ' dislike ' , ( ' POST ' , ' /id/<int:id>/dislike ' ) ) ,
( ' dislike ' , ( ' POST ' , ' /id/<int:id>/dislike ' ) ) ,
( ' checkip ' , ( ' GET ' , ' /check-ip ' ) )
( ' checkip ' , ( ' GET ' , ' /check-ip ' ) )
@ -90,6 +115,7 @@ class API(object):
return True , " "
return True , " "
@xhr
@requires ( str , ' uri ' )
@requires ( str , ' uri ' )
def new ( self , environ , request , uri ) :
def new ( self , environ , request , uri ) :
@ -173,6 +199,7 @@ class API(object):
return Response ( json . dumps ( rv ) , 200 , content_type = ' application/json ' )
return Response ( json . dumps ( rv ) , 200 , content_type = ' application/json ' )
@xhr
def edit ( self , environ , request , id ) :
def edit ( self , environ , request , id ) :
try :
try :
@ -216,6 +243,7 @@ class API(object):
resp . headers . add ( " X-Set-Cookie " , cookie ( " isso- %i " % rv [ " id " ] ) )
resp . headers . add ( " X-Set-Cookie " , cookie ( " isso- %i " % rv [ " id " ] ) )
return resp
return resp
@xhr
def delete ( self , environ , request , id , key = None ) :
def delete ( self , environ , request , id , key = None ) :
try :
try :
@ -253,7 +281,7 @@ class API(object):
resp . headers . add ( " X-Set-Cookie " , cookie ( " isso- %i " % id ) )
resp . headers . add ( " X-Set-Cookie " , cookie ( " isso- %i " % id ) )
return resp
return resp
def activate ( self , environ , request , _ , key ) :
def activate ( self , environ , request , id , key ) :
try :
try :
id = self . isso . unsign ( key , max_age = 2 * * 32 )
id = self . isso . unsign ( key , max_age = 2 * * 32 )
@ -293,11 +321,13 @@ class API(object):
return JSON ( json . dumps ( rv ) , 200 )
return JSON ( json . dumps ( rv ) , 200 )
@xhr
def like ( self , environ , request , id ) :
def like ( self , environ , request , id ) :
nv = self . comments . vote ( True , id , utils . anonymize ( str ( request . remote_addr ) ) )
nv = self . comments . vote ( True , id , utils . anonymize ( str ( request . remote_addr ) ) )
return Response ( json . dumps ( nv ) , 200 )
return Response ( json . dumps ( nv ) , 200 )
@xhr
def dislike ( self , environ , request , id ) :
def dislike ( self , environ , request , id ) :
nv = self . comments . vote ( False , id , utils . anonymize ( str ( request . remote_addr ) ) )
nv = self . comments . vote ( False , id , utils . anonymize ( str ( request . remote_addr ) ) )