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

9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 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. )