Odoo14 防暴力破解登录密码

  1 # Odoo14 防暴力破解登录密码
  2 # 主要工具:redis
  3 # 实现思路:限制每个用户24小时内登录失败次数。连续超过5次失败后,需要等待一定时间后才能再次尝试登录
  4 # 配置:在你的配置文件中增加redis配置信息(如,我的是myodoo.cfg:./odoo-bin -c myodoo.cfg)
  5 [REDIS]
  6 ODOO_SESSION_REDIS=1
  7 ODOO_SESSION_REDIS_HOST=192.168.31.20
  8 ODOO_SESSION_REDIS_PORT=6379
  9 
 10 # 代码
 11 # -*- coding: utf-8 -*-
 12 from odoo import models
 13 import contextlib
 14 from odoo.http import request
 15 import logging
 16 from odoo.exceptions import AccessDenied
 17 import datetime
 18 from odoo.tools import collections
 19 from odoo import _
 20 from configparser import ConfigParser
 21 from odoo.tools import config as odoocfg
 22 from odoo.tools.func import lazy_property
 23 
 24 _logger = logging.getLogger(__name__)
 25 
 26 try:
 27     import redis
 28     from redis.sentinel import Sentinel
 29 except ImportError:
 30     redis = None  # noqa
 31     _logger.debug("Cannot 'import redis'.")
 32 
 33 import threading
 34 
 35 class SingletonRedis(object):
 36     _instance_lock = threading.Lock()
 37 
 38     def __init__(self):
 39         sentinel_host = self.get_redis_config('ODOO_SESSION_REDIS_SENTINEL_HOST')
 40         sentinel_port = int(self.get_redis_config('ODOO_SESSION_REDIS_SENTINEL_PORT', 26379))
 41         password = self.get_redis_config('ODOO_SESSION_REDIS_PASSWORD')
 42         sentinel_master_name = self.get_redis_config('ODOO_SESSION_REDIS_SENTINEL_MASTER_NAME')
 43         url = self.get_redis_config('ODOO_SESSION_REDIS_URL')
 44         host = self.get_redis_config('ODOO_SESSION_REDIS_HOST', 'localhost')
 45         port = int(self.get_redis_config('ODOO_SESSION_REDIS_PORT', 6379))
 46         if sentinel_host:
 47             sentinel = Sentinel([(sentinel_host, sentinel_port)],
 48                                 password=password)
 49             self.rd = sentinel.master_for(sentinel_master_name)
 50         elif url:
 51             self.rd = redis.from_url(url)
 52         else:
 53             self.rd = redis.Redis(host=host, port=port, password=password, db=1)
 54 
 55     def get_redis_config(self, pcname, defv=None):
 56         # print("============================:%s:%s" % (pcname, str(odoocfg.rcfile)))
 57         config_cfg = odoocfg.rcfile
 58         conf = ConfigParser()
 59         conf.read(config_cfg)
 60         if conf.has_option('REDIS', pcname):
 61             return conf.get('REDIS', pcname)
 62         else:
 63             if defv:
 64                 return defv
 65             else:
 66                 return None
 67 
 68     @classmethod
 69     def instance(cls, *args, **kwargs):
 70         if not hasattr(SingletonRedis, "_instance"):
 71             SingletonRedis._instance = SingletonRedis(*args, **kwargs)
 72         return SingletonRedis._instance
 73         # with SingletonRedis._instance_lock:   不用考虑多线程问题,redis会自己解决。所以单例模式到这里就可以了
 74         #     if not hasattr(SingletonRedis, "_instance"):
 75         #         SingletonRedis._instance = SingletonRedis(*args, **kwargs)
 76         # return SingletonRedis._instance
 77     
 78 
 79 class LoginLimit(models.Model):
 80     _inherit = 'res.users'
 81 
 82     def _bys_login_success(self, uname):
 83         rd = SingletonRedis()
 84         rd.rd.delete(uname)
 85         rd.rd.delete('bys666888_'+uname)
 86 
 87     def _bys_on_login_cooldown(self, uname):
 88         # 一天刷新一次,
 89         # 一天有10次错误的机会,
 90         # 前5次没有限制,
 91         # 后5次每次必须隔5分钟后尝试。
 92         # 10次机会耗尽,只能等24小时后再次尝试
 93         days = 1
 94         max_err = 10
 95         min_err = 5
 96         interval_sec = 5 * 60
 97 
 98         # 查看redis中有没有数据
 99         rd = SingletonRedis()
100         rd = rd.rd
101 
102         # 有的话就拿出数据,并对请求次数判断是否过期
103         failures = rd.get(uname)
104         print(uname, failures)
105         if failures:
106             failures = int(failures) + 1
107             if failures > max_err:
108                 raise AccessDenied(_("please wait %s hours before trying again.") % (str(days*24)))
109             if failures >= min_err:
110                 a = rd.ttl('bys666888_'+uname)
111                 if a > 0:
112                     raise AccessDenied(_("please wait %s seconds before trying again.") % (a))
113                     # return True
114                 else:
115                     rd.set(name='bys666888_'+uname,value=1,ex=interval_sec)
116             rd.set(name=uname,value=failures,ex=86400*days)
117         else:
118             rd.set(name=uname,value=1,ex=86400*days)
119         
120 
121     @contextlib.contextmanager
122     def _assert_can_auth(self):
123         """ Checks that the current environment even allows the current auth
124         request to happen.
125 
126         The baseline implementation is a simple linear login cooldown: after
127         a number of failures trying to log-in, the user (by login) is put on
128         cooldown. During the cooldown period, login *attempts* are ignored
129         and logged.
130 
131         .. warning::
132 
133             The login counter is not shared between workers and not
134             specifically thread-safe, the feature exists mostly for
135             rate-limiting on large number of login attempts (brute-forcing
136             passwords) so that should not be much of an issue.
137 
138             For a more complex strategy (e.g. database or distribute storage)
139             override this method. To simply change the cooldown criteria
140             (configuration, ...) override _on_login_cooldown instead.
141 
142         .. note::
143 
144             This is a *context manager* so it can be called around the login
145             procedure without having to call it itself.
146         """
147         # needs request for remote address
148         if not request:
149             yield
150             return
151 
152         user_name = request.httprequest.form['login']
153         if self._bys_on_login_cooldown(user_name):
154             raise AccessDenied(_("Too many login failures,1 \r\nplease wait a bit before trying again."))
155         try:
156             yield
157         except AccessDenied:
158             # self._bys_login_failures(user_name)
159             raise
160         else:
161             self._bys_login_success(user_name)

 

posted @ 2022-02-17 17:11  看一百次夜空里的深蓝  阅读(516)  评论(2编辑  收藏  举报