diff --git a/alliance_auth/tests/auth_utils.py b/alliance_auth/tests/auth_utils.py index fec0b4be..f4ddfdbf 100644 --- a/alliance_auth/tests/auth_utils.py +++ b/alliance_auth/tests/auth_utils.py @@ -4,8 +4,7 @@ from django.db.models.signals import m2m_changed, pre_save from django.contrib.auth.models import User, Group from services.signals import m2m_changed_user_groups, pre_save_user from services.signals import m2m_changed_group_permissions, m2m_changed_user_permissions, m2m_changed_state_permissions -from authentication.models import UserProfile, State - +from authentication.models import UserProfile, State, CharacterOwnership from eveonline.models import EveCharacter @@ -26,14 +25,6 @@ class AuthUtils: cls.connect_signals() return user - @classmethod - def create_member(cls, username): - return cls.create_user(username, disconnect_signals=True) - - @classmethod - def create_blue(cls, username): - return cls.create_user(username, disconnect_signals=True) - @classmethod def disconnect_signals(cls): m2m_changed.disconnect(m2m_changed_user_groups, sender=User.groups.through) @@ -61,8 +52,6 @@ class AuthUtils: corporation_ticker=corp_ticker, alliance_id=alliance_id, alliance_name=alliance_name, - api_id='1234', - user=user ) UserProfile.objects.update_or_create(user=user, defaults={'main_character': char}) @@ -77,3 +66,7 @@ class AuthUtils: if disconnect_signals: cls.connect_signals() + + @classmethod + def add_permissions_to_state(cls, perms, states, disconnect_signals=True): + return cls.add_permissions_to_groups(perms, states, disconnect_signals=disconnect_signals) \ No newline at end of file diff --git a/authentication/models.py b/authentication/models.py index 26d2a933..4c88f547 100755 --- a/authentication/models.py +++ b/authentication/models.py @@ -72,6 +72,8 @@ class UserProfile(models.Model): notify(self.user, _('State Changed'), _('Your user state has been changed to %(state)s') % ({'state': state}), 'info') + from authentication.signals import state_changed + state_changed.send(sender=self.__class__, user=self.user, state=self.state) def __str__(self): return str(self.user) diff --git a/authentication/signals.py b/authentication/signals.py index b20b74f6..52987a46 100644 --- a/authentication/signals.py +++ b/authentication/signals.py @@ -1,10 +1,9 @@ from __future__ import unicode_literals from django.db.models.signals import post_save, pre_delete, m2m_changed from django.db.models import Q -from django.dispatch import receiver +from django.dispatch import receiver, Signal from django.contrib.auth.models import User from authentication.models import CharacterOwnership, UserProfile, get_guest_state, State -from services.tasks import validate_services from esi.models import Token from eveonline.managers import EveManager from eveonline.models import EveCharacter @@ -13,6 +12,9 @@ import logging logger = logging.getLogger(__name__) +state_changed = Signal(providing_args=['user', 'state']) + + def trigger_state_check(state): # evaluate all current members to ensure they still have access for profile in state.userprofile_set.all(): @@ -57,8 +59,6 @@ def reassess_on_profile_save(sender, instance, created, *args, **kwargs): update_fields = kwargs.pop('update_fields', []) or [] if 'state' not in update_fields: instance.assign_state() - # TODO: how do we prevent running this twice on profile state change? - validate_services(instance.user) @receiver(post_save, sender=User) diff --git a/authentication/templates/registered/base.html b/authentication/templates/registered/base.html index b67e9f17..d5563b9b 100644 --- a/authentication/templates/registered/base.html +++ b/authentication/templates/registered/base.html @@ -113,7 +113,7 @@ {% if perms.corputils.view_corp_corpstats or perms.corputils.view_alliance_corpstats or perms.corputils.view_blue_corpstats %}
  • - + {% trans " Corporation Stats" %}
  • diff --git a/authentication/tests/__init__.py b/authentication/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/authentication/tests/test_ownership.py b/authentication/tests/test_ownership.py new file mode 100644 index 00000000..15bc4290 --- /dev/null +++ b/authentication/tests/test_ownership.py @@ -0,0 +1,51 @@ +from __future__ import unicode_literals + +try: + # Py3 + from unittest import mock +except ImportError: + # Py2 + import mock + +from django.test import TestCase +from alliance_auth.tests.auth_utils import AuthUtils +from authentication.models import State, get_guest_state +from eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo + + +class StateTestCase(TestCase): + def setUp(self): + self.user = AuthUtils.create_user('test_user', disconnect_signals=True) + AuthUtils.add_main_character(self.user, 'Test Character', '1', corp_id='1', alliance_id='1', + corp_name='Test Corp', alliance_name='Test Alliance') + self.guest_state = get_guest_state() + self.test_character = EveCharacter.objects.get(character_id='1') + self.test_corporation = EveCorporationInfo.objects.create(corporation_id='1', corporation_name='Test Corp', + corporation_ticker='TEST', member_count=1) + self.test_alliance = EveAllianceInfo.objects.create(alliance_id='1', alliance_name='Test Alliance', + alliance_ticker='TEST', executor_corp_id='1') + self.member_state = State.objects.create( + name='Test Member', + priority=150, + ) + + def test_state_assignment_on_character_change(self): + self.member_state.member_characters.add(self.test_character) + self.assertEquals(self.user.profile.state, self.member_state) + + self.member_state.member_characters.remove(self.test_character) + self.assertEquals(self.user.profile.state, self.guest_state) + + def test_state_assignment_on_corporation_change(self): + self.member_state.member_corporations.add(self.test_corporation) + self.assertEquals(self.user.profile.state, self.member_state) + + self.member_state.member_corporations.remove(self.test_corporation) + self.assertEquals(self.user.profile.state, self.guest_state) + + def test_state_assignment_on_alliance_addition(self): + self.member_state.member_alliances.add(self.test_alliance) + self.assertEquals(self.user.profile.state, self.member_state) + + self.member_state.member_alliances.remove(self.test_alliance) + self.assertEquals(self.user.profile.state, self.guest_state) diff --git a/requirements.txt b/requirements.txt index 4395133d..8c2d749d 100755 --- a/requirements.txt +++ b/requirements.txt @@ -13,11 +13,11 @@ redis # Django Stuff # django>=1.10,<2.0 django-bootstrap-form -django-navhelper django-bootstrap-pagination django-redis>=4.4 django-registration django-sortedm2m +git+https://github.com/adarnof/django-navhelper # awating release for fix to celery/django-celery#447 # django-celery diff --git a/services/signals.py b/services/signals.py index 7612a5d6..937e72aa 100644 --- a/services/signals.py +++ b/services/signals.py @@ -11,7 +11,8 @@ from django.dispatch import receiver from services.hooks import ServicesHook from services.tasks import disable_user -from authentication.models import State +from authentication.models import State, UserProfile +from authentication.signals import state_changed logger = logging.getLogger(__name__) @@ -122,6 +123,19 @@ def m2m_changed_state_permissions(sender, instance, action, pk_set, *args, **kwa logger.debug("Permission change for state {} was not service permission, ignoring".format(instance)) +@receiver(state_changed, sender=UserProfile) +def check_service_accounts_state_changed(sender, user, state, **kwargs): + logger.debug("Received state_changed from %s to state %s" % (user, state)) + service_perms = [svc.access_perm for svc in ServicesHook.get_services()] + state_perms = ["{}.{}".format(perm.natural_key()[1], perm.natural_key()[0]) for perm in state.permissions.all()] + for perm in service_perms: + if perm not in state_perms: + for svc in ServicesHook.get_services(): + if svc.access_perm == perm: + logger.debug("User %s new state %s does not have service %s permission. Checking account." % (user, state, svc)) + svc.validate_user(user) + + @receiver(pre_delete, sender=User) def pre_delete_user(sender, instance, *args, **kwargs): logger.debug("Received pre_delete from %s" % instance) diff --git a/services/tests/test_signals.py b/services/tests/test_signals.py index 4099ce88..8b11efce 100644 --- a/services/tests/test_signals.py +++ b/services/tests/test_signals.py @@ -9,13 +9,13 @@ except ImportError: from django.test import TestCase from django.contrib.auth.models import Group, Permission - from alliance_auth.tests.auth_utils import AuthUtils +from authentication.models import State class ServicesSignalsTestCase(TestCase): def setUp(self): - self.member = AuthUtils.create_member('auth_member') + self.member = AuthUtils.create_user('auth_member', disconnect_signals=True) self.none_user = AuthUtils.create_user('none_user', disconnect_signals=True) @mock.patch('services.signals.transaction') @@ -50,7 +50,6 @@ class ServicesSignalsTestCase(TestCase): args, kwargs = svc.validate_user.call_args self.assertEqual(self.member, args[0]) - @mock.patch('services.signals.disable_user') def test_pre_delete_user(self, disable_user): @@ -75,26 +74,6 @@ class ServicesSignalsTestCase(TestCase): args, kwargs = disable_user.call_args self.assertEqual(self.member, args[0]) - @mock.patch('services.signals.set_state') - def test_pre_save_user_activation(self, set_state): - """ - Test a user set inactive has disable_member called - """ - # Arrange, set user inactive first - self.member.is_active = False - self.member.save() # Signal Trigger (but not the one we want) - - set_state.reset_mock() - - # Act - self.member.is_active = True - self.member.save() # Signal Trigger - - # Assert - self.assertTrue(set_state.called) - args, kwargs = set_state.call_args - self.assertEqual(self.member, args[0]) - @mock.patch('services.signals.transaction') @mock.patch('services.signals.ServicesHook') def test_m2m_changed_group_permissions(self, services_hook, transaction): @@ -153,3 +132,58 @@ class ServicesSignalsTestCase(TestCase): self.assertTrue(svc.validate_user.called) args, kwargs = svc.validate_user.call_args self.assertEqual(self.member, args[0]) + + @mock.patch('services.signals.transaction') + @mock.patch('services.signals.ServicesHook') + def test_m2m_changed_user_state_permissions(self, services_hook, transaction): + from django.contrib.contenttypes.models import ContentType + svc = mock.Mock() + svc.validate_user.return_value = None + svc.access_perm = 'auth.access_testsvc' + + services_hook.get_services.return_value = [svc] + + # Overload transaction.on_commit so everything happens synchronously + transaction.on_commit = lambda fn: fn() + + AuthUtils.disconnect_signals() + test_state = State.objects.create(name="Test state", priority=150) + self.member.profile.state = test_state + self.member.profile.save() + AuthUtils.connect_signals() + + ct = ContentType.objects.get(app_label='auth', model='permission') + perm = Permission.objects.create(name="Test perm", codename="access_testsvc", content_type=ct) + test_state.permissions.add(perm) + + # Act, should trigger m2m change + test_state.permissions.remove(perm) + + # Assert + self.assertTrue(services_hook.get_services.called) + + self.assertTrue(svc.validate_user.called) + args, kwargs = svc.validate_user.call_args + self.assertEqual(self.member, args[0]) + + @mock.patch('services.signals.ServicesHook') + def test_state_changed_services_valudation(self, services_hook): + """ + Test a user changing state has service accounts validated + """ + svc = mock.Mock() + svc.validate_user.return_value = None + svc.access_perm = 'auth.access_testsvc' + + services_hook.get_services.return_value = [svc] + + test_state = State.objects.create(name="Test state", priority=150, public=True) + self.member.profile.state = test_state + self.member.profile.save() + + # Assert + self.assertTrue(services_hook.get_services.called) + + self.assertTrue(svc.validate_user.called) + args, kwargs = svc.validate_user.call_args + self.assertEqual(self.member, args[0]) \ No newline at end of file