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.

172 lines
5.4 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2015 GRAP - Sylvain LE GAL
  3. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  4. import json
  5. import logging
  6. from urllib2 import urlopen
  7. from odoo import api, fields, models
  8. GEOLOCALISATION_URL = u"http://ip-api.com/json/{}"
  9. _logger = logging.getLogger(__name__)
  10. class ResAuthenticationAttempt(models.Model):
  11. _name = 'res.authentication.attempt'
  12. _order = 'create_date desc'
  13. login = fields.Char(string='Tried Login', index=True)
  14. remote = fields.Char(string='Remote IP', index=True)
  15. result = fields.Selection(
  16. string='Authentication Result',
  17. selection=[
  18. ('successful', 'Successful'),
  19. ('failed', 'Failed'),
  20. ('banned', 'Banned'),
  21. ],
  22. index=True,
  23. )
  24. remote_metadata = fields.Text(
  25. string="Remote IP metadata",
  26. compute='_compute_metadata',
  27. store=True,
  28. help="Metadata publicly available for remote IP",
  29. )
  30. whitelisted = fields.Boolean(
  31. compute="_compute_whitelisted",
  32. )
  33. @api.multi
  34. @api.depends('remote')
  35. def _compute_metadata(self):
  36. for item in self:
  37. url = GEOLOCALISATION_URL.format(item.remote)
  38. try:
  39. res = json.loads(urlopen(url, timeout=5).read())
  40. except Exception:
  41. _logger.warning(
  42. "Couldn't fetch details from %s",
  43. url,
  44. exc_info=True,
  45. )
  46. else:
  47. item.remote_metadata = "\n".join(
  48. '%s: %s' % pair for pair in res.items())
  49. @api.multi
  50. def _compute_whitelisted(self):
  51. whitelist = self._whitelist_remotes()
  52. for one in self:
  53. one.whitelisted = one.remote in whitelist
  54. @api.model
  55. def _hits_limit(self, limit, remote, login=None):
  56. """Know if a given remote hits a given limit.
  57. :param int limit:
  58. The maximum amount of failures allowed.
  59. :param str remote:
  60. The remote IP to search for.
  61. :param str login:
  62. If you want to check the IP+login combination limit, supply the
  63. login.
  64. """
  65. domain = [
  66. ("remote", "=", remote),
  67. ]
  68. if login is not None:
  69. domain += [("login", "=", login)]
  70. # Find last successful login
  71. last_ok = self.search(
  72. domain + [("result", "=", "successful")],
  73. order='create_date desc',
  74. limit=1,
  75. )
  76. if last_ok:
  77. domain += [("create_date", ">", last_ok.create_date)]
  78. # Count failures since last success, if any
  79. recent_failures = self.search_count(
  80. domain + [("result", "!=", "successful")],
  81. )
  82. # Did we hit the limit?
  83. return recent_failures >= limit
  84. @api.model
  85. def _trusted(self, remote, login):
  86. """Checks if any the remote or remote+login are trusted.
  87. :param str remote:
  88. Remote IP from which the login attempt is taking place.
  89. :param str login:
  90. User login that is being tried.
  91. :return bool:
  92. ``True`` means it is trusted. ``False`` means that it is banned.
  93. """
  94. # Cannot ban without remote
  95. if not remote:
  96. return True
  97. get_param = self.env["ir.config_parameter"].sudo().get_param
  98. # Whitelisted remotes always pass
  99. if remote in self._whitelist_remotes():
  100. return True
  101. # Check if remote is banned
  102. limit = int(get_param("auth_brute_force.max_by_ip", 50))
  103. if self._hits_limit(limit, remote):
  104. _logger.warning(
  105. "Authentication failed from remote '%s'. "
  106. "The remote has been banned. "
  107. "Login tried: %r.",
  108. remote,
  109. login,
  110. )
  111. return False
  112. # Check if remote + login combination is banned
  113. limit = int(get_param("auth_brute_force.max_by_ip_user", 10))
  114. if self._hits_limit(limit, remote, login):
  115. _logger.warning(
  116. "Authentication failed from remote '%s'. "
  117. "The remote and login combination has been banned. "
  118. "Login tried: %r.",
  119. remote,
  120. login,
  121. )
  122. return False
  123. # If you get here, you are a good boy (for now)
  124. return True
  125. def _whitelist_remotes(self):
  126. """Get whitelisted remotes.
  127. :return set:
  128. Remote IPs that are whitelisted currently.
  129. """
  130. whitelist = self.env["ir.config_parameter"].sudo().get_param(
  131. "auth_brute_force.whitelist_remotes",
  132. "",
  133. )
  134. return set(whitelist.split(","))
  135. @api.multi
  136. def action_whitelist_add(self):
  137. """Add current remotes to whitelist."""
  138. whitelist = self._whitelist_remotes()
  139. whitelist |= set(self.mapped("remote"))
  140. self.env["ir.config_parameter"].set_param(
  141. "auth_brute_force.whitelist_remotes",
  142. ",".join(whitelist),
  143. )
  144. @api.multi
  145. def action_whitelist_remove(self):
  146. """Remove current remotes from whitelist."""
  147. whitelist = self._whitelist_remotes()
  148. whitelist -= set(self.mapped("remote"))
  149. self.env["ir.config_parameter"].set_param(
  150. "auth_brute_force.whitelist_remotes",
  151. ",".join(whitelist),
  152. )