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

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. 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. )