comparison roundup/cgi/client.py @ 7153:1181157d7cec

Refactor rejecting requests; update tests, xfail test Added new Client::reject_request method. Deployed throughout handle_rest() method. Fix tests to compensate for consistent formatting of errors. Mark testRestOriginValidation test xfail. Code needed to implement it fully is only partly written. Tests for OPTIONS request on a bad attribute and valid and invalid origin tests added.
author John Rouillard <rouilj@ieee.org>
date Tue, 21 Feb 2023 22:35:58 -0500
parents 72a54826ff4f
children 89a59e46b3af
comparison
equal deleted inserted replaced
7152:4e0665238617 7153:1181157d7cec
638 self.write("") 638 self.write("")
639 else: 639 else:
640 self.setHeader("Content-Length", str(len(output))) 640 self.setHeader("Content-Length", str(len(output)))
641 self.write(output) 641 self.write(output)
642 642
643 def reject_request(self, message, message_type="text/plain",
644 status=http_.client.UNAUTHORIZED):
645 self.response_code = status
646 self.setHeader("Content-Length", str(len(message)))
647 self.setHeader("Content-Type", message_type)
648 self.write(message)
649
643 def handle_rest(self): 650 def handle_rest(self):
644 # Set the charset and language 651 # Set the charset and language
645 self.determine_charset() 652 self.determine_charset()
646 if self.instance.config["WEB_TRANSLATE_REST"]: 653 if self.instance.config["WEB_TRANSLATE_REST"]:
647 self.determine_language() 654 self.determine_language()
650 try: 657 try:
651 self.determine_user() 658 self.determine_user()
652 self.db.tx_Source = "rest" 659 self.db.tx_Source = "rest"
653 self.db.i18n = self.translator 660 self.db.i18n = self.translator
654 except LoginError as err: 661 except LoginError as err:
655 self.response_code = http_.client.UNAUTHORIZED
656 output = s2b("Invalid Login - %s" % str(err)) 662 output = s2b("Invalid Login - %s" % str(err))
657 self.setHeader("Content-Length", str(len(output))) 663 self.reject_request(output, status=http_.client.UNAUTHORIZED)
658 self.setHeader("Content-Type", "text/plain")
659 self.write(output)
660 return 664 return
661 665
662 # verify Origin is allowed on all requests including GET. 666 # verify Origin is allowed on all requests including GET.
663 # If a GET, missing origin is allowed (i.e. same site GET request) 667 # If a GET, missing origin is allowed (i.e. same site GET request)
664 if not self.is_origin_header_ok(api=True): 668 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: 669 if 'HTTP_ORIGIN' not in self.env:
671 msg = self._("Required Header Missing") 670 msg = self._("Required Header Missing")
672 else: 671 else:
673 msg = self._("Client is not allowed to use Rest Interface.") 672 msg = self._("Client is not allowed to use Rest Interface.")
674 673
674 # Use code 400. Codes 401 and 403 imply that authentication
675 # is needed or authenticated person is not authorized.
676 # Preflight doesn't do authentication.
675 output = s2b( 677 output = s2b(
676 '{ "error": { "status": 400, "msg": "%s" } }' % msg) 678 '{ "error": { "status": 400, "msg": "%s" } }' % msg)
677 self.setHeader("Content-Length", str(len(output))) 679 self.reject_request(output,
678 self.setHeader("Content-Type", "application/json") 680 message_type="application/json",
679 self.write(output) 681 status=400)
680 return 682 return
681 683
682 # Handle CORS preflight request. We know rest is enabled 684 # Handle CORS preflight request. We know rest is enabled
683 # because handle_rest is called. Preflight requests 685 # because handle_rest is called. Preflight requests
684 # are unauthenticated, so no need to check permissions. 686 # are unauthenticated, so no need to check permissions.
685 if ( self.is_cors_preflight() ): 687 if ( self.is_cors_preflight() ):
686 self.handle_preflight() 688 self.handle_preflight()
687 return 689 return
688 elif not self.db.security.hasPermission('Rest Access', self.userid): 690 elif not self.db.security.hasPermission('Rest Access', self.userid):
689 self.response_code = 403
690 output = s2b('{ "error": { "status": 403, "msg": "Forbidden." } }') 691 output = s2b('{ "error": { "status": 403, "msg": "Forbidden." } }')
691 self.setHeader("Content-Length", str(len(output))) 692 self.reject_request(output,
692 self.setHeader("Content-Type", "application/json") 693 message_type="application/json",
693 self.write(output) 694 status=403)
694 return 695 return
695 696
696 self.check_anonymous_access() 697 self.check_anonymous_access()
697 698
698 try: 699 try:
701 # raises exception on check failure. 702 # raises exception on check failure.
702 # Note this returns true for a GET request. 703 # Note this returns true for a GET request.
703 # Must check supplied Origin header for bad value first. 704 # Must check supplied Origin header for bad value first.
704 csrf_ok = self.handle_csrf(api=True) 705 csrf_ok = self.handle_csrf(api=True)
705 except (Unauthorised, UsageError) as msg: 706 except (Unauthorised, UsageError) as msg:
706 # FIXME should return what the client requests 707 # FIXME should format return value according to
707 # via accept header. 708 # client's accept header, so application/xml, text/plain etc..
708 output = s2b('{ "error": { "status": 400, "msg": "%s"}}' % 709 output = s2b('{ "error": { "status": 400, "msg": "%s"}}' %
709 str(msg)) 710 str(msg))
710 self.response_code = 400 711 self.reject_request(output,
711 self.setHeader("Content-Length", str(len(output))) 712 message_type="application/json",
712 self.setHeader("Content-Type", "application/json") 713 status=400)
713 self.write(output)
714 csrf_ok = False # we had an error, failed check 714 csrf_ok = False # we had an error, failed check
715 return 715 return
716 716
717 # With the return above the if will never be false, 717 # With the return above the if will never be false,
718 # Keeping the if so we can remove return to pass 718 # Keeping the if so we can remove return to pass

Roundup Issue Tracker: http://roundup-tracker.org/