This is a collection of monkey patches and utilties to make a site more HIPAA friendly.
- Rate limits the
django.contrib.auth.forms.AuthenticationForm
and resets that rate limit whendjango.contrib.auth.forms.SetPasswordForm
is saved. - Provides an abstract base model for simple Logging capabilities (which many of this package's features rely on)
- Automatically logs out a user after a configurable amount of inactivity. A simple JavaScript pinging mechanism is used to prevent logouts when the user is actively engaged on a single page for a long time.
- Ensures passwords are complex
- Ensure users can't re-use the last N passwords they've used
- Ensure passwords are changed every M units of time (where M can differ for is_staff=True and is_staff=False users)
- Python 3+ only
- You're using the SessionMiddleware, AuthenticationMiddleware, MessageMiddleware.
- You use the
AuthenticationForm
andSetPasswordForm
fromdjango.contrib.auth.forms
to authenticate users and reset passwords. - You're using the normal
logout
andpassword_change
views fromdjango.contrib.auth.views
- You can stomach the idea of monkey patching to minimize changes to the consumer of this package.
- You're running jQuery on the client side
pip install -e this package
After SessionMiddleware
, AuthenticationMiddleware
and MessageMiddleware
, append hipaa.middleware.RequirePasswordChangeMiddleware
and hipaa.middleware.StillAliveMiddleware
to MIDDLEWARE_CLASSES
Add 'hipaa' to INSTALLED_APPS.
Somewhere in your app, create a model that is a subclass of hipaa.Log
:
# utils/models.py
from hipaa.models import Log, LogModelField
class Log(Log):
# this creates a ForeignKey to Car that you can use when logging an event
car = LogModelField("cars.Car")
class Meta:
db_table = "log"
Add some settings to your project:
# project/settings.py
from datetime import timedelta
# Automatically log the user out when you haven't seen a new HTTP request,
# or any activity in this many units of time
AUTOMATIC_LOGOUT_AFTER = timedelta(minutes=15)
# Show a pending-logout warning this amount of time before the
# pending logout.
SHOW_LOGOUT_WARNING_BEFORE = timedelta(minutes=5)
# 20 login attempts per 10 minutes
LOGIN_RATE_LIMIT = (20, timedelta(minutes=10))
# Require the password to be reset after this amount of time
REQUIRE_PASSWORD_RESET_AFTER = timedelta(days=180)
REQUIRE_PASSWORD_RESET_FOR_STAFF_AFTER = timedelta(days=90)
# Prevent the re-use of the last n passwords
CANNOT_USE_LAST_N_PASSWORDS = 24
Add <script src="{{ STATIC_URL }}hipaa/ping.js"></script>
to your base Django template (after jQuery is included) to ping the site every once in a while. This will prevent the user from being logged out if they stay on the same page for a long time.
To log events, use Log.info(), Log.warning(), Log.error() and Log.critical()
The arguments are: request=None, action="", extra="", user=None, ip_address=None, **loggable_model_fields
. If you pass in an HttpRequest object, the user and ip address will be determined automatically.
# assuming this is where your subclass of hipaa.models.Log is located...
from project.utils.models import Log
# assuming your subclass of Log has a LogModelField pointed at cars.Car
from project.cars.models import Car
def some_django_view(request, car_id)
car = get_object_or_404(Car, pk=car_id)
# this creates a log entry that records the IP address of the request,
# and the user (if the user is authenticated), with a reference to the
# car object
Log.info(
# pass the request object to get the IP address and user
request=request,
# `action` is just a string but to avoid string typing, there are a few
# built in constants like Log.CREATED, Log.EDITED, Log.DELETED,
# Log.VIEWED
action=Log.VIEWED,
extra="some abitrary text content",
car=car
)
return render(request, "cars/detail.html", {"car": car})
Log
is just a normal django model, so you can query it like Log.objects.filter(...)
.
In the example above, if the Car object was deleted, the car
field on the Log entry would be set to null. This is problematic because you lose important information about the log entry (i.e. what car it was in reference to). To get around this problem, a special field called fieldname_pk
on the Log model (where fieldname
is the name of the field) is automatically created when you declare a LogModelField
. It contains the string PK of the model the log entry is for. In this example, there is a field on the Log model called "car_pk" which contains the PK of the car object it referenced.