Skip to content

Commit

Permalink
Merge pull request #21 from avallbona/develop
Browse files Browse the repository at this point in the history
version v2.0.4
  • Loading branch information
avallbona authored Jun 21, 2020
2 parents fdd8f7d + e64f012 commit 9549e40
Show file tree
Hide file tree
Showing 14 changed files with 125 additions and 86 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Python package

on:
push:
branches: [ master, _improving_test_action ]
branches: [ master, develop ]
pull_request:
branches: [ master, _improving_test_action ]
branches: [ master, develop ]

jobs:
test:
Expand Down
2 changes: 2 additions & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* Marko Samastur
* Andreu Vallbona
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Change Log

## 2.0.4 (2020-06-21)

* Refactored setup.py in order to pack all the needed files
* Extended the default admin templeta to show `user logged as another user`
* Added `__str__` to ImpostorLog
* Updated MANIFEST.in
* Moved some package metadata to `__init__`
* Improved code coverage

## 2.0.3 (2020-06-20)

* Adjusted github action for tests
Expand Down
7 changes: 6 additions & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
recursive-include impostor/templates *.html
include AUTHORS
include LICENSE
include CHANGELOG.MD
include README.md
recursive-include impostor *
global-exclude *.pyc
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@
Impostor is a Django application which allows staff members to login as
a different user by using their own username and password.

**Login**

![`Login`](https://i.imgur.com/TaoZyOh.png)

**Logged as**

![`Logged as`](https://i.imgur.com/db3fSi8.png)

**Impostor log**

![`Impostor log`](https://i.imgur.com/OQ9IWB7.png)

Every such authentication is recorded in database and listed in admin
interface to everyone with an access to ImpostorLog interface. However it is
not possible to delete log entries through admin interface to make covering
Expand Down Expand Up @@ -60,6 +72,9 @@ INSTALLED_APPS = [
]
```

In order to be able to see the `user logged as anotheruser` if the django admin,
be sure to include the 'impostor' app before the 'django.contrib.admin' in the **INSTALLED_APPS**.

Run

```bash
Expand Down
7 changes: 6 additions & 1 deletion impostor/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
"""Impostor."""
__VERSION__ = "2.0.3"
__version__ = "2.0.4"
__author__ = "Marko Samastur"
__email__ = "[email protected]"
__mantainer__ = "Andreu Vallbona"
__mantainer_email__ = "[email protected]"
default_app_config = "impostor.apps.ImpostorConfig"
2 changes: 1 addition & 1 deletion impostor/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@


class ImpostorConfig(AppConfig):
name = 'Impostor'
name = 'impostor'
verbose_name = 'Impostor'
8 changes: 8 additions & 0 deletions impostor/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,11 @@ class Meta:
verbose_name = _('Impostor log')
verbose_name_plural = _('Impostor logs')
ordering = ('-logged_in', 'impostor')

def __str__(self):
"""
str representation of the object.
:return:
"""
return '{} as {}'.format(self.impostor, self.imposted_as)
17 changes: 17 additions & 0 deletions impostor/templates/admin/base_site.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{% extends "admin/base_site.html" %}
{% load i18n impostor_tags%}


{% block welcome-msg %}
{% trans 'Welcome,' %}
{% get_impersonated_as request as impersonated_as %}
{% if not impersonated_as %}
<strong>{% firstof user.get_short_name user.get_username %}</strong>.
{% else %}
<span class="loggedAs">
<strong>{% firstof impersonated_as.impostor %}</strong>
{% trans ' logged as '%}
<strong>{% firstof impersonated_as.imposted_as %}</strong>.
</span>
{% endif %}
{% endblock %}
Empty file.
21 changes: 21 additions & 0 deletions impostor/templatetags/impostor_tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""template tags for impostor."""

from django import template
from impostor.models import ImpostorLog

register = template.Library()


@register.simple_tag
def get_impersonated_as(request):
"""return the impersonated_as template tag to include into the main template.
:param request:
:return:
"""
try:
impersonated_as = ImpostorLog.objects.get(token=request.session['impostor_token'])
except (ImpostorLog.DoesNotExist, KeyError):
impersonated_as = None

return impersonated_as
90 changes: 12 additions & 78 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,81 +1,16 @@
import codecs
import os
import sys
import re

from setuptools import find_packages
from setuptools import setup, find_packages

from distutils.command.install import INSTALL_SCHEMES
from distutils.command.install_data import install_data
from distutils.core import setup


class OsxInstallData(install_data):
# On MacOS, the platform-specific lib dir is /System/Library/Framework/Python/.../
# which is wrong. Python 2.5 supplied with MacOS 10.5 has an Apple-specific fix
# for this in distutils.command.install_data#306. It fixes install_lib but not
# install_data, which is why we roll our own install_data class.

def finalize_options(self):
# By the time finalize_options is called, install.install_lib is set to the
# fixed directory, so we set the installdir to install_lib. The
# install_data class uses ('install_data', 'install_dir') instead.
self.set_undefined_options('install', ('install_lib', 'install_dir'))
install_data.finalize_options(self)


if sys.platform == "darwin":
cmdclasses = {'install_data': OsxInstallData}
else:
cmdclasses = {'install_data': install_data}


def fullsplit(path, result=None):
def get_metadata(package, field):
"""
Split a pathname into components (the opposite of os.path.join) in a platform-neutral way.
:param path:
:param result:
:return:
Return package data as listed in `__{field}__` in `init.py`.
"""
if result is None:
result = []
head, tail = os.path.split(path)
if head == '':
return [tail] + result
if head == path:
return result
return fullsplit(head, [tail] + result)


# Tell distutils to put the data_files in platform-specific installation
# locations. See here for an explanation:
# http://groups.google.com/group/comp.lang.python/browse_thread/thread/35ec7b2fed36eaec/2105ee4d9e8042cb
for scheme in INSTALL_SCHEMES.values():
scheme['data'] = scheme['purelib']

# Compile the list of packages available, because distutils doesn't have
# an easy way to do this.
packages, data_files = [], []
root_dir = os.path.dirname(__file__)
if root_dir != '':
os.chdir(root_dir)
impostor_dir = 'impostor'

for dirpath, dirnames, filenames in os.walk(impostor_dir):
# Ignore dirnames that start with '.'
for i, dirname in enumerate(dirnames):
if dirname.startswith('.'):
del dirnames[i]
if '__init__.py' in filenames:
packages.append('.'.join(fullsplit(dirpath)))
elif filenames:
data_files.append([dirpath, [os.path.join(dirpath, f) for f in filenames]])

# Small hack for working with bdist_wininst.
# See http://mail.python.org/pipermail/distutils-sig/2004-August/004134.html
if len(sys.argv) > 1 and sys.argv[1] == 'bdist_wininst':
for file_info in data_files:
file_info[0] = '\\PURELIB\\%s' % file_info[0]
init_py = codecs.open(os.path.join(package, '__init__.py'), encoding='utf-8').read()
return re.search("^__{}__ = ['\"]([^'\"]+)['\"]".format(field), init_py, re.MULTILINE).group(1)


def read(fname):
Expand All @@ -91,20 +26,19 @@ def read(fname):

setup(
name="Impostor",
version="2.0.3",
version=get_metadata('impostor', 'version'),
url='https://github.com/avallbona/Impostor/',
author='Marko Samastur',
author_email='[email protected]',
maintainer='Andreu Vallbona',
maintainer_email='[email protected]',
author=get_metadata('impostor', 'author'),
author_email=get_metadata('impostor', 'email'),
maintainer=get_metadata('impostor', 'mantainer'),
maintainer_email=get_metadata('impostor', 'mantainer_email'),
description='Staff can login as a different user.',
long_description=read('README.md'),
long_description_content_type='text/markdown',
license='MIT License',
platforms=['any'],
packages=find_packages(exclude=['tests*']),
cmdclass=cmdclasses,
data_files=data_files,
include_package_data=True,
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment',
Expand Down
23 changes: 23 additions & 0 deletions tests/test_impostor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from impostor.backend import AuthBackend
from impostor.forms import BigAuthenticationForm
from impostor.models import ImpostorLog
from impostor.templatetags.impostor_tags import get_impersonated_as

admin_username = 'real_test_admin'
admin_pass = 'admin_pass'
Expand Down Expand Up @@ -125,6 +126,8 @@ def test_impersonation(self, first_user, password, impersonated_user, expected,
:param rf:
:return:
"""
setattr(rf, 'META', {})
rf.META['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,192.168.0.1'
assert ImpostorLog.objects.count() == 0
composed_username = '{} as {}'.format(first_user.username, impersonated_user.username)
authenticated_user = authenticate(request=rf, username=composed_username, password=password)
Expand All @@ -134,6 +137,7 @@ def test_impersonation(self, first_user, password, impersonated_user, expected,
log = ImpostorLog.objects.first()
assert log.impostor == first_user
assert log.imposted_as == impersonated_user
assert log.impostor_ip == '127.0.0.1'
else:
assert authenticated_user is None

Expand Down Expand Up @@ -170,3 +174,22 @@ def test_impostor_group(self, custom_settings, existing_attr):
assert AuthBackend().impostor_group is None
else:
assert AuthBackend().impostor_group is not None

@pytest.mark.parametrize('in_session,expected', [
(True, True),
(False, False)
])
def test_impersonated_as_tag(self, real_admin, real_user, rf, in_session, expected):
obj = ImpostorLog.objects.create(impostor=real_admin, imposted_as=real_user)
setattr(rf, 'session', {})
if in_session:
rf.session['impostor_token'] = obj.token
result = get_impersonated_as(rf)
if expected:
assert result == obj
else:
assert result != obj

def test_impostor_log_str(self, real_admin, real_user):
obj = ImpostorLog.objects.create(impostor=real_admin, imposted_as=real_user)
assert str(obj) == '{} as {}'.format(real_admin.username, real_user.username)
6 changes: 3 additions & 3 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ deps =
pytest-django==3.9.0
pytest-lazy-fixture==0.6.3
dj111: Django>=1.11,<2.0
dj20: Django==2.0.13,<2.1
dj21: Django>=2.1.15,<2.2
dj20: Django>=2.0,<2.1
dj21: Django>=2.1,<2.2
dj22: Django>=2.2,<3.0
dj30: Django>=3.0,<4.0
setenv =
Expand All @@ -28,4 +28,4 @@ basepython = python3.6
deps =
flake8==3.8.2
commands =
flake8 . --exclude=venv/,.tox/
flake8 . --exclude=venv/,.tox/

0 comments on commit 9549e40

Please sign in to comment.