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.