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

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. help="Metadata publicly available for remote IP",
  28. )
  29. whitelisted = fields.Boolean(
  30. compute="_compute_whitelisted",
  31. )
  32. @api.multi
  33. @api.depends('remote')
  34. def _compute_metadata(self):
  35. for item in self:
  36. url = GEOLOCALISATION_URL.format(item.remote)
  37. try:
  38. res = json.loads(urlopen(url, timeout=5).read())
  39. except Exception:
  40. _logger.warning(
  41. "Couldn't fetch details from %s",
  42. url,
  43. exc_info=True,
  44. )
  45. else:
  46. item.remote_metadata = "\n".join(
  47. '%s: %s' % pair for pair in res.items())
  48. @api.multi
  49. def _compute_whitelisted(self):
  50. whitelist = self._whitelist_remotes()
  51. for one in self:
  52. one.whitelisted = one.remote in whitelist
  53. @api.model
  54. def _hits_limit(self, limit, remote, login=None):
  55. """Know if a given remote hits a given limit.
  56. :param int limit:
  57. The maximum amount of failures allowed.
  58. :param str remote:
  59. The remote IP to search for.
  60. :param str login:
  61. If you want to check the IP+login combination limit, supply the
  62. login.
  63. """
  64. domain = [
  65. ("remote", "=", remote),
  66. ]
  67. if login is not None:
  68. domain += [("login", "=", login)]
  69. # Find last successful login
  70. last_ok = self.search(
  71. domain + [("result", "=", "successful")],
  72. order='create_date desc',
  73. limit=1,
  74. )
  75. if last_ok:
  76. domain += [("create_date", ">", last_ok.create_date)]
  77. # Count failures since last success, if any
  78. recent_failures = self.search_count(
  79. domain + [("result", "!=", "successful")],
  80. )
  81. # Did we hit the limit?
  82. return recent_failures >= limit
  83. @api.model
  84. def _trusted(self, remote, login):
  85. """Checks if any the remote or remote+login are trusted.
  86. :param str remote:
  87. Remote IP from which the login attempt is taking place.
  88. :param str login:
  89. User login that is being tried.
  90. :return bool:
  91. ``True`` means it is trusted. ``False`` means that it is banned.
  92. """
  93. # Cannot ban without remote
  94. if not remote:
  95. return True
  96. get_param = self.env["ir.config_parameter"].sudo().get_param
  97. # Whitelisted remotes always pass
  98. if remote in self._whitelist_remotes():
  99. return True
  100. # Check if remote is banned
  101. limit = int(get_param("auth_brute_force.max_by_ip", 50))
  102. if self._hits_limit(limit, remote):
  103. _logger.warning(
  104. "Authentication failed from remote '%s'. "
  105. "The remote has been banned. "
  106. "Login tried: %r.",
  107. remote,
  108. login,
  109. )
  110. return False
  111. # Check if remote + login combination is banned
  112. limit = int(get_param("auth_brute_force.max_by_ip_user", 10))
  113. if self._hits_limit(limit, remote, login):
  114. _logger.warning(
  115. "Authentication failed from remote '%s'. "
  116. "The remote and login combination has been banned. "
  117. "Login tried: %r.",
  118. remote,
  119. login,
  120. )
  121. return False
  122. # If you get here, you are a good boy (for now)
  123. return True
  124. def _whitelist_remotes(self):
  125. """Get whitelisted remotes.
  126. :return set:
  127. Remote IPs that are whitelisted currently.
  128. """
  129. whitelist = self.env["ir.config_parameter"].sudo().get_param(
  130. "auth_brute_force.whitelist_remotes",
  131. "",
  132. )
  133. return set(whitelist.split(","))
  134. @api.multi
  135. def action_whitelist_add(self):
  136. """Add current remotes to whitelist."""
  137. whitelist = self._whitelist_remotes()
  138. whitelist |= set(self.mapped("remote"))
  139. self.env["ir.config_parameter"].set_param(
  140. "auth_brute_force.whitelist_remotes",
  141. ",".join(whitelist),
  142. )
  143. @api.multi
  144. def action_whitelist_remove(self):
  145. """Remove current remotes from whitelist."""
  146. whitelist = self._whitelist_remotes()
  147. whitelist -= set(self.mapped("remote"))
  148. self.env["ir.config_parameter"].set_param(
  149. "auth_brute_force.whitelist_remotes",
  150. ",".join(whitelist),
  151. )