From 27cab39c7c8efbac76c142b3962f3bfd039a4f68 Mon Sep 17 00:00:00 2001 From: Alex Velez Date: Mon, 24 Feb 2025 11:29:56 -0500 Subject: [PATCH] Fix failing tests and remove old device provisioning endpoint --- kolibri/core/device/api.py | 32 --- kolibri/core/device/api_urls.py | 6 - kolibri/core/device/serializers.py | 216 --------------- kolibri/core/device/tasks.py | 9 +- kolibri/core/device/test/test_api.py | 246 ------------------ kolibri/core/device/test/test_tasks.py | 202 ++++++++++++++ kolibri/core/tasks/api.py | 1 - .../onboarding-forms/SettingUpKolibri.vue | 2 +- 8 files changed, 208 insertions(+), 506 deletions(-) create mode 100644 kolibri/core/device/test/test_tasks.py diff --git a/kolibri/core/device/api.py b/kolibri/core/device/api.py index 06dc4bbe0a8..fb0c3b7bad2 100644 --- a/kolibri/core/device/api.py +++ b/kolibri/core/device/api.py @@ -3,7 +3,6 @@ from sys import version_info from django.conf import settings -from django.contrib.auth import login from django.db.models import Exists from django.db.models import F from django.db.models import Max @@ -13,9 +12,7 @@ from django.http import Http404 from django.http.response import HttpResponseBadRequest from django.utils import timezone -from django.utils.decorators import method_decorator from django.utils.translation import get_language -from django.views.decorators.csrf import csrf_protect from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import FilterSet from django_filters.rest_framework import ModelChoiceFilter @@ -23,7 +20,6 @@ from morango.models import InstanceIDModel from morango.models import TransferSession from rest_framework import mixins -from rest_framework import status from rest_framework import views from rest_framework import viewsets from rest_framework.permissions import IsAuthenticated @@ -37,12 +33,9 @@ from .models import LearnerDeviceStatus from .models import StatusSentiment from .models import UserSyncStatus -from .permissions import NotProvisionedCanPost from .permissions import UserHasAnyDevicePermissions from .serializers import DevicePermissionsSerializer -from .serializers import DeviceProvisionSerializer from .serializers import DeviceSettingsSerializer -from kolibri.core.analytics.tasks import schedule_ping from kolibri.core.api import ReadOnlyValuesViewset from kolibri.core.auth.api import KolibriAuthPermissions from kolibri.core.auth.api import KolibriAuthPermissionsFilter @@ -96,31 +89,6 @@ class DevicePermissionsViewSet(viewsets.ModelViewSet): filter_backends = (KolibriAuthPermissionsFilter,) -@method_decorator(csrf_protect, name="dispatch") -class DeviceProvisionView(viewsets.GenericViewSet): - permission_classes = (NotProvisionedCanPost,) - serializer_class = DeviceProvisionSerializer - - def create(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - data = serializer.save() - if data["superuser"]: - login(request, data["superuser"]) - output_serializer = self.get_serializer(data) - response_data = output_serializer.data - - # Restart zeroconf before moving along when we're a SoUD - if response_data["is_soud"]: - logger.info("Updating our Kolibri instance on the Zeroconf network now") - from kolibri.utils.server import update_zeroconf_broadcast - - update_zeroconf_broadcast() - - schedule_ping() # Trigger telemetry pingback after we've provisioned - return Response(response_data, status=status.HTTP_201_CREATED) - - class FreeSpaceView(mixins.ListModelMixin, viewsets.GenericViewSet): permission_classes = (IsAuthenticated,) diff --git a/kolibri/core/device/api_urls.py b/kolibri/core/device/api_urls.py index 29a4b4ac689..cb4505d5c06 100644 --- a/kolibri/core/device/api_urls.py +++ b/kolibri/core/device/api_urls.py @@ -5,7 +5,6 @@ from .api import DeviceInfoView from .api import DeviceNameView from .api import DevicePermissionsViewSet -from .api import DeviceProvisionView from .api import DeviceRestartView from .api import DeviceSettingsView from .api import DriveInfoViewSet @@ -23,11 +22,6 @@ urlpatterns = [ re_path(r"^", include(router.urls)), - re_path( - r"^deviceprovision/", - DeviceProvisionView.as_view({"post": "create"}), - name="deviceprovision", - ), re_path(r"^freespace/", FreeSpaceView.as_view({"get": "list"}), name="freespace"), re_path(r"^deviceinfo/", DeviceInfoView.as_view(), name="deviceinfo"), re_path(r"^devicesettings/", DeviceSettingsView.as_view(), name="devicesettings"), diff --git a/kolibri/core/device/serializers.py b/kolibri/core/device/serializers.py index eebe98feb86..af30fad0831 100644 --- a/kolibri/core/device/serializers.py +++ b/kolibri/core/device/serializers.py @@ -1,25 +1,12 @@ -from django.db import transaction from django.utils.translation import check_for_language from django.utils.translation import gettext_lazy as _ from rest_framework import serializers -from rest_framework.exceptions import ParseError -from kolibri.core.auth.constants import user_kinds -from kolibri.core.auth.constants.facility_presets import choices -from kolibri.core.auth.models import Facility from kolibri.core.auth.models import FacilityUser -from kolibri.core.auth.serializers import FacilitySerializer from kolibri.core.content.tasks import automatic_resource_import from kolibri.core.content.tasks import automatic_synchronize_content_requests_and_import from kolibri.core.device.models import DevicePermissions from kolibri.core.device.models import DeviceSettings -from kolibri.core.device.models import OSUser -from kolibri.core.device.utils import APP_AUTH_TOKEN_COOKIE_NAME -from kolibri.core.device.utils import provision_device -from kolibri.core.device.utils import provision_single_user_device -from kolibri.core.device.utils import valid_app_key_on_request -from kolibri.plugins.app.utils import GET_OS_USER -from kolibri.plugins.app.utils import interface from kolibri.utils.filesystem import check_is_directory from kolibri.utils.filesystem import get_path_permission @@ -49,209 +36,6 @@ def validate_language_id(self, language_id): return language_id -class DeviceProvisionSerializer(DeviceSerializerMixin, serializers.Serializer): - facility = FacilitySerializer(required=False, allow_null=True) - facility_id = serializers.CharField(max_length=50, required=False, allow_null=True) - preset = serializers.ChoiceField(choices=choices, required=False, allow_null=True) - superuser = NoFacilityFacilityUserSerializer(required=False) - language_id = serializers.CharField(max_length=15) - device_name = serializers.CharField(max_length=50, allow_null=True) - settings = serializers.JSONField() - allow_guest_access = serializers.BooleanField(allow_null=True) - is_provisioned = serializers.BooleanField(default=True) - is_soud = serializers.BooleanField(default=True) - - class Meta: - fields = ( - "facility", - "facility_id", - "preset", - "superuser", - "language_id", - "device_name", - "settings", - "allow_guest_access", - "is_provisioned", - "is_soud", - ) - - def validate(self, data): - if ( - GET_OS_USER in interface - and "request" in self.context - and valid_app_key_on_request(self.context["request"]) - ): - data["auth_token"] = self.context["request"].COOKIES.get( - APP_AUTH_TOKEN_COOKIE_NAME - ) - elif "superuser" not in data: - raise serializers.ValidationError("Superuser is required for provisioning") - - has_facility = "facility" in data - has_facility_id = "facility_id" in data - - if (has_facility and has_facility_id) or ( - not has_facility and not has_facility_id - ): - raise serializers.ValidationError( - "Please provide one of `facility` or `facility_id`; but not both." - ) - - if has_facility and "preset" not in data: - raise serializers.ValidationError( - "Please provide `preset` if `facility` is specified" - ) - - return data - - def create(self, validated_data): # noqa C901 - """ - Endpoint for initial setup of a device. - Expects a value for: - default language - the default language of this Kolibri device - facility - the required fields for setting up a facility - facilitydataset - facility configuration options - superuser - the required fields for a facilityuser who will be set as the super user for this device - """ - with transaction.atomic(): - if validated_data.get("facility"): - facility_data = validated_data.pop("facility") - facility_id = None - else: - facility_id = validated_data.pop("facility_id") - facility_data = None - - if facility_id: - try: - # We've already imported the facility to the device before provisioning - facility = Facility.objects.get(pk=facility_id) - preset = facility.dataset.preset - facility_created = False - except Facility.DoesNotExist: - raise ParseError( - "Facility with id={0} does not exist".format(facility_id) - ) - else: - try: - facility = Facility.objects.create(**facility_data) - preset = validated_data.pop("preset") - facility.dataset.preset = preset - facility.dataset.reset_to_default_settings(preset) - facility_created = True - except Exception: - raise ParseError("Please check `facility` or `preset` fields.") - - custom_settings = validated_data.pop("settings") - - allow_learner_download_resources = False - - if facility_created: - # We only want to update things about the facility or the facility dataset in the case - # that we are creating the facility during this provisioning process. - # If it has been imported as part of a whole facility import, then we should not be - # making edits just now. - # If it has been imported as part of a learner only device import, then editing - # these things now will a) not be synced back, and b) will actively block future - # syncing of updates to the facility or facility dataset from our 'upstream'. - - if "on_my_own_setup" in custom_settings: - facility.on_my_own_setup = custom_settings.pop("on_my_own_setup") - # If we are in on my own setup, then we want to allow learners to download resources - # to give them a seamless onboarding experience, without the need to use the device - # plugin to download resources en masse. - allow_learner_download_resources = True - - # overwrite the settings in dataset_data with validated_data.settings - for key, value in custom_settings.items(): - if value is not None: - setattr(facility.dataset, key, value) - facility.dataset.save() - - auth_token = validated_data.pop("auth_token", None) - - if "superuser" in validated_data: - superuser_data = validated_data["superuser"] - # We've imported a facility if the username exists - try: - superuser = FacilityUser.objects.get( - username=superuser_data["username"] - ) - except FacilityUser.DoesNotExist: - try: - # Otherwise we make the superuser - superuser = FacilityUser.objects.create_superuser( - superuser_data["username"], - superuser_data["password"], - facility=facility, - full_name=superuser_data.get("full_name"), - ) - except Exception: - raise ParseError( - "`username`, `password`, or `full_name` are missing in `superuser`" - ) - if auth_token: - # If we have an auth token, we need to create an OSUser for the superuser - # so that we can associate the user with the OSUser - os_username, _ = interface.get_os_user(auth_token) - OSUser.objects.update_or_create( - os_username=os_username, defaults={"user": superuser} - ) - - elif auth_token: - superuser = FacilityUser.objects.get_or_create_os_user( - auth_token, facility=facility - ) - else: - raise ParseError( - "Either `superuser` or `auth_token` must be provided for provisioning" - ) - - is_soud = validated_data.pop("is_soud") - - if superuser: - if facility_created: - # Only do this if this is a created, not imported facility. - facility.add_role(superuser, user_kinds.ADMIN) - - if DevicePermissions.objects.count() == 0: - DevicePermissions.objects.create( - user=superuser, - is_superuser=True, - can_manage_content=True, - ) - - # Create device settings - language_id = validated_data.pop("language_id") - allow_guest_access = validated_data.pop("allow_guest_access") - - if allow_guest_access is None: - allow_guest_access = preset != "formal" - - provisioning_data = { - "device_name": validated_data["device_name"], - "is_provisioned": validated_data["is_provisioned"], - "language_id": language_id, - "default_facility": facility, - "allow_guest_access": allow_guest_access, - "allow_learner_download_resources": allow_learner_download_resources, - } - - if is_soud: - provision_single_user_device(superuser, **provisioning_data) - else: - provision_device(**provisioning_data) - - # The API View expects these fields to be in the returned serialized data as well - provisioning_data.update( - { - "superuser": superuser, - "preset": preset, - "settings": custom_settings, - } - ) - return provisioning_data - - class PathListField(serializers.ListField): def to_representation(self, data): return [ diff --git a/kolibri/core/device/tasks.py b/kolibri/core/device/tasks.py index e8304116d68..71bd507df7d 100644 --- a/kolibri/core/device/tasks.py +++ b/kolibri/core/device/tasks.py @@ -20,6 +20,7 @@ from kolibri.core.device.utils import valid_app_key_on_request from kolibri.core.tasks.decorators import register_task from kolibri.core.tasks.permissions import FirstProvisioning +from kolibri.core.tasks.utils import get_current_job from kolibri.core.tasks.validation import JobValidator from kolibri.plugins.app.utils import GET_OS_USER from kolibri.plugins.app.utils import interface @@ -179,7 +180,7 @@ def provisiondevice(**data): # noqa C901 "Either `superuser` or `auth_token` must be provided for provisioning" ) - is_soud = data.pop("is_soud") + is_soud = data.pop("is_soud", True) if superuser: if facility_created: @@ -220,6 +221,6 @@ def provisiondevice(**data): # noqa C901 schedule_ping() # Trigger telemetry pingback after we've provisioned - return { - "facility_id": facility.id, - } + job = get_current_job() + if job: + job.update_metadata(facility_id=facility.id) diff --git a/kolibri/core/device/test/test_api.py b/kolibri/core/device/test/test_api.py index 17e7f31e03f..b89360df5f9 100644 --- a/kolibri/core/device/test/test_api.py +++ b/kolibri/core/device/test/test_api.py @@ -15,15 +15,9 @@ from morango.models import SyncSession from morango.models import TransferSession from rest_framework import status -from rest_framework.test import APIClient from rest_framework.test import APITestCase import kolibri -from kolibri.core.auth.constants.role_kinds import ADMIN -from kolibri.core.auth.models import Facility -from kolibri.core.auth.models import FacilityDataset -from kolibri.core.auth.models import FacilityUser -from kolibri.core.auth.models import Role from kolibri.core.auth.test.helpers import clear_process_cache from kolibri.core.auth.test.helpers import create_superuser from kolibri.core.auth.test.helpers import provision_device @@ -42,11 +36,6 @@ from kolibri.core.device.models import UserSyncStatus from kolibri.core.public.constants import user_sync_statuses from kolibri.core.public.constants.user_sync_options import DELAYED_SYNC -from kolibri.plugins.app.test.helpers import register_capabilities -from kolibri.plugins.app.utils import GET_OS_USER -from kolibri.plugins.app.utils import interface -from kolibri.plugins.utils.test.helpers import plugin_disabled -from kolibri.plugins.utils.test.helpers import plugin_enabled from kolibri.utils.conf import OPTIONS from kolibri.utils.tests.helpers import override_option @@ -54,213 +43,6 @@ DUMMY_PASSWORD = "password" -class DeviceProvisionTestCase(APITestCase): - def setUp(self): - clear_process_cache() - - superuser_data = {"username": "superuser", "password": "password"} - facility_data = {"name": "Wilson Elementary"} - preset_data = "nonformal" - dataset_data = { - "learner_can_edit_username": True, - "learner_can_edit_name": True, - "learner_can_edit_password": True, - "learner_can_sign_up": True, - "learner_can_delete_account": True, - "learner_can_login_with_no_password": False, - } - settings = {} - allow_guest_access = True - - language_id = "en" - - def _default_provision_data(self): - return { - "device_name": None, - "superuser": self.superuser_data, - "facility": self.facility_data, - "preset": self.preset_data, - "settings": self.settings, - "language_id": self.language_id, - "allow_guest_access": self.allow_guest_access, - } - - def _post_deviceprovision(self, data): - return self.client.post( - reverse("kolibri:core:deviceprovision"), data, format="json" - ) - - def test_personal_setup_defaults(self): - data = self._default_provision_data() - data["preset"] = "informal" - # Client should pass an empty Dict for settings - data["settings"] = {} - self._post_deviceprovision(data) - settings = FacilityDataset.objects.get() - self.assertEqual(settings.learner_can_edit_username, True) - self.assertEqual(settings.learner_can_edit_name, True) - self.assertEqual(settings.learner_can_edit_password, True) - self.assertEqual(settings.learner_can_sign_up, True) - self.assertEqual(settings.learner_can_delete_account, True) - self.assertEqual(settings.learner_can_login_with_no_password, False) - self.assertEqual(settings.show_download_button_in_learn, True) - - device_settings = DeviceSettings.objects.get() - self.assertEqual(device_settings.allow_guest_access, True) - - def test_cannot_post_if_provisioned(self): - provision_device() - data = self._default_provision_data() - response = self._post_deviceprovision(data) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_superuser_created(self): - data = self._default_provision_data() - self._post_deviceprovision(data) - self.assertEqual( - FacilityUser.objects.get().username, self.superuser_data["username"] - ) - - def test_superuser_password_set_correctly(self): - data = self._default_provision_data() - self._post_deviceprovision(data) - self.assertTrue( - FacilityUser.objects.get().check_password(self.superuser_data["password"]) - ) - - def test_superuser_device_permissions_created(self): - data = self._default_provision_data() - self._post_deviceprovision(data) - self.assertEqual( - DevicePermissions.objects.get(), - FacilityUser.objects.get().devicepermissions, - ) - - def test_facility_created(self): - data = self._default_provision_data() - self._post_deviceprovision(data) - self.assertEqual(Facility.objects.get().name, self.facility_data["name"]) - - def test_admin_role_created(self): - data = self._default_provision_data() - self._post_deviceprovision(data) - self.assertEqual(Role.objects.get().kind, ADMIN) - - def test_facility_role_created(self): - data = self._default_provision_data() - self._post_deviceprovision(data) - self.assertEqual(Role.objects.get().collection.name, self.facility_data["name"]) - - def test_dataset_set_created(self): - data = self._default_provision_data() - self._post_deviceprovision(data) - self.assertEqual( - FacilityDataset.objects.get().learner_can_edit_username, - self.dataset_data["learner_can_edit_username"], - ) - self.assertEqual( - FacilityDataset.objects.get().learner_can_edit_name, - self.dataset_data["learner_can_edit_name"], - ) - self.assertEqual( - FacilityDataset.objects.get().learner_can_edit_password, - self.dataset_data["learner_can_edit_password"], - ) - self.assertEqual( - FacilityDataset.objects.get().learner_can_sign_up, - self.dataset_data["learner_can_sign_up"], - ) - self.assertEqual( - FacilityDataset.objects.get().learner_can_delete_account, - self.dataset_data["learner_can_delete_account"], - ) - self.assertEqual( - FacilityDataset.objects.get().learner_can_login_with_no_password, - self.dataset_data["learner_can_login_with_no_password"], - ) - - def test_device_settings_created(self): - data = self._default_provision_data() - self.assertEqual(DeviceSettings.objects.count(), 0) - self._post_deviceprovision(data) - self.assertEqual(DeviceSettings.objects.count(), 1) - - def test_device_settings_values(self): - data = self._default_provision_data() - data["allow_guest_access"] = False - self._post_deviceprovision(data) - device_settings = DeviceSettings.objects.get() - self.assertEqual(device_settings.default_facility, Facility.objects.get()) - self.assertFalse(device_settings.allow_guest_access) - self.assertFalse(device_settings.allow_peer_unlisted_channel_import) - self.assertTrue(device_settings.allow_learner_unassigned_resource_access) - - def test_create_superuser_error(self): - data = self._default_provision_data() - data.update({"superuser": {}}) - response = self._post_deviceprovision(data) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - def test_osuser_superuser_error_no_app(self): - with plugin_disabled("kolibri.plugins.app"): - data = self._default_provision_data() - del data["superuser"] - response = self._post_deviceprovision(data) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - def test_osuser_superuser_created(self): - with plugin_enabled("kolibri.plugins.app"), register_capabilities( - **{GET_OS_USER: lambda x: ("test_user", True)} - ): - initialize_url = interface.get_initialize_url(auth_token="test") - self.client.get(initialize_url) - data = self._default_provision_data() - del data["superuser"] - data.update({"auth_token": "test"}) - self._post_deviceprovision(data) - self.client.get(initialize_url) - self.assertEqual( - DevicePermissions.objects.get(), - FacilityUser.objects.get().devicepermissions, - ) - self.assertTrue(FacilityUser.objects.get().os_user) - - def test_imported_facility_no_update(self): - facility = Facility.objects.create(name="This is a test") - settings = FacilityDataset.objects.get() - settings.learner_can_edit_username = True - settings.save() - data = self._default_provision_data() - data["facility_id"] = facility.id - del data["facility"] - # Client should pass an empty Dict for settings - data["settings"] = { - "learner_can_edit_username": False, - "on_my_own_setup": True, - } - settings.refresh_from_db() - facility.refresh_from_db() - self._post_deviceprovision(data) - self.assertEqual(settings.learner_can_edit_username, True) - self.assertEqual(facility.on_my_own_setup, False) - - def test_imported_facility_with_fake_facility_id(self): - data = self._default_provision_data() - # Fake facility_id - data["facility_id"] = "12345678123456781234567812345678" - del data["facility"] - response = self._post_deviceprovision(data) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - def test_imported_facility_with_no_facility_data(self): - data = self._default_provision_data() - # Try to create facility with no data - data["facility_id"] = None - del data["facility"] - response = self._post_deviceprovision(data) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - class DeviceSettingsTestCase(APITestCase): @classmethod def setUpTestData(cls): @@ -980,31 +762,3 @@ def test_downloads_queryset__sync_downloads_in_progress_removed(self): content_removal_request.save() response = self.client.get(reverse("kolibri:core:usersyncstatus-list")) self.assertFalse(response.data[0]["sync_downloads_in_progress"]) - - -class CSRFProtectedDeviceTestCase(APITestCase): - def setUp(self): - clear_process_cache() - self.client_csrf = APIClient(enforce_csrf_checks=True) - self.superuser_data = {"username": "superuser", "password": "password"} - self.facility_data = {"name": "Wilson Elementary"} - self.preset_data = "nonformal" - self.settings = {} - self.allow_guest_access = True - self.language_id = "en" - - def test_csrf_protected_deviceprovision(self): - response = self.client_csrf.post( - reverse("kolibri:core:deviceprovision"), - { - "device_name": None, - "superuser": self.superuser_data, - "facility": self.facility_data, - "preset": self.preset_data, - "settings": self.settings, - "language_id": self.language_id, - "allow_guest_access": self.allow_guest_access, - }, - format="json", - ) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) diff --git a/kolibri/core/device/test/test_tasks.py b/kolibri/core/device/test/test_tasks.py new file mode 100644 index 00000000000..dd1cfe6f031 --- /dev/null +++ b/kolibri/core/device/test/test_tasks.py @@ -0,0 +1,202 @@ +import pytest +from django.test import TestCase +from rest_framework.exceptions import ParseError +from rest_framework.exceptions import ValidationError + +from kolibri.core.auth.constants.role_kinds import ADMIN +from kolibri.core.auth.models import Facility +from kolibri.core.auth.models import FacilityDataset +from kolibri.core.auth.models import FacilityUser +from kolibri.core.auth.models import Role +from kolibri.core.auth.test.helpers import clear_process_cache +from kolibri.core.device.models import DevicePermissions +from kolibri.core.device.models import DeviceSettings +from kolibri.core.device.tasks import DeviceProvisionValidator +from kolibri.core.device.tasks import provisiondevice +from kolibri.plugins.utils.test.helpers import plugin_disabled + + +class DeviceProvisionTestCase(TestCase): + def setUp(self): + clear_process_cache() + + superuser_data = {"username": "superuser", "password": "password"} + facility_data = {"name": "Wilson Elementary"} + preset_data = "nonformal" + dataset_data = { + "learner_can_edit_username": True, + "learner_can_edit_name": True, + "learner_can_edit_password": True, + "learner_can_sign_up": True, + "learner_can_delete_account": True, + "learner_can_login_with_no_password": False, + } + settings = {} + allow_guest_access = True + + language_id = "en" + + def _default_provision_data(self): + return { + "type": "kolibri.core.device.tasks.provisiondevice", + "device_name": None, + "superuser": self.superuser_data, + "facility": self.facility_data, + "preset": self.preset_data, + "settings": self.settings, + "language_id": self.language_id, + "allow_guest_access": self.allow_guest_access, + } + + def _post_deviceprovision(self, data): + serializer = DeviceProvisionValidator(data=data) + serializer.is_valid(raise_exception=True) + validated_data = serializer.validated_data + return provisiondevice(**validated_data["kwargs"]) + + def test_personal_setup_defaults(self): + data = self._default_provision_data() + data["preset"] = "informal" + # Client should pass an empty Dict for settings + data["settings"] = {} + self._post_deviceprovision(data) + settings = FacilityDataset.objects.get() + self.assertEqual(settings.learner_can_edit_username, True) + self.assertEqual(settings.learner_can_edit_name, True) + self.assertEqual(settings.learner_can_edit_password, True) + self.assertEqual(settings.learner_can_sign_up, True) + self.assertEqual(settings.learner_can_delete_account, True) + self.assertEqual(settings.learner_can_login_with_no_password, False) + self.assertEqual(settings.show_download_button_in_learn, True) + + device_settings = DeviceSettings.objects.get() + self.assertEqual(device_settings.allow_guest_access, True) + + def test_superuser_created(self): + data = self._default_provision_data() + self._post_deviceprovision(data) + self.assertEqual( + FacilityUser.objects.get().username, self.superuser_data["username"] + ) + + def test_superuser_password_set_correctly(self): + data = self._default_provision_data() + self._post_deviceprovision(data) + self.assertTrue( + FacilityUser.objects.get().check_password(self.superuser_data["password"]) + ) + + def test_superuser_device_permissions_created(self): + data = self._default_provision_data() + self._post_deviceprovision(data) + self.assertEqual( + DevicePermissions.objects.get(), + FacilityUser.objects.get().devicepermissions, + ) + + def test_facility_created(self): + data = self._default_provision_data() + self._post_deviceprovision(data) + self.assertEqual(Facility.objects.get().name, self.facility_data["name"]) + + def test_admin_role_created(self): + data = self._default_provision_data() + self._post_deviceprovision(data) + self.assertEqual(Role.objects.get().kind, ADMIN) + + def test_facility_role_created(self): + data = self._default_provision_data() + self._post_deviceprovision(data) + self.assertEqual(Role.objects.get().collection.name, self.facility_data["name"]) + + def test_dataset_set_created(self): + data = self._default_provision_data() + self._post_deviceprovision(data) + self.assertEqual( + FacilityDataset.objects.get().learner_can_edit_username, + self.dataset_data["learner_can_edit_username"], + ) + self.assertEqual( + FacilityDataset.objects.get().learner_can_edit_name, + self.dataset_data["learner_can_edit_name"], + ) + self.assertEqual( + FacilityDataset.objects.get().learner_can_edit_password, + self.dataset_data["learner_can_edit_password"], + ) + self.assertEqual( + FacilityDataset.objects.get().learner_can_sign_up, + self.dataset_data["learner_can_sign_up"], + ) + self.assertEqual( + FacilityDataset.objects.get().learner_can_delete_account, + self.dataset_data["learner_can_delete_account"], + ) + self.assertEqual( + FacilityDataset.objects.get().learner_can_login_with_no_password, + self.dataset_data["learner_can_login_with_no_password"], + ) + + def test_device_settings_created(self): + data = self._default_provision_data() + self.assertEqual(DeviceSettings.objects.count(), 0) + self._post_deviceprovision(data) + self.assertEqual(DeviceSettings.objects.count(), 1) + + def test_device_settings_values(self): + data = self._default_provision_data() + data["allow_guest_access"] = False + self._post_deviceprovision(data) + device_settings = DeviceSettings.objects.get() + self.assertEqual(device_settings.default_facility, Facility.objects.get()) + self.assertFalse(device_settings.allow_guest_access) + self.assertFalse(device_settings.allow_peer_unlisted_channel_import) + self.assertTrue(device_settings.allow_learner_unassigned_resource_access) + + def test_create_superuser_error(self): + data = self._default_provision_data() + data.update({"superuser": {}}) + with pytest.raises(ValidationError): + self._post_deviceprovision(data) + + def test_osuser_superuser_error_no_app(self): + with plugin_disabled("kolibri.plugins.app"): + data = self._default_provision_data() + del data["superuser"] + with pytest.raises(ValidationError): + self._post_deviceprovision(data) + + def test_imported_facility_no_update(self): + facility = Facility.objects.create(name="This is a test") + settings = FacilityDataset.objects.get() + settings.learner_can_edit_username = True + settings.save() + data = self._default_provision_data() + data["facility_id"] = facility.id + del data["facility"] + # Client should pass an empty Dict for settings + data["settings"] = { + "learner_can_edit_username": False, + "on_my_own_setup": True, + } + settings.refresh_from_db() + facility.refresh_from_db() + self._post_deviceprovision(data) + self.assertEqual(settings.learner_can_edit_username, True) + self.assertEqual(facility.on_my_own_setup, False) + + def test_imported_facility_with_fake_facility_id(self): + data = self._default_provision_data() + # Fake facility_id + data["facility_id"] = "12345678123456781234567812345678" + del data["facility"] + with pytest.raises(ParseError): + self._post_deviceprovision(data) + + def test_imported_facility_with_no_facility_data(self): + data = self._default_provision_data() + # Try to create facility with no data + data["facility_id"] = None + del data["facility"] + with pytest.raises(ParseError): + self._post_deviceprovision(data) diff --git a/kolibri/core/tasks/api.py b/kolibri/core/tasks/api.py index 8f8a5512afb..9f59464f240 100644 --- a/kolibri/core/tasks/api.py +++ b/kolibri/core/tasks/api.py @@ -100,7 +100,6 @@ def _job_to_response(self, job): "args": job.args, "kwargs": job.kwargs, "extra_metadata": job.extra_metadata, - "result": job.result, # Output is UTC naive, coerce to UTC aware. "scheduled_datetime": make_aware(orm_job.scheduled_time, utc).isoformat(), "repeat": orm_job.repeat, diff --git a/kolibri/plugins/setup_wizard/assets/src/views/onboarding-forms/SettingUpKolibri.vue b/kolibri/plugins/setup_wizard/assets/src/views/onboarding-forms/SettingUpKolibri.vue index 40175a25a42..64f1a8abccd 100644 --- a/kolibri/plugins/setup_wizard/assets/src/views/onboarding-forms/SettingUpKolibri.vue +++ b/kolibri/plugins/setup_wizard/assets/src/views/onboarding-forms/SettingUpKolibri.vue @@ -211,7 +211,7 @@ throw new Error('Device provisioning task not found'); } if (task.status === TaskStatuses.COMPLETED) { - const facilityId = task.result.facility_id; + const facilityId = task.extra_metadata.facility_id; const { username, password } = this.deviceProvisioningData.superuser; this.clearPollingTasks(); this.wrapOnboarding();