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