Welcome to django-sudo
¶
django-sudo
is an implementation of GitHub’s Sudo Mode for Django.
What is this for?¶
django-sudo
provides an extra layer of security for after a user is already logged in. Views can
be decorated with @sudo_required
, and then a user
must re-enter their password to view that page. After verifying their password, that user has
elevated permissions for the duration of SUDO_COOKIE_AGE
. This duration is independent of the
normal session duration allowing short elevated permission durations, but retain long user sessions.
Installation¶
$ pip install django-sudo
Compatibility¶
- Django 1.4-1.9
- Python 2.6-3.5
- pypy
Contents¶
Getting Started¶
Installation¶
First, install the django-sudo
library with pip.
$ pip install django-sudo
Next, we need to add the sudo
application to our INSTALLED_APPS
. Installing the application
will automatically register the user_logged_in
and user_logged_out
signals that are needed.
INSTALLED_APPS = (
# ...
'sudo',
)
Now we need to install the required middleware into MIDDLEWARE_CLASSES
:
MIDDLEWARE_CLASSES = (
# ...
'sudo.middleware.SudoMiddleware',
)
Note
sudo.middleware.SudoMiddleware
must be installed after
django.contrib.session.middleware.SessionMiddleware
.
Proceed to the Configuration documentation.
Configuration¶
Settings¶
By default, all of the settings are optional and define sane and secure defaults.
SUDO_URL
- The url or view name for the sudo view. Default: sudo.views.sudo
SUDO_REDIRECT_URL
- Default url to be redirected to after elevating permissions. Default: /
SUDO_REDIRECT_FIELD_NAME
- The querystring argument to be used for redirection. Default: next
SUDO_COOKIE_AGE
- How long should sudo mode be active for? Duration in seconds. Default: 10800
SUDO_COOKIE_DOMAIN
- The domain to bind the sudo cookie to. Default: current exact domain.
SUDO_COOKIE_HTTPONLY
Should the cookie only be accessible via http requests? Default: True
Note
If this is set to
False
, any JavaScript files have the ability to access this cookie, so this should only be changed if you have a good reason to do so.SUDO_COOKIE_NAME
- The name of the cookie to be used for sudo mode. Default: sudo
SUDO_COOKIE_PATH
- Restrict the sudo cookie to a specific path. Default: /
SUDO_COOKIE_SECURE
Only transmit the sudo cookie over https if True. Default: matches current protocol
Note
By default, we will match the protocol that made the request. So if your sudo page is over https, we will set the
secure
flag on the cookie so it won’t be transmitted over plain http. It is highly recommended that you only usedjango-sudo
over https.SUDO_COOKIE_SALT
- An extra salt to be added into the cookie signature. Default: ‘’
SUDO_REDIRECT_TO_FIELD_NAME
- The name of the session attribute used to preserve the redirect destination between the original page request and successful sudo login. Default: sudo_redirect_to
Set up URLs¶
We need to hook up one url to use django-sudo
properly. At minimum, you need something like
the following:
(r'^sudo/$', # Whatever path you want
'sudo.views.sudo', # Required
{'template_name': 'sudo/sudo.html'} # Optionally change the template to be used
)
Required Template¶
To get up and running, we last need to create a template for the sudo page to render. By default,
the package will look for sudo/sudo.html
but can easily be overwritten by setting the
template_name
when defining the url definition as seen above.
sudo/sudo.html¶
This template gets rendered with the the following context:
form
- An instance of
SudoForm
. SUDO_REDIRECT_FIELD_NAME
- The value of
?next=/foo/
. IfSUDO_REDIRECT_FIELD_NAME
isname
, then expect to find{{ next }}
in the context, with the value of/foo/
.
After configuring things, we can now start securing pages.
Usage¶
Once we have django-sudo
installed and
configured, we need to decide which views should be secured.
-
sudo.decorators.
sudo_required
()¶ The meat of
django-sudo
comes from decorating your views with@sudo_required
much in the same way that@login_required
works.Let’s pretend that we have a page on our site that has sensitive information that we want to make extra sure that a user is allowed to see it:
from sudo.decorators import sudo_required @login_required # Make sure they're at least logged in @sudo_required # On top of being logged in, are you in sudo mode? def super_secret_stuff(request): return HttpResponse('your social security number')
That’s it! When a user visits this page and they don’t have the correct permission, they’ll be redirected to a page and prompted for their password. After entering their password, they’ll be redirected back to this page to continue on what they were trying to do.
-
class
sudo.mixins.
SudoMixin
¶ SudoMixin
provides an easy way to sudo a class-based view. Any view that inherits from this mixin is automatically wrapped by the@sudo_required
decorator.This works well with the
LoginRequiredMixin
from django-braces:from django.views import generic from braces.views import LoginRequiredMixin from sudo.mixins import SudoMixin class SuperSecretView(LoginRequiredMixin, SudoMixin, generic.TemplateView): template_name = 'secret/super-secret.html'
-
request.
is_sudo
()¶
Returns a boolean to indicate if the current request is in sudo mode or not. This gets added on by
the SudoMiddleware
. This is an shortcut for calling
has_sudo_privileges()
directly.
-
class
sudo.middleware.
SudoMiddleware
¶ By default, you just need to add this into your
MIDDLEWARE_CLASSES
list.-
has_sudo_privileges
(self, request)¶
Subclass and override
has_sudo_privileges()
if you’d like to override the default behavior ofrequest.is_sudo()
.-
process_request
(self, request)¶
Adds
is_sudo()
to the request.-
process_response
(self, request, response)¶
Controls the behavior of setting and deleting the sudo cookie for the browser.
-
-
sudo.utils.
grant_sudo_privileges
(request, max_age=SUDO_COOKIE_AGE)¶ Assigns a random token to the user’s session that allows them to have elevated permissions.
from sudo.utils import grant_sudo_privileges token = grant_sudo_privileges(request)
-
sudo.utils.
revoke_sudo_privileges
(request)¶ Revoke sudo privileges from a request explicitly
from sudo.utils import revoke_sudo_privileges revoke_sudo_privileges(request)
-
sudo.utils.
has_sudo_privileges
(request)¶ Check if a request is allowed to perform sudo actions.
from sudo.utils import has_sudo_privileges has_sudo = has_sudo_privileges(request)
Contributing¶
Getting the source¶
You will first want to clone the source repository locally with git
:
$ git clone git@github.com:mattrobenolt/django-sudo.git
Setup Environment¶
I would recommend using virtualenv to set up a dev environment. After creating an environment, install all dep dependencies with:
$ pip install -r dev-requirements.txt
Running Tests¶
Tests are run using pytest and can be found inside
tests/*
.
Tests can simply be run using:
$ py.test
This will discover and run the test suite using your default Python interpreter. To run tests for all supported platforms, we use tox.
$ tox
Submitting Patches¶
Patches are accepted via Pull Requests on GitHub.
Note
If you are submitting a security patch, please see our Security page for special instructions.
Tests¶
All new code and changed code must come with 100% test coverage to be considered for acceptance.
How does this work?¶
django-sudo
works by setting an additional cookie that must match a secret value in your
session. This cookie is ideally set to a shorter TTL than the normal session. When not in sudo mode,
any view that is decorated with @sudo_required
will require the user to re-enter their password.
Once in sudo mode, they won’t be prompted to enter their password for the next SUDO_COOKIE_AGE
seconds.
In practice, we want to serve this sudo cookie over https only to avoid a man-in-the-middle attack where someone hijacks this cookie. This can be utilized safely in situations where the sessionid cookie is being transmitted over http, but we want to make sure that secure areas of our site are not accessible with just the sessionid.
- When logging in,
django-sudo
automatically elevates your permission tosudo mode
. - A second cookie is sent to your browser (by default this cookie is named
sudo
but can be set to anything withSUDO_COOKIE_NAME
). This cookie contains a randomly generated string of characters. - The same randomly generated string of characters is stored in the user’s session.
- On subsequent requests, the cookie value must match the value that was stored in the session. If the values do not match, or the cookie is not sent at all, the user will be redirected to a page to re-enter their password.
- If they re-enter their password successfully, a new cookie is set and their permissions are again elevated.
Note
The best way to secure your site and your users is to use https. django-sudo
won’t be able
to help you if it’s being served over http.
Security¶
We take the security of django-sudo
seriously. If you believe you’ve
identified a security vulnerability, please report it to matt@ydekproductions.com
.
You may encrypt the message using the PGP fingerprint: D9D7 15C4 32A9 8C40 F794 35AB A54A 6FD1 5E45 9C77
.
Once you’ve submitted an issue via email, you should receive an acknowledgement within 48 hours, and depending on the action to be taken, you may receive further follow-up emails.
Changelog¶
2.1.0¶
- Vendored a more secure
is_safe_url
implementation from latest Django, instead of relying on a potentially insecure bundled version. See #17.
2.0.1¶
- Added
sudo.views.SudoView
class based view. This is now more extensible and should be preferred over the oldersudo.views.sudo
view function. - Removed
SUDO_FORM
setting. It’s now suggested to subclasssudo.views.SudoView
and overrideform_class
. - Added
SUDO_URL
setting to set the url for the sudo page.
2.0.0¶
- Bad release. :( Don’t install.
1.2.0¶
1.1.3¶
- Use
constant_time_compare
when verifying the correct sudo token. - Make sure to check against all
AUTHENTICATION_BACKENDS
for theSudoForm
. See #3.
1.1.2¶
- Added new setting,
SUDO_FORM
which allows you to override the default form that is used. See #2.
1.1.1¶
- Fixed a bug when using the new
SUDO_COOKIE_SALT
. If specifying a non-default salt, all cookies would be marked incorrectly as invalid. - Don’t use
request.REQUEST
anymore since that’s deprecated in modern Django. Always userequest.GET
instead since we never POSTed thenext
variable anyways.
1.1.0¶
- Switch to using signed cookies for the sudo cookie, see #1.
- Added new
SUDO_COOKIE_SALT
setting to go along with the signed cookie.
1.0.0¶
- Initial release