You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
171 lines
5.3 KiB
171 lines
5.3 KiB
# -*- coding: utf-8 -*-
|
|
# Copyright 2015 GRAP - Sylvain LE GAL
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
|
|
|
import json
|
|
import logging
|
|
from urllib2 import urlopen
|
|
from odoo import api, fields, models
|
|
|
|
GEOLOCALISATION_URL = u"http://ip-api.com/json/{}"
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ResAuthenticationAttempt(models.Model):
|
|
_name = 'res.authentication.attempt'
|
|
_order = 'create_date desc'
|
|
|
|
login = fields.Char(string='Tried Login', index=True)
|
|
remote = fields.Char(string='Remote IP', index=True)
|
|
result = fields.Selection(
|
|
string='Authentication Result',
|
|
selection=[
|
|
('successful', 'Successful'),
|
|
('failed', 'Failed'),
|
|
('banned', 'Banned'),
|
|
],
|
|
index=True,
|
|
)
|
|
remote_metadata = fields.Text(
|
|
string="Remote IP metadata",
|
|
compute='_compute_metadata',
|
|
help="Metadata publicly available for remote IP",
|
|
)
|
|
whitelisted = fields.Boolean(
|
|
compute="_compute_whitelisted",
|
|
)
|
|
|
|
@api.multi
|
|
@api.depends('remote')
|
|
def _compute_metadata(self):
|
|
for item in self:
|
|
url = GEOLOCALISATION_URL.format(item.remote)
|
|
try:
|
|
res = json.loads(urlopen(url, timeout=5).read())
|
|
except Exception:
|
|
_logger.warning(
|
|
"Couldn't fetch details from %s",
|
|
url,
|
|
exc_info=True,
|
|
)
|
|
else:
|
|
item.remote_metadata = "\n".join(
|
|
'%s: %s' % pair for pair in res.items())
|
|
|
|
@api.multi
|
|
def _compute_whitelisted(self):
|
|
whitelist = self._whitelist_remotes()
|
|
for one in self:
|
|
one.whitelisted = one.remote in whitelist
|
|
|
|
@api.model
|
|
def _hits_limit(self, limit, remote, login=None):
|
|
"""Know if a given remote hits a given limit.
|
|
|
|
:param int limit:
|
|
The maximum amount of failures allowed.
|
|
|
|
:param str remote:
|
|
The remote IP to search for.
|
|
|
|
:param str login:
|
|
If you want to check the IP+login combination limit, supply the
|
|
login.
|
|
"""
|
|
domain = [
|
|
("remote", "=", remote),
|
|
]
|
|
if login is not None:
|
|
domain += [("login", "=", login)]
|
|
# Find last successful login
|
|
last_ok = self.search(
|
|
domain + [("result", "=", "successful")],
|
|
order='create_date desc',
|
|
limit=1,
|
|
)
|
|
if last_ok:
|
|
domain += [("create_date", ">", last_ok.create_date)]
|
|
# Count failures since last success, if any
|
|
recent_failures = self.search_count(
|
|
domain + [("result", "!=", "successful")],
|
|
)
|
|
# Did we hit the limit?
|
|
return recent_failures >= limit
|
|
|
|
@api.model
|
|
def _trusted(self, remote, login):
|
|
"""Checks if any the remote or remote+login are trusted.
|
|
|
|
:param str remote:
|
|
Remote IP from which the login attempt is taking place.
|
|
|
|
:param str login:
|
|
User login that is being tried.
|
|
|
|
:return bool:
|
|
``True`` means it is trusted. ``False`` means that it is banned.
|
|
"""
|
|
# Cannot ban without remote
|
|
if not remote:
|
|
return True
|
|
get_param = self.env["ir.config_parameter"].sudo().get_param
|
|
# Whitelisted remotes always pass
|
|
if remote in self._whitelist_remotes():
|
|
return True
|
|
# Check if remote is banned
|
|
limit = int(get_param("auth_brute_force.max_by_ip", 50))
|
|
if self._hits_limit(limit, remote):
|
|
_logger.warning(
|
|
"Authentication failed from remote '%s'. "
|
|
"The remote has been banned. "
|
|
"Login tried: %r.",
|
|
remote,
|
|
login,
|
|
)
|
|
return False
|
|
# Check if remote + login combination is banned
|
|
limit = int(get_param("auth_brute_force.max_by_ip_user", 10))
|
|
if self._hits_limit(limit, remote, login):
|
|
_logger.warning(
|
|
"Authentication failed from remote '%s'. "
|
|
"The remote and login combination has been banned. "
|
|
"Login tried: %r.",
|
|
remote,
|
|
login,
|
|
)
|
|
return False
|
|
# If you get here, you are a good boy (for now)
|
|
return True
|
|
|
|
def _whitelist_remotes(self):
|
|
"""Get whitelisted remotes.
|
|
|
|
:return set:
|
|
Remote IPs that are whitelisted currently.
|
|
"""
|
|
whitelist = self.env["ir.config_parameter"].sudo().get_param(
|
|
"auth_brute_force.whitelist_remotes",
|
|
"",
|
|
)
|
|
return set(whitelist.split(","))
|
|
|
|
@api.multi
|
|
def action_whitelist_add(self):
|
|
"""Add current remotes to whitelist."""
|
|
whitelist = self._whitelist_remotes()
|
|
whitelist |= set(self.mapped("remote"))
|
|
self.env["ir.config_parameter"].set_param(
|
|
"auth_brute_force.whitelist_remotes",
|
|
",".join(whitelist),
|
|
)
|
|
|
|
@api.multi
|
|
def action_whitelist_remove(self):
|
|
"""Remove current remotes from whitelist."""
|
|
whitelist = self._whitelist_remotes()
|
|
whitelist -= set(self.mapped("remote"))
|
|
self.env["ir.config_parameter"].set_param(
|
|
"auth_brute_force.whitelist_remotes",
|
|
",".join(whitelist),
|
|
)
|