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:
1 parent
39c12cf496
commit
afdc884277
8 files changed
+41
-9
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
------
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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."""
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in new issue
Block a user