Mercurial > p > roundup > code
diff roundup/cgi/client.py @ 7556:273c8c2b5042
fix(api): - issue2551063 - Rest/Xmlrpc interfaces needs failed login protection.
Failed API login rate limiting with expiring lockout added.
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Wed, 19 Jul 2023 20:37:45 -0400 |
| parents | 1cf1ffa65522 |
| children | f8b07ffd0226 |
line wrap: on
line diff
--- a/roundup/cgi/client.py Tue Jul 18 23:18:09 2023 -0400 +++ b/roundup/cgi/client.py Wed Jul 19 20:37:45 2023 -0400 @@ -43,8 +43,8 @@ Redirect, SendFile, SendStaticFile, SeriousError) from roundup.cgi.form_parser import FormParser -from roundup.exceptions import LoginError, Reject, RejectRaw, \ - Unauthorised, UsageError +from roundup.exceptions import LoginError, RateLimitExceeded, Reject, \ + RejectRaw, Unauthorised, UsageError from roundup.mailer import Mailer, MessageSendError @@ -572,7 +572,7 @@ self.determine_language() # Open the database as the correct user. try: - self.determine_user() + self.determine_user(is_api="xmlrpc") self.db.tx_Source = "xmlrpc" self.db.i18n = self.translator except LoginError as msg: @@ -583,6 +583,14 @@ self.setHeader("Content-Length", str(len(output))) self.write(s2b(output)) return + except RateLimitExceeded as msg: + output = xmlrpc_.client.dumps( + xmlrpc_.client.Fault(429, "%s" % msg), + allow_none=True) + self.setHeader("Content-Type", "text/xml") + self.setHeader("Content-Length", str(len(output))) + self.write(s2b(output)) + return if not self.db.security.hasPermission('Xmlrpc Access', self.userid): output = xmlrpc_.client.dumps( @@ -655,13 +663,19 @@ # Open the database as the correct user. # TODO: add everything to RestfulDispatcher try: - self.determine_user() + self.determine_user(is_api="rest") self.db.tx_Source = "rest" self.db.i18n = self.translator except LoginError as err: output = s2b("Invalid Login - %s" % str(err)) self.reject_request(output, status=http_.client.UNAUTHORIZED) return + except RateLimitExceeded as err: + output = s2b("%s" % str(err)) + # PYTHON2:FIXME http_.client.TOO_MANY_REQUESTS missing + # python2 so use numeric code. + self.reject_request(output, status=429) + return # verify Origin is allowed on all requests including GET. # If a GET, missing origin is allowed (i.e. same site GET request) @@ -854,6 +868,8 @@ except SysCallError: # OpenSSL.SSL.SysCallError is similar to IOError above pass + except RateLimitExceeded: + raise except SeriousError as message: self.write_html(str(message)) @@ -917,6 +933,9 @@ except FormError as e: self.add_error_message(self._('Form Error: ') + str(e)) self.write_html(self.renderContext()) + except RateLimitExceeded as e: + self.add_error_message(str(e)) + self.write_html(self.renderContext()) except IOError: # IOErrors here are due to the client disconnecting before # receiving the reply. @@ -1110,7 +1129,7 @@ return(token) - def determine_user(self): + def determine_user(self, is_api=False): """Determine who the user is""" self.opendb('admin') @@ -1171,8 +1190,8 @@ # So we set the user to anonymous first. self.make_user_anonymous() login = self.get_action_class('login')(self) - login.verifyLogin(username, password) - except LoginError: + login.verifyLogin(username, password, is_api=is_api) + except (LoginError, RateLimitExceeded): self.make_user_anonymous() raise user = username
