Skip to content

Commit

Permalink
[REF] auth_brute_force: Cover all auth entrypoints
Browse files Browse the repository at this point in the history
To fix OCA#1125 I needed to refactor the addon.

The fix is affected by odoo/odoo#24183 and will not work until it gets fixed upstream due to the technical limitations implied. This PR is implemented assuming odoo/odoo#24187 will be merged and backported.
  • Loading branch information
yajo committed Apr 11, 2018
1 parent 52f29e6 commit 85f687b
Show file tree
Hide file tree
Showing 11 changed files with 551 additions and 106 deletions.
2 changes: 2 additions & 0 deletions auth_brute_force/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ For further information, please visit:
Known issues / Roadmap
======================

* Rename all ``successfull`` entries to ``successful``.
* The ID used to identify a remote request is the IP provided in the request
(key 'REMOTE_ADDR').
* Depending of server and / or user network configuration, the idenfication
Expand All @@ -94,6 +95,7 @@ Contributors

* Sylvain LE GAL (https://twitter.com/legalsylvain)
* David Vidal <david.vidal@tecnativa.com>
* Jairo Llopis <jairo.llopis@tecnativa.com>

Maintainer
----------
Expand Down
3 changes: 1 addition & 2 deletions auth_brute_force/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# -*- encoding: utf-8 -*-
# -*- coding: utf-8 -*-

from . import models
from . import controllers
12 changes: 6 additions & 6 deletions auth_brute_force/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@
# Copyright 2017 Tecnativa - David Vidal
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
'name': 'Authentification - Brute-force Attack',
'name': 'Authentification - Brute-Force Filter',
'version': '10.0.1.0.0',
'category': 'Tools',
'summary': "Tracks Authentication Attempts and Prevents Brute-force"
" Attacks module",
'summary': "Track Authentication Attempts and Prevent Brute-force Attacks",
'author': "GRAP, "
"Tecnativa, "
"Odoo Community Association (OCA)",
'website': 'http://www.grap.coop',
'website': 'https://github.com/OCA/server-auth',
'license': 'AGPL-3',
'depends': [
'web',
],
# If we don't depend on it, it would inhibit this addon
"auth_crypt",
],
'data': [
'security/ir_model_access.yml',
'data/ir_config_parameter.xml',
Expand Down
3 changes: 0 additions & 3 deletions auth_brute_force/controllers/__init__.py

This file was deleted.

76 changes: 0 additions & 76 deletions auth_brute_force/controllers/main.py

This file was deleted.

1 change: 1 addition & 0 deletions auth_brute_force/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@

from . import res_banned_remote
from . import res_authentication_attempt
from . import res_users
73 changes: 59 additions & 14 deletions auth_brute_force/models/res_authentication_attempt.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,80 @@
# Copyright 2015 GRAP - Sylvain LE GAL
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from odoo import _, api, fields, models
import logging
from odoo import api, fields, models, SUPERUSER_ID

_logger = logging.getLogger(__name__)


class ResAuthenticationAttempt(models.Model):
_name = 'res.authentication.attempt'
_order = 'attempt_date desc'

_ATTEMPT_RESULT = [
('successfull', _('Successfull')),
('failed', _('Failed')),
('banned', _('Banned')),
]

# Column Section
attempt_date = fields.Datetime(string='Attempt Date')
attempt_date = fields.Datetime(
default=fields.Datetime.now,
)
login = fields.Char(string='Tried Login')
remote = fields.Char(string='Remote ID')
result = fields.Selection(
selection=_ATTEMPT_RESULT, string='Authentication Result')
string='Authentication Result',
selection=[
('successfull', 'Successful'),
('failed', 'Failed'),
('banned', 'Banned'),
],
)

@api.multi
def write(self, vals):
result = super(ResAuthenticationAttempt, self).write(vals)
self._autoban()
return result

# Custom Section
@api.model
def search_last_failed(self, remote):
def create(self, vals):
result = super(ResAuthenticationAttempt, self).create(vals)
result._autoban()
return result

@api.model
def search_last_failed(self, remote, until=None):
if not until:
until = fields.Datetime.now()
last_ok = self.search(
[('result', '=', 'successfull'), ('remote', '=', remote)],
[('result', '=', 'successfull'), ('remote', '=', remote),
("attempt_date", "<=", until)],
order='attempt_date desc', limit=1)
if last_ok:
return self.search([
('remote', '=', remote),
('attempt_date', '>', last_ok.attempt_date)])
('attempt_date', '>', last_ok.attempt_date),
("attempt_date", "<=", until),
])
else:
return self.search([('remote', '=', remote)])

@api.multi
def _autoban(self):
"""Ban a remote if it had too many failures."""
limit = int(self.env["ir.config_parameter"].get_param(
"auth_brute_force.max_attempt_qty", 10))
for one in self:
if one.result != "failed":
continue
last_failures = self.search_last_failed(
one.remote,
one.attempt_date,
)
if len(last_failures) >= limit:
_logger.warning(
"Authentication failed from remote '%s'. "
"The remote has been banned. Login tried: '%s'.",
one.remote,
one.login,
)
with self.pool.cursor() as cr:
env = self.env(cr, SUPERUSER_ID)
env["res.banned.remote"].create({
'remote': one.remote,
})
28 changes: 23 additions & 5 deletions auth_brute_force/models/res_banned_remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
# Copyright 2015 GRAP - Sylvain LE GAL
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

import urllib
import json
import logging
from urllib2 import urlopen

from odoo import api, fields, models

_logger = logging.getLogger(__name__)


class ResBannedRemote(models.Model):
_name = 'res.banned.remote'
Expand All @@ -33,13 +36,28 @@ class ResBannedRemote(models.Model):
def _compute_description(self):
for item in self:
url = self._GEOLOCALISATION_URL.format(item.remote)
res = json.loads(urllib.urlopen(url).read())
item.description = ''
for k, v in res.iteritems():
item.description += '%s : %s\n' % (k, v)
try:
res = json.loads(urlopen(url, timeout=5).read())
except Exception:
_logger.error(
"Couldn't fetch details from %s",
url,
exc_info=True,
)
else:
item.description = "\n".join('%s: %s' % pair
for pair in res.items())

@api.multi
def _compute_attempt_ids(self):
for item in self:
attempt_obj = self.env['res.authentication.attempt']
item.attempt_ids = attempt_obj.search_last_failed(item.remote)

@api.model
def is_banned(self, remote):
"""Know if a remote is banned."""
return bool(self.search(
[("remote", "=", remote), ("active", "=", True)],
count=True,
))

0 comments on commit 85f687b

Please sign in to comment.