# HG changeset patch # User Peter Sanchez # Date 1592423120 25200 # Wed Jun 17 12:45:20 2020 -0700 # Node ID d372c9781928d2b960c3f33f84528293fd6a8e58 # Parent eb9a4024a9fddfd26406d602e78c6150ebb87f54 Re-adding README.rst. Forgot it's required by pypi. Doh! diff --git a/README.md b/README.md --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Simple application to allow superusers to "impersonate" other non-superuser accounts. -**Version:** 1.5 +**Version:** 1.5.1 **Author:** Peter Sanchez () diff --git a/README.rst b/README.rst new file mode 100644 --- /dev/null +++ b/README.rst @@ -0,0 +1,508 @@ +django-impersonate |nlshield| +============================= + +:Info: Simple application to allow superusers to "impersonate" other non-superuser accounts. +:Version: 1.5.1 +:Author: Peter Sanchez (http://www.petersanchez.com) + +Python / Django Support +======================= + +- Python 3.6+ for Django versions 1.11, 2.2 and 3.0 + +**Note:** As of version 1.4 we are only officially supporting Python +3.6+ and following the Django support schedule. Meaning we can only +guarantee support for the Django versions actively supported by the +Django project. However, older versions of Python and Django should +continue to work. We just will not guarantee it. + +Dependencies +============ + +- Depends on your project using the django.contrib.session framework. + +**NOTE:** + +- **Version 1.5 is now only officially supporting Django's 1.11, 2.2, + and 3.0** +- **Version 1.4 is now officially supporting Python 3.6+ and Django + 1.11+** +- **Version 1.3 adds a settings format change.** Please see the + Settings section for details. +- **Version 1.0 adds new functionality by default.** Please see the + ``DISABLE_LOGGING`` settings option. +- If you need to use this with Django older than 1.8, please use + version django-impersonate == 1.0.1 +- If you need to use this with Django older than 1.7, please use + version django-impersonate == 0.9.2 +- **Version 0.9.2 partially reverts work completed in version 0.9.1.** + This is because work done to address a request in `Issue + #17 `__ + broke default behavior for all previous versions. `Issue + #24 `__ + was opened and the fix was released in 0.9.2 to address it. Please + see the new ``USE_HTTP_REFERER`` settings option. +- If you need to use this with Django older than 1.4, please use + version django-impersonate == 0.5.3 + +Installation +============ + +PIP: + +:: + + pip install django-impersonate + +Basic Manual Install: + +:: + + $ python setup.py build + $ sudo python setup.py install + +Alternative Install (Manually): + +Place impersonate directory in your Python path. Either in your Python +installs site-packages directory or set your $PYTHONPATH environment +variable to include a directory where the impersonate directory lives. + +Use +=== + +1. Add ``impersonate`` to your INSTALLED\_APPS +2. Add ``impersonate.middleware.ImpersonateMiddleware`` to your + ``MIDDLEWARE`` setting. +3. Add ``impersonate.urls`` somewhere in your url structure. Example: + + :: + + urlpatterns = patterns('', + url(r'^admin/', include(admin.site.urls)), + url(r'^impersonate/', include('impersonate.urls')), + ... (all your other urls here) ... + ) + +**Note:** The ``ImpersonationMiddleware`` class should be placed AFTER +the ``django.contrib.auth.*`` middleware classes + +Functionality +============= + +**You can now impersonate another user by hitting the following path:** + +:: + + /impersonate// + +Replace with the user id of the user you want to impersonate. + +While in impersonation "mode" the ``request.user`` object will have an +``is_impersonate`` attribute set to ``True``. So if you wanted to check +in your templates or view, you just do something like...: + +:: + + {% if user.is_impersonate %} .... {% endif %} + +The original user is available as ``request.impersonator``: + +:: + + {{ request.user }} ({{ request.impersonator }}) + +The real user is available as ``request.real_user`` - this is equivalent +to calling ``getattr(request, 'impersonator', request.user)``: + +:: + + assert request.real_user == getattr(request, 'impersonator', request.user) + +You can reference this URL with ``reverse`` or the ``{% url %}`` +template tag as ``impersonate-start`` and expects the argument of the +user ID. Example: + +:: + + reverse('impersonate-start', args=[user.id]) + reverse('impersonate-start', uid=user.id) + +**To remove the impersonation, hit the following path:** + +:: + + /impersonate/stop/ + +You can reference this URL with ``reverse`` or the ``{% url %}`` +template tag as ``impersonate-stop``. When you call this URL, you will +be redirected to the page that you used to start impersonating a user +(eg, some search results or the user list). + +**To list all users you can go to:** + +:: + + /impersonate/list/ + +This will render the template ``impersonate/list_users.html`` and will +pass the following in the context: + +- ``users`` - queryset of all users +- ``paginator`` - Django Paginator instance +- ``page`` - Current page of objects (from Paginator) +- ``page_number`` - Current page number, defaults to 1 + +You can reference this URL with ``reverse`` or the ``{% url %}`` +template tag as ``impersonate-list``. + +**To search all users you can go to:** + +:: + + /impersonate/search/ + +This will render the template 'impersonate/search\_users.html' and will +pass the following in the context: + +- ``users`` - queryset of all users +- ``paginator`` - Django Paginator instance +- ``page`` - Current page of objects (from Paginator) +- ``page_number`` - Current page number, defaults to 1 +- ``query`` - The search query that was entered + +The view will expect a GET request and look for the ``q`` variable being +passed. If present, it will search the user entries with the value of +``q``. The default fields searched are: + +``User.username``, ``User.first_name``, ``User.last_name``, +``User.email`` + +You can reference this URL with ``reverse`` or the ``{% url %}`` +template tag as ``impersonate-search``. + +**To allow some users to impersonate other users** + +You can optionally allow only some non-superuser and non-staff users to +impersonate by adding a ``CUSTOM_ALLOW`` setting option. Create a +function that takes a request object, and based on your rules, returns +``True`` if the user is allowed to impersonate or not. + +**To limit what users a user can impersonate** + +By, optionally, setting the ``CUSTOM_USER_QUERYSET`` option you can +control what users can be impersonated. It takes a request object of the +user, and returns a QuerySet of users. This is used when searching for +users to impersonate, when listing what users to impersonate, and when +trying to start impersonation. + +Signals +======= + +If you wish to hook into the impersonation session (for instance, in +order to audit access), there are two signals that are fired by +django-impersonate, at the beginning and end of a session: + +- ``session_begin`` - sent when calling the ``impersonate`` view +- ``session_end`` - sent when calling the ``stop_impersonate`` view + +Both of these signals send the same arguments: + +- ``sender`` - this is a Django signal requirement, and is always set + to None +- ``impersonator`` - a reference to the User object of the person doing + the impersonation +- ``impersonating`` - a reference to the User object of the person + being impersonated +- ``request`` - the Django HttpRequest object from which the + impersonation was invoked + +The request object is included as it contains pertinent information that +you may wish to audit - such as client IP address, user-agent string, +etc. + +For an example of how to hook up the signals, see the relevant test - +``test_successful_impersonation_signals``. + +The ``session_end`` signal will only be fired if the impersonator +explicitly ends the session. + +Settings +======== + +The following settings are available for django-impersonate. All +settings should be set as variables in a dictionary assigned to the +attribute named ``IMPERSONATE``. + +For example: + +:: + + IMPERSONATE = { + 'REDIRECT_URL': '/some-path/', + 'PAGINATE_COUNT': 10, + } + +Here are the options available... + +:: + + REDIRECT_URL + +This is the URL you want to be redirected to *after* you have chosen to +impersonate another user. If this is not present it will check for the +LOGIN\_REDIRECT\_URL setting and fall back to '/' if neither is present. +Value should be a string containing the redirect path. + +:: + + USE_HTTP_REFERER + +If this is set to ``True``, then the app will attempt to be redirect you +to the URL you were at when the impersonation began once you have +*stopped* the impersonation. For example, if you were at the url +'/foo/bar/' when you began impersonating a user, once you end the +impersonation, you will be redirected back to '/foo/bar/' instead of the +value in ``REDIRECT_URL``. + +Value should be a boolean, defaults to ``False`` + +:: + + PAGINATE_COUNT + +This is the number of users to paginate by when using the list or search +views. This defaults to 20. Value should be an integer. + +:: + + REQUIRE_SUPERUSER + +If this is set to ``True``, then only users who have ``is_superuser`` +set to ``True`` will be allowed to impersonate other users. Default is +``False``. If ``False``, then any ``is_staff`` user will be able to +impersonate other users. + +**Note:** Regardless of this setting, a ``is_staff`` user will **not** +be allowed to impersonate a ``is_superuser`` user. + +Value should be a boolean + +If the ``CUSTOM_ALLOW`` is set, then that custom function is used, and +this setting is ignored. + +:: + + ALLOW_SUPERUSER + +By default, superusers cannot be impersonated; this setting allows for +that. + +**Note:** Even when this is true, only superusers can impersonate other +superusers, regardless of the value of REQUIRE\_SUPERUSER. + +Value should be a boolean and the default is ``False``. + +:: + + URI_EXCLUSIONS + +Set to a list/tuple of url patterns that, if matched, user impersonation +is not completed. It defaults to: + +:: + + (r'^admin/',) + +If you do not want to use even the default exclusions then set the +setting to an empty list/tuple. + +:: + + CUSTOM_USER_QUERYSET + +A string that represents a function (e.g. +``module.submodule.mod.function_name``) that allows more fine grained +control over what users a user can impersonate. It takes one argument, +the request object, and should return a QuerySet. Only the users in this +queryset can be impersonated. + +This function will not be called when the request has an unauthorised +users, and will only be called when the user is allowed to impersonate +(cf. ``REQUIRE_SUPERUSER`` and ``CUSTOM_ALLOW``). + +Regardless of what this function returns, a user cannot impersonate a +superuser, even if there are superusers in the returned QuerySet. + +It is optional, and if it is not present, the user can impersonate any +user (i.e. the default is ``User.objects.all()``). + +:: + + CUSTOM_ALLOW + +A string that represents a function (e.g. +``module.submodule.mod.function_name``) that allows more fine grained +control over who can use the impersonation. It takes one argument, the +request object, and should return True to allow impersonation. +Regardless of this setting, the user must be logged in to impersonate. +If this setting is used, ``REQUIRE_SUPERUSER`` is ignored. + +It is optional, and if it is not present, the previous rules about +superuser and ``REQUIRE_SUPERUSER`` apply. + +:: + + REDIRECT_FIELD_NAME + +A string that represents the name of a request (GET) parameter which +contains the URL to redirect to after impersonating a user. This can be +used to redirect to a custom page after impersonating a user. Example: + +:: + + # in settings.py + IMPERSONATE = {'REDIRECT_FIELD_NAME': 'next'} + + # in your template + switch user + +To return always to the current page after impersonating a user, use +request.path: + +:: + + `switch user` + +Each use case is different so obviously set the next value to whatever +your case requires. + +:: + + SEARCH_FIELDS + +Array of user model fields used for building searching query. Default +value is [``User.USERNAME_FIELD``, ``first_name``, ``last_name``, +``email``]. If the User model doesn't have the ``USERNAME_FIELD`` +attribute, it falls back to 'username' (< Django 1.5). + +:: + + LOOKUP_TYPE + +A string that represents SQL lookup type for searching users by query on +fields above. It is ``icontains`` by default. + +:: + + DISABLE_LOGGING + +A boolean that can be used to disable the logging of impersonation +sessions. By default each impersonation ``session_begin`` signal will +create a new ``ImpersonationLog`` object, which is closed out (duration +calculated) at the corresponding ``session_end`` signal. + +It is optional, and defaults to False (i.e. logging is enabled). + +:: + + MAX_FILTER_SIZE + +The max number of items acceptable in the admin list filters. If the +number of items exceeds this, then the filter is removed (just shows +all). This is used by the "Filter by impersonator" filter. + +It is optional, and defaults to 100. + +Admin +===== + +As of version 1.3 django-impersonate now includes a helper admin mixin, +located at ``impersonate.admin.UserAdminImpersonateMixin``, to include +in your User model's ModelAdmin. This provides a direct link to +impersonate users from your user model's Django admin list view. Using +it is very simple, however if you're using the default +``django.contrib.auth.models.User`` model you will need to unregister +the old ModelAdmin before registering your own. + +The ``UserAdminImpersonateMixin`` has a attribute named +``open_new_window`` that **defaults to ``False``**. If this is set to +True a new window will be opened to start the new impersonation session +when clicking the impersonate link directly in the admin. + +Here's an example: + +:: + + # yourapp/admin.py + from django.contrib import admin + from django.contrib.auth.models import User + from django.contrib.auth.admin import UserAdmin + from impersonate.admin import UserAdminImpersonateMixin + + + class NewUserAdmin(UserAdminImpersonateMixin, UserAdmin): + open_new_window = True + pass + + admin.site.unregister(User) + admin.site.register(User, NewUserAdmin) + +Testing +======= + +From the repo checkout, ensure you have Django in your ``PYTHONPATH`` +and run: + +:: + + $ python runtests.py + +To get test coverage, use: + +:: + + $ coverage run --branch runtests.py + $ coverage html <- Pretty HTML files for you + $ coverage report -m <- Ascii report + +If you're bored and want to test all the supported environments, you'll +need tox.: + +:: + + $ pip install tox + $ tox + +And you should see: + +:: + + py36-django1.11: commands succeeded + py36-django2.2: commands succeeded + py36-django3.0: commands succeeded + py37-django1.11: commands succeeded + py37-django2.2: commands succeeded + py37-django3.0: commands succeeded + py38-django1.11: commands succeeded + py38-django2.2: commands succeeded + py38-django3.0: commands succeeded + congratulations :) + +Copyright & Warranty +==================== + +All documentation, libraries, and sample code are Copyright 2011 Peter +Sanchez . The library and sample code are made +available to you under the terms of the BSD license which is contained +in the included file, BSD-LICENSE. + +Commercial Support +------------------ + +This software, and lots of other software like it, has been built in +support of many of Netlandish's own projects, and the projects of our +clients. We would love to help you on your next project so get in touch +by dropping us a note at hello@netlandish.com. + +.. |nlshield| image:: https://img.shields.io/badge/100%25-Netlandish-blue.svg?style=square-flat + :target: http://www.netlandish.com diff --git a/setup.py b/setup.py --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from distutils.core import setup project_name = 'impersonate' -long_description = open('README.md').read() +long_description = open('README.rst').read() # Idea from django-registration setup.py packages, data_files = [], []