Return 429 status when too many login attempts

This prevents to consume too much resource by sleeping over a very long period.

issue6215
review29161002
This commit is contained in:
Cédric Krier committed 2017-02-02 11:55:44 +01:00
1 parent 39c12cf496
commit afdc884277
8 files changed
+41 -9

No files matched your search

+1
View File
@@ -1,3 +1,4 @@
* Return 429 status when too many login attempts
* Add set_rpc on Field
* Add has_select_for on Database
* Store custom report translation in separate module
+9
View File
@@ -219,6 +219,15 @@ The time in seconds until a session expires.
Default: `600`
max_attempt
~~~~~~~~~~~
The maximum authentication attempt before the server answers unconditionally
`Too Many Requests` for any other attempts. The counting is done on all
attempts over a period of `timeout`.
Default: `5`
report
------
+1
View File
@@ -62,6 +62,7 @@ class TrytonConfigParser(ConfigParser.RawConfigParser):
self.add_section('session')
self.set('session', 'authentications', 'password')
self.set('session', 'timeout', 600)
self.set('session', 'max_attempt', 5)
self.add_section('report')
self.set('report', 'unoconv',
'pipe,name=trytond;urp;StarOffice.ComponentContext')
+4
View File
@@ -47,3 +47,7 @@ class ConcurrencyException(TrytonException):
message)
self.message = message
self.code = 4
class RateLimitException(TrytonException):
"""User has sent too many requests in a given amount of time."""
+10 -4
View File
@@ -15,7 +15,8 @@ from trytond import __version__
from trytond.transaction import Transaction
from trytond.cache import Cache
from trytond.exceptions import (
UserError, UserWarning, ConcurrencyException, LoginException)
UserError, UserWarning, ConcurrencyException, LoginException,
RateLimitException)
from trytond.tools import is_instance_method
from trytond.wsgi import app
from .wrappers import with_pool
@@ -49,8 +50,13 @@ def login(request, database_name, user, parameters, language=None):
except DatabaseOperationalError:
logger.error('fail to connect to %s', database_name, exc_info=True)
abort(404)
session = security.login(
database_name, user, parameters, language=language)
try:
session = security.login(
database_name, user, parameters, language=language)
code = 403
except RateLimitException:
session = None
code = 429
with Transaction().start(database_name, 0):
Cache.clean(database_name)
Cache.resets(database_name)
@@ -58,7 +64,7 @@ def login(request, database_name, user, parameters, language=None):
logger.info('%s \'%s\' from %s using %s on database \'%s\'',
msg, user, request.remote_addr, request.scheme, database_name)
if not session:
abort(403)
abort(code)
return session
+8 -1
View File
@@ -4,6 +4,9 @@ import logging
import time
import random
from werkzeug.exceptions import abort
from trytond.config import config
from trytond.wsgi import app
from trytond.protocols.wrappers import with_pool, with_transaction
@@ -46,7 +49,11 @@ def user_application(request, pool):
]))
return key
elif request.method == 'DELETE':
time.sleep(2 ** LoginAttempt.count(login) - 1)
count = LoginAttempt.count(login)
if count > config.get('session', 'max_attempt', default=5):
LoginAttempt.add(login)
abort(429)
time.sleep(2 ** count - 1)
applications = UserApplication.search([
('user.login', '=', login),
('key', '=', data.get('key')),
+6 -2
View File
@@ -34,7 +34,7 @@ from ..pool import Pool
from ..config import config
from ..pyson import PYSONEncoder, Eval
from ..rpc import RPC
from ..exceptions import LoginException
from ..exceptions import LoginException, RateLimitException
__all__ = [
'User', 'LoginAttempt', 'UserAction', 'UserGroup', 'Warning_',
@@ -496,7 +496,11 @@ class User(ModelSQL, ModelView):
Return user id if password matches
'''
LoginAttempt = Pool().get('res.user.login.attempt')
time.sleep(2 ** LoginAttempt.count(login) - 1)
count = LoginAttempt.count(login)
if count > config.get('session', 'max_attempt', default=5):
LoginAttempt.add(login)
raise RateLimitException()
time.sleep(2 ** count - 1)
for method in config.get(
'session', 'authentications', default='password').split(','):
try:
+2 -2
View File
@@ -4,7 +4,7 @@ from trytond.pool import Pool
from trytond.config import config
from trytond.transaction import Transaction
from trytond import backend
from trytond.exceptions import LoginException
from trytond.exceptions import LoginException, RateLimitException
def _get_pool(dbname):
@@ -28,7 +28,7 @@ def login(dbname, loginname, parameters, cache=True, language=None):
if count:
continue
raise
except LoginException:
except (LoginException, RateLimitException):
# Let's store any changes done
transaction.commit()
raise