Hook URLs require logged in user with a main character.

Should prevent anything else like #983

Heavily inspired by https://gist.github.com/garrypolley/3762045#gistcomment-2089316
This commit is contained in:
Adarnof 2018-02-26 22:35:47 -05:00
parent 552c795041
commit cc8a7a18d2
7 changed files with 100 additions and 5 deletions

View File

@ -0,0 +1,37 @@
from django.conf.urls import include
from functools import wraps
from django.shortcuts import redirect
from django.contrib import messages
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.decorators import login_required
def user_has_main_character(user):
return bool(user.profile.main_character)
def decorate_url_patterns(urls, decorator):
url_list, app_name, namespace = include(urls)
def process_patterns(url_patterns):
for pattern in url_patterns:
if hasattr(pattern, 'url_patterns'):
# this is an include - apply to all nested patterns
process_patterns(pattern.url_patterns)
else:
# this is a pattern
pattern.callback = decorator(pattern.callback)
process_patterns(url_list)
return url_list, app_name, namespace
def main_character_required(view_func):
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
if user_has_main_character(request.user):
return view_func(request, *args, **kwargs)
messages.error(request, _('A main character is required to perform that action. Add one below.'))
return redirect('authentication:dashboard')
return login_required(_wrapped_view)

View File

@ -57,7 +57,7 @@
</div>
{% endwith %}
{% else %}
<div class="alert alert-danger" role="alert">{% trans "Missing main character model." %}</div>
<div class="alert alert-danger" role="alert">{% trans "No main character set." %}</div>
{% endif %}
<div class="clearfix"></div>
<div class="col-xs-6">

View File

@ -8,6 +8,61 @@ from .backends import StateBackend
from .tasks import check_character_ownership
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo
from esi.models import Token
from allianceauth.authentication.decorators import main_character_required
from django.test.client import RequestFactory
from django.http.response import HttpResponse
from django.contrib.auth.models import AnonymousUser
from django.conf import settings
from django.shortcuts import reverse
from urllib import parse
MODULE_PATH = 'allianceauth.authentication'
class DecoratorTestCase(TestCase):
@staticmethod
@main_character_required
def dummy_view(*args, **kwargs):
return HttpResponse(status=200)
@classmethod
def setUpTestData(cls):
cls.main_user = AuthUtils.create_user('main_user', disconnect_signals=True)
cls.no_main_user = AuthUtils.create_user('no_main_user', disconnect_signals=True)
main_character = EveCharacter.objects.create(
character_id=1,
character_name='Main Character',
corporation_id=1,
corporation_name='Corp',
corporation_ticker='CORP',
)
CharacterOwnership.objects.create(user=cls.main_user, character=main_character, owner_hash='1')
cls.main_user.profile.main_character = main_character
def setUp(self):
self.request = RequestFactory().get('/test/')
@mock.patch(MODULE_PATH + '.decorators.messages')
def test_login_redirect(self, m):
setattr(self.request, 'user', AnonymousUser())
response = self.dummy_view(self.request)
self.assertEqual(response.status_code, 302)
url = getattr(response, 'url', None)
self.assertEqual(parse.urlparse(url).path, reverse(settings.LOGIN_URL))
@mock.patch(MODULE_PATH + '.decorators.messages')
def test_main_character_redirect(self, m):
setattr(self.request, 'user', self.no_main_user)
response = self.dummy_view(self.request)
self.assertEqual(response.status_code, 302)
url = getattr(response, 'url', None)
self.assertEqual(url, reverse('authentication:dashboard'))
@mock.patch(MODULE_PATH + '.decorators.messages')
def test_successful_request(self, m):
setattr(self.request, 'user', self.main_user)
response = self.dummy_view(self.request)
self.assertEqual(response.status_code, 200)
class BackendTestCase(TestCase):

View File

@ -8,6 +8,7 @@ from allianceauth.tests.auth_utils import AuthUtils
class PermissionsToolViewsTestCase(WebTest):
def setUp(self):
self.member = AuthUtils.create_member('auth_member')
AuthUtils.add_main_character(self.member, 'test character', '1234', '2345', 'test corp', 'testc')
self.member.email = 'auth_member@example.com'
self.member.save()
self.none_user = AuthUtils.create_user('none_user', disconnect_signals=True)

View File

@ -145,6 +145,7 @@ class DiscordHooksTestCase(TestCase):
class DiscordViewsTestCase(WebTest):
def setUp(self):
self.member = AuthUtils.create_member('auth_member')
AuthUtils.add_main_character(self.member, 'test character', '1234', '2345', 'test corp', 'testc')
add_permissions()
def login(self):

View File

@ -9,9 +9,9 @@ import allianceauth.authentication.urls
import allianceauth.notifications.urls
import allianceauth.groupmanagement.urls
import allianceauth.services.urls
from allianceauth.authentication.decorators import main_character_required, decorate_url_patterns
from allianceauth import NAME
from allianceauth import views
from allianceauth.authentication import hmac_urls
from allianceauth.hooks import get_hooks
@ -42,13 +42,14 @@ urlpatterns = [
url(r'', include(allianceauth.groupmanagement.urls)),
# Services
url(r'', include(allianceauth.services.urls)),
url(r'', decorate_url_patterns(allianceauth.services.urls.urlpatterns, main_character_required)),
# Night mode
url(r'^night/', views.NightModeRedirectView.as_view(), name='nightmode')
]
# Append app urls
app_urls = get_hooks('url_hook')
for app in app_urls:
urlpatterns += [app().include_pattern]
urlpatterns += [url(r'', decorate_url_patterns([app().include_pattern], main_character_required))]

View File

@ -2,7 +2,7 @@
```eval_rst
.. note::
Currently most URL patterns are statically defined in the project's core urls.py file. Ideally this behaviour will change over time with each module of Alliance Auth providing all of its menu items via the hook. New modules should aim to use the hook over statically adding URL patterns to the project's patterns.
URLs added through URL Hooks are protected by a decorator which ensures the requesting user is logged in and has a main character set.
```
The URL hooks allow you to dynamically specify URL patterns from your plugin app or service. To achieve this you should subclass or instantiate the `services.hooks.UrlHook` class and then register the URL patterns with the hook.