Codebase list django-stronghold / ccf912e
Merge branch 'dfsg_clean' Sunil Mohan Adapa 6 years ago
15 changed file(s) with 238 addition(s) and 57 deletion(s). Raw diff Collapse all Expand all
22 - "2.6"
33 - "2.7"
44 - "3.4"
5 - "3.5"
6 - "3.6"
57 env:
6 - DJANGO_VERSION=1.4.13
7 - DJANGO_VERSION=1.5.8
8 - DJANGO_VERSION=1.6.5
9 - DJANGO_VERSION=1.7.1
8 - DJANGO_VERSION="django>=1.4,<1.5"
9 - DJANGO_VERSION="django>=1.5,<1.6"
10 - DJANGO_VERSION="django>=1.6,<1.7"
11 - DJANGO_VERSION="django>=1.7,<1.8"
12 - DJANGO_VERSION="django>=1.8,<1.9"
13 - DJANGO_VERSION="django>=1.9,<1.10"
14 - DJANGO_VERSION="django>=1.10,<1.11"
15 - DJANGO_VERSION="django>=1.11,<1.12"
16 - DJANGO_VERSION="django>=2.0,<2.1"
1017 matrix:
1118 exclude:
1219 - python: "2.6"
13 env: DJANGO_VERSION=1.7.1
20 env: DJANGO_VERSION="django>=1.7,<1.8"
21 - python: "2.6"
22 env: DJANGO_VERSION="django>=1.8,<1.9"
23 - python: "2.6"
24 env: DJANGO_VERSION="django>=1.9,<1.10"
25 - python: "2.6"
26 env: DJANGO_VERSION="django>=1.10,<1.11"
27 - python: "2.6"
28 env: DJANGO_VERSION="django>=1.11,<1.12"
29 - python: "2.6"
30 env: DJANGO_VERSION="django>=2.0,<2.1"
31 - python: "2.7"
32 env: DJANGO_VERSION="django>=2.0,<2.1"
1433 - python: "3.4"
15 env: DJANGO_VERSION=1.4.13
34 env: DJANGO_VERSION="django>=1.4,<1.5"
35 - python: "3.4"
36 env: DJANGO_VERSION="django>=1.5,<1.6"
37 - python: "3.4"
38 env: DJANGO_VERSION="django>=1.6,<1.7"
39 - python: "3.4"
40 env: DJANGO_VERSION="django>=1.7,<1.8"
41 - python: "3.5"
42 env: DJANGO_VERSION="django>=1.4,<1.5"
43 - python: "3.5"
44 env: DJANGO_VERSION="django>=1.5,<1.6"
45 - python: "3.5"
46 env: DJANGO_VERSION="django>=1.6,<1.7"
47 - python: "3.5"
48 env: DJANGO_VERSION="django>=1.7,<1.8"
49 - python: "3.6"
50 env: DJANGO_VERSION="django>=1.4,<1.5"
51 - python: "3.6"
52 env: DJANGO_VERSION="django>=1.5,<1.6"
53 - python: "3.6"
54 env: DJANGO_VERSION="django>=1.6,<1.7"
55 - python: "3.6"
56 env: DJANGO_VERSION="django>=1.7,<1.8"
1657 install:
1758 - pip install -r requirements.txt
18 - pip install Django==$DJANGO_VERSION
59 - pip install $DJANGO_VERSION
1960 - python setup.py install
2061 script: make test
0 ![travis](https://travis-ci.org/mgrouchy/django-stronghold.png?branch=master)
0 [![Build Status](https://travis-ci.org/mgrouchy/django-stronghold.svg?branch=master)](https://travis-ci.org/mgrouchy/django-stronghold)
11
2 #Stronghold
2 # Stronghold
33
44 Get inside your stronghold and make all your Django views default login_required
55
77
88 WARNING: still in development, so some of the DEFAULTS and such will be changing without notice.
99
10 ##Installation
10 ## Installation
1111
1212 Install via pip.
1313
3535
3636 ```
3737
38 ##Usage
38 ## Usage
3939
4040 If you followed the installation instructions now all your views are defaulting to require a login.
4141 To make a view public again you can use the public decorator provided in `stronghold.decorators` like so:
4242
43 ###For function based views
43 ### For function based views
4444 ```python
4545 from stronghold.decorators import public
4646
5252
5353 ```
5454
55 ###for class based views
55 ### For class based views (decorator)
5656
5757 ```python
5858 from django.utils.decorators import method_decorator
6969 return super(SomeView, self).dispatch(*args, **kwargs)
7070 ```
7171
72 ##Configuration (optional)
72 ### For class based views (mixin)
73
74 ```python
75 from stronghold.views import StrongholdPublicMixin
7376
7477
75 ###STRONGHOLD_DEFAULTS
78 class SomeView(StrongholdPublicMixin, View):
79 pass
80 ```
81
82 ## Configuration (optional)
83
84
85 ### STRONGHOLD_DEFAULTS
7686
7787 Use Strongholds defaults in addition to your own settings.
7888
8797 will be made public without using the `@public` decorator.
8898
8999
90 ###STRONGHOLD_PUBLIC_URLS
100 ### STRONGHOLD_PUBLIC_URLS
91101
92102 **Default**:
93103 ```python
108118
109119 > Note: Public URL regexes are matched against [HttpRequest.path_info](https://docs.djangoproject.com/en/dev/ref/request-response/#django.http.HttpRequest.path_info).
110120
111 ###STRONGHOLD_PUBLIC_NAMED_URLS
121 ### STRONGHOLD_PUBLIC_NAMED_URLS
112122 You can add a tuple of url names in your settings file with the
113123 `STRONGHOLD_PUBLIC_NAMED_URLS` setting. Names in this setting will be reversed using
114124 `django.core.urlresolvers.reverse` and any url matching the output of the reverse
122132 If STRONGHOLD_DEFAULTS is True additionally we search for `django.contrib.auth`
123133 if it exists, we add the login and logout view names to `STRONGHOLD_PUBLIC_NAMED_URLS`
124134
125 ###STRONGHOLD_PERMISSIONS_DECORATOR
126 Optionally configure STRONGHOLD_PERMISSIONS_DECORATOR to be something besides
127 `login_required`. This allows the developer to set this to an alternative
128 decorator like `staff_member_required` or a user created decorator that
129 processes a view function and returns `None` or a `HTTPResponse`.
135 ### STRONGHOLD_USER_TEST_FUNC
136 Optionally, set STRONGHOLD_USER_TEST_FUNC to a callable to limit access to users
137 that pass a custom test. The callback receives a `User` object and should
138 return `True` if the user is authorized. This is equivalent to decorating a
139 view with `user_passes_test`.
140
141 **Example**:
142
143 ```python
144 STRONGHOLD_USER_TEST_FUNC = lambda user: user.is_staff
145 ```
130146
131147 **Default**:
148
132149 ```python
133 STRONGHOLD_PERMISSIONS_DECORATOR = login_required
150 STRONGHOLD_USER_TEST_FUNC = lambda user: user.is_authenticated
134151 ```
135152
136153 ##Compatiblity
140157 * Django 1.5.x
141158 * Django 1.6.x
142159 * Django 1.7.x
160 * Django 1.8.x
161 * Django 1.9.x
162 * Django 1.10.x
163 * Django 1.11.x
164 * Django 2.0.x
143165
144 ##Contribute
166 ## Contribute
145167
146168 See CONTRIBUTING.md
6161 # do some work
6262 #...
6363
64 for class based views
65 ~~~~~~~~~~~~~~~~~~~~~
64 for class based views (decorator)
65 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6666
6767 .. code:: python
6868
7878 @method_decorator(public)
7979 def dispatch(self, *args, **kwargs):
8080 return super(SomeView, self).dispatch(*args, **kwargs)
81
82 for class based views (mixin)
83 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
84
85 .. code:: python
86
87 from stronghold import StrongholdPublicMixin
88
89 class SomeView(StrongholdPublicMixin, View):
90 pass
8191
8292 Configuration (optional)
8393 ------------------------
142152 ``django.contrib.auth`` if it exists, we add the login and logout view
143153 names to ``STRONGHOLD_PUBLIC_NAMED_URLS``
144154
145 STRONGHOLD\_PERMISSIONS\_DECORATOR
146 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
155 STRONGHOLD\_USER\_TEST\_FUNC
156 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
157 Optionally, set STRONGHOLD_USER_TEST_FUNC to a callable to limit access to users
158 that pass a custom test. The callback receives a ``User`` object and should
159 return ``True`` if the user is authorized. This is equivalent to decorating a
160 view with ``user_passes_test``.
147161
148 Optionally configure STRONGHOLD_PERMISSIONS_DECORATOR to be something besides
149 ``login_required``. This allows the developer to set this to an alternative
150 decorator like ``staff_member_required`` or a user created decorator that
151 processes a view function and returns ``None`` or a ``HTTPResponse``.
162 **Example**:
163
164 .. code:: python
165 STRONGHOLD_USER_TEST_FUNC = lambda user: user.is_staff
152166
153167 **Default**:
154168
155169 .. code:: python
156 STRONGHOLD_PERMISSIONS_DECORATOR = login_required
170 STRONGHOLD_USER_TEST_FUNC = lambda user: user.is_authenticated
157171
158172
159173 Compatiblity
165179 - Django 1.5.x
166180 - Django 1.6.x
167181 - Django 1.7.x
182 - Django 1.8.x
183 - Django 1.9.x
184 - Django 1.10.x
185 - Django 1.11.x
186 - Django 2.0.x
168187
169188 Contribute
170189 ----------
0 [bdist_wheel]
1 universal = 1
2
1111
1212 setup(
1313 name='django-stronghold',
14 version='0.2.7',
14 version='0.3.0',
1515 description='Get inside your stronghold and make all your Django views default login_required',
1616 url='https://github.com/mgrouchy/django-stronghold',
1717 author='Mike Grouchy',
2424 install_requires=dependencies,
2525 tests_require=test_dependencies,
2626 long_description=open('README.rst').read(),
27 classifiers=(
27 classifiers=[
2828 'Development Status :: 5 - Production/Stable',
2929 'Intended Audience :: Developers',
3030 'Natural Language :: English',
3434 'Programming Language :: Python :: 2.7',
3535 'Programming Language :: Python :: 3',
3636 'Programming Language :: Python :: 3.4',
37 ),
37 'Programming Language :: Python :: 3.5',
38 'Programming Language :: Python :: 3.6',
39 ],
3840 )
00 import re
11
2 from django.core.urlresolvers import reverse, NoReverseMatch
2 try:
3 from django.urls import reverse, NoReverseMatch
4 except ImportError:
5 from django.core.urlresolvers import reverse, NoReverseMatch
36 from django.conf import settings
47 from django.contrib.auth.decorators import login_required
5
68
79 STRONGHOLD_PUBLIC_URLS = getattr(settings, 'STRONGHOLD_PUBLIC_URLS', ())
810 STRONGHOLD_DEFAULTS = getattr(settings, 'STRONGHOLD_DEFAULTS', True)
911 STRONGHOLD_PUBLIC_NAMED_URLS = getattr(settings, 'STRONGHOLD_PUBLIC_NAMED_URLS', ())
10 STRONGHOLD_PERMISSIONS_DECORATOR = getattr(settings, 'STRONGHOLD_PERMISSIONS_DECORATOR', login_required)
12
13 def is_authenticated(user):
14 """ make compatible with django 1 and 2 """
15 try:
16 return user.is_authenticated()
17 except TypeError:
18 return user.is_authenticated
19
20 STRONGHOLD_USER_TEST_FUNC = getattr(settings, 'STRONGHOLD_USER_TEST_FUNC', is_authenticated)
1121
1222
1323 if STRONGHOLD_DEFAULTS:
1424 if 'django.contrib.auth' in settings.INSTALLED_APPS:
1525 STRONGHOLD_PUBLIC_NAMED_URLS += ('login', 'logout')
16
26
1727 # Do not login protect the logout url, causes an infinite loop
1828 logout_url = getattr(settings, 'LOGOUT_URL', None)
1929 if logout_url:
0 from django.contrib.auth.decorators import user_passes_test
01 from stronghold import conf, utils
12
3 try:
4 from django.utils.deprecation import MiddlewareMixin
5 except ImportError:
6 MiddlewareMixin = object
27
3 class LoginRequiredMiddleware(object):
8
9 class LoginRequiredMiddleware(MiddlewareMixin):
410 """
5 Force all views to use the permissions defined by
6 STRONGHOLD_PERMISSIONS_DECORATOR. Default is login_required, but can use
7 staff_member_required or a user defined decorator
11 Restrict access to users that for which STRONGHOLD_USER_TEST_FUNC returns
12 True. Default is to check if the user is authenticated.
813
914 View is deemed to be public if the @public decorator is applied to the view
1015
1520 """
1621
1722 def __init__(self, *args, **kwargs):
23 if MiddlewareMixin != object:
24 super(LoginRequiredMiddleware, self).__init__(*args, **kwargs)
1825 self.public_view_urls = getattr(conf, 'STRONGHOLD_PUBLIC_URLS', ())
1926
2027 def process_view(self, request, view_func, view_args, view_kwargs):
21 if request.user.is_authenticated() or utils.is_view_func_public(view_func) \
28 if conf.STRONGHOLD_USER_TEST_FUNC(request.user) \
29 or utils.is_view_func_public(view_func) \
2230 or self.is_public_url(request.path_info):
2331 return None
2432
25 return conf.STRONGHOLD_PERMISSIONS_DECORATOR(view_func)(request, *view_args, **view_kwargs)
33 decorator = user_passes_test(conf.STRONGHOLD_USER_TEST_FUNC)
34 return decorator(view_func)(request, *view_args, **view_kwargs)
2635
2736 def is_public_url(self, url):
2837 return any(public_url.match(url) for public_url in self.public_view_urls)
00 from stronghold.tests.testdecorators import *
11 from stronghold.tests.testmiddleware import *
2 from stronghold.tests.testmixins import *
23 from stronghold.tests.testutils import *
11
22 from stronghold import decorators
33
4 from django.utils import unittest
4 import django
5 if django.VERSION[:2] < (1, 9):
6 from django.utils import unittest
7 else:
8 import unittest
59
610
711 class StrongholdDecoratorTests(unittest.TestCase):
33 from stronghold import conf
44 from stronghold.middleware import LoginRequiredMiddleware
55
6 from django.core.urlresolvers import reverse
6 try:
7 from django.urls import reverse
8 except ImportError:
9 from django.core.urlresolvers import reverse
10
711 from django.http import HttpResponse
812 from django.test import TestCase
913 from django.test.client import RequestFactory
3539 'request': self.request,
3640 }
3741
42 def set_authenticated(self, is_authenticated):
43 """Set whether user is authenticated in the request."""
44 user = self.request.user
45 user.is_authenticated.return_value = is_authenticated
46
47 # In Django >= 1.10, is_authenticated acts as property and method
48 user.is_authenticated.__bool__ = lambda self: is_authenticated
49 user.is_authenticated.__nonzero__ = lambda self: is_authenticated
50
3851 def test_redirects_to_login_when_not_authenticated(self):
39 self.request.user.is_authenticated.return_value = False
52 self.set_authenticated(False)
4053
4154 response = self.middleware.process_view(**self.kwargs)
4255
4356 self.assertEqual(response.status_code, 302)
4457
4558 def test_returns_none_when_authenticated(self):
46 self.request.user.is_authenticated.return_value = True
59 self.set_authenticated(True)
4760
4861 response = self.middleware.process_view(**self.kwargs)
4962
5063 self.assertEqual(response, None)
5164
5265 def test_returns_none_when_url_is_in_public_urls(self):
53 self.request.user.is_authenticated.return_value = False
66 self.set_authenticated(False)
5467 self.middleware.public_view_urls = [re.compile(r'/test-protected-url/')]
5568
5669 response = self.middleware.process_view(**self.kwargs)
5871 self.assertEqual(response, None)
5972
6073 def test_returns_none_when_url_is_decorated_public(self):
61 self.request.user.is_authenticated.return_value = False
74 self.set_authenticated(False)
6275
6376 self.kwargs['view_func'].STRONGHOLD_IS_PUBLIC = True
6477 response = self.middleware.process_view(**self.kwargs)
6578
6679 self.assertEqual(response, None)
80
81 def test_redirects_to_login_when_not_passing_custom_test(self):
82 with mock.patch('stronghold.conf.STRONGHOLD_USER_TEST_FUNC', lambda u: u.is_staff):
83 self.request.user.is_staff = False
84
85 response = self.middleware.process_view(**self.kwargs)
86
87 self.assertEqual(response.status_code, 302)
88
89 def test_returns_none_when_passing_custom_test(self):
90 with mock.patch('stronghold.conf.STRONGHOLD_USER_TEST_FUNC', lambda u: u.is_staff):
91 self.request.user.is_staff = True
92
93 response = self.middleware.process_view(**self.kwargs)
94
95 self.assertEqual(response, None)
0 from stronghold.views import StrongholdPublicMixin
1
2 import django
3 from django.views.generic import View
4 from django.views.generic.base import TemplateResponseMixin
5
6 if django.VERSION[:2] < (1, 9):
7 from django.utils import unittest
8 else:
9 import unittest
10
11
12 class StrongholdMixinsTests(unittest.TestCase):
13
14 def test_public_mixin_sets_attr(self):
15
16 class TestView(StrongholdPublicMixin, View):
17 pass
18
19 self.assertTrue(TestView.dispatch.STRONGHOLD_IS_PUBLIC)
20
21 def test_public_mixin_sets_attr_with_multiple_mixins(self):
22
23 class TestView(StrongholdPublicMixin, TemplateResponseMixin, View):
24 template_name = 'dummy.html'
25
26 self.assertTrue(TestView.dispatch.STRONGHOLD_IS_PUBLIC)
00 from stronghold import utils
11
2 from django.utils import unittest
2 import django
3 if django.VERSION[:2] < (1, 9):
4 from django.utils import unittest
5 else:
6 import unittest
37
48
59 class IsViewFuncPublicTests(unittest.TestCase):
0 from django.utils.decorators import method_decorator
1 from stronghold.decorators import public
2
3
4 class StrongholdPublicMixin(object):
5
6 @method_decorator(public)
7 def dispatch(self, *args, **kwargs):
8 return super(StrongholdPublicMixin, self).dispatch(*args, **kwargs)
6464 'stronghold.middleware.LoginRequiredMiddleware',
6565 )
6666
67 MIDDLEWARE = MIDDLEWARE_CLASSES
68
6769 ROOT_URLCONF = 'test_project.urls'
6870
6971 # Python dotted path to the WSGI application used by Django's runserver.
0 from django.conf.urls import patterns, url
0 from django.conf.urls import url
11 from . import views
22
33
4 urlpatterns = patterns(
5 '',
4 urlpatterns = [
65 url(r'^protected/$', views.ProtectedView.as_view(), name="protected_view"),
76 url(r'^public/$', views.PublicView.as_view(), name="public_view"),
8 )
7 ]