@@ -621,6 +621,25 @@ def handle_xmlrpc(self):
621621 self .setHeader ("Content-Length" , str (len (output )))
622622 self .write (output )
623623
624+ def is_cors_preflight (self ):
625+ return (
626+ self .env ['REQUEST_METHOD' ] == "OPTIONS"
627+ and self .request .headers .get ("Access-Control-Request-Headers" )
628+ and self .request .headers .get ("Access-Control-Request-Method" )
629+ and self .request .headers .get ("Origin" ))
630+
631+ def handle_preflight (self ):
632+ # Call rest library to handle the pre-flight request
633+ handler = rest .RestfulInstance (self , self .db )
634+ output = handler .dispatch (self .env ['REQUEST_METHOD' ],
635+ self .path , self .form )
636+
637+ if self .response_code == 204 :
638+ self .write ("" )
639+ else :
640+ self .setHeader ("Content-Length" , str (len (output )))
641+ self .write (output )
642+
624643 def handle_rest (self ):
625644 # Set the charset and language
626645 self .determine_charset ()
@@ -640,32 +659,32 @@ def handle_rest(self):
640659 self .write (output )
641660 return
642661
643- # allow preflight request even if unauthenticated
644- if (
645- self .env ['REQUEST_METHOD' ] == "OPTIONS"
646- and self .request .headers .get ("Access-Control-Request-Headers" )
647- and self .request .headers .get ("Access-Control-Request-Method" )
648- and self .request .headers .get ("Origin" )
649- ):
650- # verify Origin is allowed
651- if not self .is_origin_header_ok (api = True ):
652- # Use code 400 as 401 and 403 imply that authentication
653- # is needed or authenticates person is not authorized.
654- # Preflight doesn't do authentication.
655- self .response_code = 400
662+ # verify Origin is allowed on all requests including GET.
663+ # If a GET, missing origin is allowed (i.e. same site GET request)
664+ if not self .is_origin_header_ok (api = True ):
665+ # Use code 400. Codes 401 and 403 imply that authentication
666+ # is needed or authenticated person is not authorized.
667+ # Preflight doesn't do authentication.
668+ self .response_code = 400
669+
670+ if 'HTTP_ORIGIN' not in self .env :
671+ msg = self ._ ("Required Header Missing" )
672+ else :
656673 msg = self ._ ("Client is not allowed to use Rest Interface." )
657- output = s2b (
658- '{ "error": { "status": 400, "msg": "%s" } }' % msg )
659- self .setHeader ("Content-Length" , str (len (output )))
660- self .setHeader ("Content-Type" , "application/json" )
661- self .write (output )
662- return
663674
664- # Call rest library to handle the pre-flight request
665- handler = rest .RestfulInstance (self , self .db )
666- output = handler .dispatch (self .env ['REQUEST_METHOD' ],
667- self .path , self .form )
675+ output = s2b (
676+ '{ "error": { "status": 400, "msg": "%s" } }' % msg )
677+ self .setHeader ("Content-Length" , str (len (output )))
678+ self .setHeader ("Content-Type" , "application/json" )
679+ self .write (output )
680+ return
668681
682+ # Handle CORS preflight request. We know rest is enabled
683+ # because handle_rest is called. Preflight requests
684+ # are unauthenticated, so no need to check permissions.
685+ if ( self .is_cors_preflight () ):
686+ self .handle_preflight ()
687+ return
669688 elif not self .db .security .hasPermission ('Rest Access' , self .userid ):
670689 self .response_code = 403
671690 output = s2b ('{ "error": { "status": 403, "msg": "Forbidden." } }' )
@@ -680,6 +699,8 @@ def handle_rest(self):
680699 # Call csrf with api (xmlrpc, rest) checks enabled.
681700 # It will return True if everything is ok,
682701 # raises exception on check failure.
702+ # Note this returns true for a GET request.
703+ # Must check supplied Origin header for bad value first.
683704 csrf_ok = self .handle_csrf (api = True )
684705 except (Unauthorised , UsageError ) as msg :
685706 # FIXME should return what the client requests
@@ -695,7 +716,7 @@ def handle_rest(self):
695716
696717 # With the return above the if will never be false,
697718 # Keeping the if so we can remove return to pass
698- # output though and format output according to accept
719+ # output though and format output according to accept
699720 # header.
700721 if csrf_ok is True :
701722 # Call rest library to handle the request
@@ -1259,10 +1280,23 @@ def check_anonymous_access(self):
12591280 "allowed to use the web interface" ))
12601281
12611282 def is_origin_header_ok (self , api = False ):
1283+ """Determine if origin is valid for the context
1284+
1285+ Allow (return True) if ORIGIN is missing and it is a GET.
1286+ Allow if ORIGIN matches the base url.
1287+ If this is a API call:
1288+ Allow if ORIGIN matches an element of allowed_api_origins.
1289+ Allow if allowed_api_origins includes '*' as first element..
1290+ Otherwise disallow.
1291+ """
1292+
12621293 try :
12631294 origin = self .env ['HTTP_ORIGIN' ]
12641295 except KeyError :
1265- return False
1296+ if self .env ['REQUEST_METHOD' ] == 'GET' :
1297+ return True
1298+ else :
1299+ return False
12661300
12671301 # note base https://host/... ends host with with a /,
12681302 # so add it to origin.
0 commit comments