Well, as far as the delay and the blocking of the IP, I put this together, which would go somewhere in the /admin/models/access.py file, but I'd like to get some comments, as I've never coded this type of thing before so I'd like to know if there's a better way to code it, and what problems it might cause:
import os, pickle, time deny_file = os.path.join(request.folder, 'private', 'hosts.deny') denied_hosts = cache.ram('admin_denied_hosts', lambda: read_hosts_deny(), time_expire=3600) if request.client in denied_hosts: if denied_hosts[request.client] >= 5: raise HTTP(200, T( 'admin disabled because too many invalid password attempts')) def read_hosts_deny(): if os.path.exists(deny_file): with open(deny_file, 'rb') as f: d = pickle.load(f) return d else: return {} def write_hosts_deny(): with open(deny_file, 'wb') as f: pickle.dump(denied_hosts, f) def failed_login(): if not request.is_local: times_denied = 0 if request.client in denied_hosts: times_denied = denied_hosts[request.client] + 1 denied_hosts[request.client] = times_denied write_hosts_deny() time.sleep(5) def successful_login(): if not request.is_local: if request.client in denied_hosts: del denied_hosts[request.client] write_hosts_deny() #################################################### # Then in /admin/controllers/default.py -> index() # #################################################### def index(): """ Index handler """ send = request.vars.send if DEMO_MODE: session.authorized = True session.last_time = t0 if not send: send = URL('site') if session.authorized: redirect(send) elif request.vars.password: if verify_password(request.vars.password): session.authorized = True ### ADDED THE FOLLOWING LINE ### successful_login() ################################ if CHECK_VERSION: session.check_version = True else: session.check_version = False session.last_time = t0 if isinstance(send, list): # ## why does this happen? send = str(send[0]) redirect(send) else: response.flash = T('invalid password') #### ADDED THE FOLLOWING LINE ### failed_login() ################################# return dict(send=send) This adds a 5 second delay to a failed login attempt, adds the IP address to the denied_hosts dictionary, along with the number of failed attempts. This dictionary is cached in RAM and is written to /admin/private/hosts.deny to maintain the list after a restart. Once a login is successful, the failed attempt counter is reset to zero.