diff --git a/api/tests/test_views.py b/api/tests/test_views.py index d3150ef..b3868a3 100644 --- a/api/tests/test_views.py +++ b/api/tests/test_views.py @@ -11,6 +11,7 @@ from oauth2_provider.models import Application from mission_control.models import Rover, BlockDiagram +from rovercode_web.users.models import User class BaseAuthenticatedTestCase(TestCase): @@ -36,6 +37,7 @@ def test_rover_create(self): # Create the rover response = self.client.post( reverse('api:v1:rover-list'), rover_info) + print(response) id = response.data['id'] self.assertIn('client_id', response.data) self.assertIn('client_secret', response.data) @@ -56,6 +58,18 @@ def test_rover_create(self): self.assertEqual(response.status_code, 200) self.assertGreater(checkin_time, creation_time) + def test_rover_create_with_shared_user(self): + """Test the rover registration interface.""" + shared_user = User.objects.create() + self.client.login(username='administrator', password='password') + rover_info = {'name': 'Curiosity', 'local_ip': '192.168.0.10', 'shared_users': shared_user.id} + + # Create the rover + response = self.client.post( + reverse('api:v1:rover-list'), rover_info) + self.assertEqual(response.data['shared_users'], [shared_user.id]) + self.assertEqual(response.status_code, 201) + def test_rover(self): """Test the rover view displays the correct items.""" self.client.login(username='administrator', password='password') diff --git a/mission_control/filters.py b/mission_control/filters.py index 5817778..b2a7452 100644 --- a/mission_control/filters.py +++ b/mission_control/filters.py @@ -14,4 +14,4 @@ class Meta: """Meta class.""" model = Rover - fields = ['name', 'client_id'] + fields = ['name', 'client_id', 'shared_users'] diff --git a/mission_control/migrations/0012_rover_shared_users.py b/mission_control/migrations/0012_rover_shared_users.py new file mode 100644 index 0000000..0f899da --- /dev/null +++ b/mission_control/migrations/0012_rover_shared_users.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.16 on 2019-01-31 20:51 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('mission_control', '0011_auto_20190129_0521'), + ] + + operations = [ + migrations.AddField( + model_name='rover', + name='shared_users', + field=models.ManyToManyField(related_name='_rover_shared_users_+', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/mission_control/migrations/0013_auto_20190131_2056.py b/mission_control/migrations/0013_auto_20190131_2056.py new file mode 100644 index 0000000..b1aa314 --- /dev/null +++ b/mission_control/migrations/0013_auto_20190131_2056.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.16 on 2019-01-31 20:56 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mission_control', '0012_rover_shared_users'), + ] + + operations = [ + migrations.AlterField( + model_name='rover', + name='shared_users', + field=models.ManyToManyField(null=True, related_name='_rover_shared_users_+', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/mission_control/migrations/0014_auto_20190131_2153.py b/mission_control/migrations/0014_auto_20190131_2153.py new file mode 100644 index 0000000..0be6f40 --- /dev/null +++ b/mission_control/migrations/0014_auto_20190131_2153.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.16 on 2019-01-31 21:53 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mission_control', '0013_auto_20190131_2056'), + ] + + operations = [ + migrations.AlterField( + model_name='rover', + name='shared_users', + field=models.ManyToManyField(related_name='shared_rover', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/mission_control/migrations/0015_auto_20190131_2234.py b/mission_control/migrations/0015_auto_20190131_2234.py new file mode 100644 index 0000000..4ced835 --- /dev/null +++ b/mission_control/migrations/0015_auto_20190131_2234.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.16 on 2019-01-31 22:34 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mission_control', '0014_auto_20190131_2153'), + ] + + operations = [ + migrations.AlterField( + model_name='rover', + name='shared_users', + field=models.ManyToManyField(blank=True, related_name='shared_rover', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/mission_control/models.py b/mission_control/models.py index 0df22fe..c7ed17c 100644 --- a/mission_control/models.py +++ b/mission_control/models.py @@ -10,6 +10,7 @@ class Rover(models.Model): name = models.CharField(null=False, max_length=25) owner = models.ForeignKey(User) oauth_application = models.ForeignKey(Application, blank=True, null=True) + shared_users = models.ManyToManyField(User, blank=True, related_name='shared_rover') local_ip = models.CharField(max_length=15, null=True) last_checkin = models.DateTimeField(auto_now=True) left_forward_pin = models.CharField( diff --git a/mission_control/serializers.py b/mission_control/serializers.py index 9056865..6a11397 100644 --- a/mission_control/serializers.py +++ b/mission_control/serializers.py @@ -24,7 +24,7 @@ class Meta: 'left_forward_pin', 'left_backward_pin', 'right_forward_pin', 'right_backward_pin', 'left_eye_pin', 'right_eye_pin', 'left_eye_i2c_port', 'left_eye_i2c_addr', 'right_eye_i2c_port', - 'right_eye_i2c_addr', 'client_id', 'client_secret' + 'right_eye_i2c_addr', 'client_id', 'client_secret', 'shared_users' ) read_only_fields = ('owner',) @@ -38,8 +38,14 @@ def create(self, validated_data): client_type=Application.CLIENT_CONFIDENTIAL, name=name ) - return Rover.objects.create(oauth_application=oauth_application, + shared_users = validated_data.pop('shared_users', None) + rover = Rover.objects.create(oauth_application=oauth_application, **validated_data) + if shared_users: + rover.shared_users.set(shared_users) + rover.save() + + return rover class BlockDiagramSerializer(serializers.ModelSerializer): diff --git a/realtime/consumers.py b/realtime/consumers.py index 06f2356..6146ff1 100644 --- a/realtime/consumers.py +++ b/realtime/consumers.py @@ -29,7 +29,11 @@ def connect(self): self.close(code=404) return - if rover.owner.id != user.id: + print("\n\n\n\n\nShared users:") + print(rover.shared_users.all()) + print(user.id) + print("\n\n\n\n") + if user != rover.owner and user not in rover.shared_users.all(): self.close(code=403) return diff --git a/realtime/tests/test_rover_consumer.py b/realtime/tests/test_rover_consumer.py index c79a78c..78e0e2f 100644 --- a/realtime/tests/test_rover_consumer.py +++ b/realtime/tests/test_rover_consumer.py @@ -90,6 +90,35 @@ async def test_rover_consumer_nonexistent_client_id(): assert code == 404 assert not connected +@pytest.mark.django_db(transaction=True) +@pytest.mark.asyncio +async def test_rover_consumer_shared_user(): + """Test trying to connect to a rover the user doesn't own.""" + requesting_user = User.objects.create(username='req_user', id=MOCK_USER_ID) + owner = User.objects.create(username='owner', id=MOCK_USER_ID + 1) + oauth_app = Application.objects.create( + user=owner, + authorization_grant_type=Application.GRANT_CLIENT_CREDENTIALS, + client_type=Application.CLIENT_CONFIDENTIAL, + name='rover' + ) + rover = Rover.objects.create( + name='rover', + owner=owner, + local_ip='8.8.8.8', + oauth_application=oauth_app, + ) + rover.shared_users.set([requesting_user]) + application = MockAuthMiddleware(URLRouter([ + url(r'^ws/realtime/(?P[^/]+)/$', RoverConsumer), + ])) + communicator = WebsocketCommunicator( + application, + "/ws/realtime/{}/".format(oauth_app.client_id) + ) + connected, _ = await communicator.connect() + assert connected + @pytest.mark.django_db(transaction=True) @pytest.mark.asyncio