diff --git a/build-aux/flatpak/modules/python3-kolibri-patches/0001-Allow-superuser-to-be-null-in-device-provision-API.patch b/build-aux/flatpak/modules/python3-kolibri-patches/0001-Allow-superuser-to-be-null-in-device-provision-API.patch deleted file mode 100644 index a411abc..0000000 --- a/build-aux/flatpak/modules/python3-kolibri-patches/0001-Allow-superuser-to-be-null-in-device-provision-API.patch +++ /dev/null @@ -1,49 +0,0 @@ -From cdb3ae92eba9ee22ed5b264784e2c1bf72541666 Mon Sep 17 00:00:00 2001 -From: Dylan McCall -Date: Thu, 2 Dec 2021 15:31:30 -0800 -Subject: [PATCH] Allow superuser to be null in device provision API - ---- - kolibri/core/device/serializers.py | 20 ++++++++++++-------- - 1 file changed, 12 insertions(+), 8 deletions(-) - -diff --git a/kolibri/core/device/serializers.py b/kolibri/core/device/serializers.py -index 9103e49f93..c84de06979 100644 ---- a/kolibri/core/device/serializers.py -+++ b/kolibri/core/device/serializers.py -@@ -40,8 +40,8 @@ class DeviceSerializerMixin(object): - class DeviceProvisionSerializer(DeviceSerializerMixin, serializers.Serializer): - facility = FacilitySerializer() - preset = serializers.ChoiceField(choices=choices) -- superuser = NoFacilityFacilityUserSerializer() -- language_id = serializers.CharField(max_length=15) -+ superuser = NoFacilityFacilityUserSerializer(allow_null=True) -+ language_id = serializers.CharField(max_length=15, allow_null=True) - device_name = serializers.CharField(max_length=50, allow_null=True) - settings = serializers.JSONField() - allow_guest_access = serializers.BooleanField(allow_null=True) -@@ -78,12 +78,16 @@ class DeviceProvisionSerializer(DeviceSerializerMixin, serializers.Serializer): - facility.dataset.save() - - # Create superuser -- superuser = FacilityUser.objects.create_superuser( -- validated_data["superuser"]["username"], -- validated_data["superuser"]["password"], -- facility=facility, -- full_name=validated_data["superuser"].get("full_name"), -- ) -+ superuser_data = validated_data.pop("superuser") -+ if superuser_data: -+ superuser = FacilityUser.objects.create_superuser( -+ superuser_data["username"], -+ superuser_data["password"], -+ facility=facility, -+ full_name=superuser_data.get("full_name"), -+ ) -+ else: -+ superuser = None - - # Create device settings - language_id = validated_data.pop("language_id") --- -2.33.1 diff --git a/build-aux/flatpak/modules/python3-kolibri-pytz.json b/build-aux/flatpak/modules/python3-kolibri-pytz.json index cea1924..6976470 100644 --- a/build-aux/flatpak/modules/python3-kolibri-pytz.json +++ b/build-aux/flatpak/modules/python3-kolibri-pytz.json @@ -2,13 +2,13 @@ "name": "python3-pytz", "buildsystem": "simple", "build-commands": [ - "pip3 install --exists-action=i --no-index --find-links=\"file://${PWD}\" pytz==2022.7.1 --upgrade --target=\"${KOLIBRI_MODULE_PATH}/dist/\"" + "pip3 install --exists-action=i --no-index --find-links=\"file://${PWD}\" pytz==2023.3.post1 --upgrade --target=\"${KOLIBRI_MODULE_PATH}/dist/\"" ], "sources": [ { "type": "file", - "url": "https://files.pythonhosted.org/packages/2e/09/fbd3c46dce130958ee8e0090f910f1fe39e502cc5ba0aadca1e8a2b932e5/pytz-2022.7.1-py2.py3-none-any.whl", - "sha256": "78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a" + "url": "https://files.pythonhosted.org/packages/69/4f/7bf883f12ad496ecc9514cd9e267b29a68b3e9629661a2bbc24f80eff168/pytz-2023.3.post1.tar.gz", + "sha256": "7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b" } ] } diff --git a/build-aux/flatpak/modules/python3-kolibri.json b/build-aux/flatpak/modules/python3-kolibri.json index 30a0a58..8f7d2c1 100644 --- a/build-aux/flatpak/modules/python3-kolibri.json +++ b/build-aux/flatpak/modules/python3-kolibri.json @@ -3,14 +3,13 @@ "buildsystem": "simple", "build-commands": [ "pip3 install --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} kolibri", - "patch -d ${KOLIBRI_MODULE_PATH} -p2 < 0001-Allow-superuser-to-be-null-in-device-provision-API.patch", "patch -d ${KOLIBRI_MODULE_PATH}/dist/ifcfg -p3 < dist_ifcfg/0001-Remove-needless-ifcfg-warning.patch" ], "sources": [ { "type": "file", - "url": "https://files.pythonhosted.org/packages/10/7d/8bfa283b1f89c2e4c442da3aff8eea119403609f176a97157454851733a7/kolibri-0.15.12-py2.py3-none-any.whl", - "sha256": "66871d3780263c3f5b5562c9821e803952edc0da594036dfb532fa25f5917c04" + "url": "https://github.com/learningequality/kolibri/releases/download/v0.16.0-beta5/kolibri-0.16.0b5-py2.py3-none-any.whl", + "sha256": "3925b7a18b6684547fd50e1df7326ace318d9d81d1fa02317f1870e117eb515c" }, { "type": "dir", diff --git a/build-aux/flatpak/modules/python3-setproctitle.json b/build-aux/flatpak/modules/python3-setproctitle.json index eabff3e..e678174 100644 --- a/build-aux/flatpak/modules/python3-setproctitle.json +++ b/build-aux/flatpak/modules/python3-setproctitle.json @@ -7,8 +7,8 @@ "sources": [ { "type": "file", - "url": "https://files.pythonhosted.org/packages/78/9a/cf6bf4c472b59aef3f3c0184233eeea8938d3366bcdd93d525261b1b9e0a/setproctitle-1.2.3.tar.gz", - "sha256": "ecf28b1c07a799d76f4326e508157b71aeda07b84b90368ea451c0710dbd32c0" + "url": "https://files.pythonhosted.org/packages/b5/47/ac709629ddb9779fee29b7d10ae9580f60a4b37e49bce72360ddf9a79cdc/setproctitle-1.3.2.tar.gz", + "sha256": "b9fb97907c830d260fa0658ed58afd48a86b2b88aac521135c352ff7fd3477fd" } ] } diff --git a/build-aux/flatpak/org.endlessos.Key.Devel.json b/build-aux/flatpak/org.endlessos.Key.Devel.json index 5a55f2b..ebf7c7a 100644 --- a/build-aux/flatpak/org.endlessos.Key.Devel.json +++ b/build-aux/flatpak/org.endlessos.Key.Devel.json @@ -16,7 +16,8 @@ "--system-talk-name=org.endlessos.Key.Devel.Daemon", "--env=KOLIBRI_HOME=~/.var/app/org.endlessos.Key.Devel/data/kolibri", "--env=KOLIBRI_HTTP_PORT=0", - "--env=PYTHONPATH=/app/kolibri-plugins/lib/python" + "--env=PYTHONPATH=/app/kolibri-plugins/lib/python", + "--env=JSC_useDFGJIT=0" ], "add-extensions" : { "org.learningequality.Kolibri.Content" : { diff --git a/src/kolibri_app/globals.py b/src/kolibri_app/globals.py index ee7452e..3641047 100644 --- a/src/kolibri_app/globals.py +++ b/src/kolibri_app/globals.py @@ -14,8 +14,12 @@ config.PROFILE_ENV_PREFIX + "APP_DEVELOPER_EXTRAS" ) -APP_FORCE_AUTOMATIC_LOGIN = os.environ.get( - config.PROFILE_ENV_PREFIX + "APP_FORCE_AUTOMATIC_LOGIN" +APP_DISABLE_AUTOMATIC_LOGIN = os.environ.get( + config.PROFILE_ENV_PREFIX + "APP_DISABLE_AUTOMATIC_LOGIN" +) + +APP_DISABLE_AUTOMATIC_PROVISION = os.environ.get( + config.PROFILE_ENV_PREFIX + "APP_DISABLE_AUTOMATIC_PROVISION" ) XDG_CURRENT_DESKTOP = os.environ.get("XDG_CURRENT_DESKTOP") diff --git a/src/kolibri_daemon/kolibri_http_process.py b/src/kolibri_daemon/kolibri_http_process.py index c812333..48aefac 100644 --- a/src/kolibri_daemon/kolibri_http_process.py +++ b/src/kolibri_daemon/kolibri_http_process.py @@ -61,7 +61,6 @@ def run(self): self.__kolibri_bus = KolibriProcessBus( port=OPTIONS["Deployment"]["HTTP_PORT"], zip_port=OPTIONS["Deployment"]["ZIP_CONTENT_PORT"], - background=False, ) kolibri_daemon_plugin = _KolibriDaemonPlugin(self.__kolibri_bus, self.context) diff --git a/src/kolibri_daemon/kolibri_utils.py b/src/kolibri_daemon/kolibri_utils.py index 8e656b6..42ec032 100644 --- a/src/kolibri_daemon/kolibri_utils.py +++ b/src/kolibri_daemon/kolibri_utils.py @@ -5,11 +5,14 @@ import json import logging import os +import platform import shutil import typing +from gettext import gettext as _ from pathlib import Path from kolibri_app.config import KOLIBRI_HOME_TEMPLATE_DIR +from kolibri_app.globals import APP_DISABLE_AUTOMATIC_PROVISION from kolibri_app.globals import KOLIBRI_HOME_PATH from .content_extensions_manager import ContentExtensionsManager @@ -26,6 +29,7 @@ "kolibri_app_desktop_xdg_plugin", "kolibri_desktop_auth_plugin", "kolibri_dynamic_collections_plugin", + "kolibri_explore_plugin", "kolibri_zim_plugin", ] @@ -56,10 +60,8 @@ def _init_kolibri_env(): # workload, we can use a smaller number of threads. os.environ.setdefault("KOLIBRI_CHERRYPY_THREAD_POOL", "10") - # Automatically provision with $KOLIBRI_HOME/automatic_provision.json if it - # exists. - # TODO: Once kolibri-gnome supports automatic login for all cases, use an - # included automatic provision file by default. + # Automatically provision with $KOLIBRI_HOME/automatic_provision.json or a + # generated automatic_provision.json if applicable. automatic_provision_path = _get_automatic_provision_path() if automatic_provision_path: os.environ.setdefault( @@ -72,7 +74,6 @@ def _init_kolibri_env(): def _enable_kolibri_plugin(plugin_name: str, optional=False) -> bool: from kolibri.plugins import config as plugins_config - from kolibri.plugins.registry import registered_plugins from kolibri.plugins.utils import enable_plugin if optional and not importlib.util.find_spec(plugin_name): @@ -80,7 +81,6 @@ def _enable_kolibri_plugin(plugin_name: str, optional=False) -> bool: if plugin_name not in plugins_config.ACTIVE_PLUGINS: logger.info(f"Enabling plugin {plugin_name}") - registered_plugins.register_plugins([plugin_name]) enable_plugin(plugin_name) return True @@ -89,24 +89,36 @@ def _enable_kolibri_plugin(plugin_name: str, optional=False) -> bool: def _get_automatic_provision_path() -> typing.Optional[Path]: path = KOLIBRI_HOME_PATH.joinpath("automatic_provision.json") - if not path.is_file(): + if path.is_file(): + return path + elif not APP_DISABLE_AUTOMATIC_PROVISION: + # TODO: Only do this if Kolibri does not have a facility configured. + with path.open("w") as file: + json.dump(_get_automatic_provision_data(), file) + return path + else: return None - with path.open("r") as in_file: - try: - data = json.load(in_file) - except json.JSONDecodeError as error: - logger.warning( - f"Error reading automatic provision data from '{path.as_posix()}': {error}" - ) - return None - - if not data.keys().isdisjoint(["facility", "superusername", "superuserpassword"]): - # If a file has an attribute unique to the old format, we will asume it - # is outdated. - return None - return path +def _get_automatic_provision_data(): + facility_name = _("Kolibri on {host}").format(host=platform.node() or "localhost") + return { + "facility_name": facility_name, + "preset": "formal", + "facility_settings": { + "learner_can_login_with_no_password": False, + }, + "device_settings": { + "language_id": None, + "landing_page": "learn", + "allow_guest_access": False, + "allow_other_browsers_to_connect": False, + }, + "superuser": { + "username": None, + "password": None, + }, + } def _kolibri_update_from_home_template(): diff --git a/src/kolibri_gnome/kolibri_context.py b/src/kolibri_gnome/kolibri_context.py index 53a0dcc..9df1536 100644 --- a/src/kolibri_gnome/kolibri_context.py +++ b/src/kolibri_gnome/kolibri_context.py @@ -1,10 +1,8 @@ from __future__ import annotations import logging -import platform import re import typing -from gettext import gettext as _ from pathlib import Path from urllib.parse import parse_qs from urllib.parse import SplitResult @@ -245,7 +243,6 @@ class _KolibriSetupHelper(GObject.GObject): is_app_key_cookie_ready = GObject.Property(type=bool, default=False) is_session_cookie_ready = GObject.Property(type=bool, default=False) - is_facility_ready = GObject.Property(type=bool, default=False) is_setup_complete = GObject.Property(type=bool, default=False) @@ -270,23 +267,19 @@ def __init__( self.__kolibri_daemon.connect( "notify::app-key-cookie", self.__kolibri_daemon_on_notify_app_key_cookie ) - self.__kolibri_daemon.connect( - "notify::is-started", self.__kolibri_daemon_on_notify_is_started - ) await_properties( [ - (self, "is-facility-ready"), + (self.__kolibri_daemon, "is-started"), (self, "login-token"), ], - self.__on_await_facility_ready_and_login_token, + self.__on_await_kolibri_is_started_and_login_token, ) map_properties( [ (self, "is-app-key-cookie-ready"), (self, "is-session-cookie-ready"), - (self, "is-facility-ready"), ], self.__update_is_setup_complete, ) @@ -311,90 +304,28 @@ def __kolibri_daemon_on_dbus_owner_changed( else: self.props.is_session_cookie_ready = True - def __kolibri_daemon_on_notify_is_started( - self, kolibri_daemon: KolibriDaemonManager, pspec: GObject.ParamSpec = None + def __kolibri_daemon_on_login_token_ready( + self, kolibri_daemon: KolibriDaemonManager, login_token: typing.Optional[str] ): - self.props.is_facility_ready = False - - if not self.__kolibri_daemon.props.is_started: - return - - if not self.__kolibri_daemon.do_automatic_login: - # No automatic login so we don't need a facility: - self.props.is_facility_ready = True - return - - self.__kolibri_daemon.kolibri_api_get_async( - "/api/public/v1/facility/", - result_cb=self.__on_kolibri_api_facility_response, - ) - - def __on_kolibri_api_facility_response(self, data: typing.Any): - if isinstance(data, list) and data: - self.props.is_facility_ready = True - return - - # There is no facility, so automatically provision the device: - self.__automatic_device_provision() - - def __automatic_device_provision(self): - # TODO: In the future, this could be done in kolibri-daemon itself by - # using a simple automatic_provision.json file in the Kolibri home - # template. We need to do it here for now because we are only - # using this configuration with automatic login, which is only - # enabled in certain cases. - logger.info("Provisioning deviceā€¦") - facility_name = _("Kolibri on {host}").format( - host=platform.node() or "localhost" - ) - request_body_data = { - "facility": { - "name": facility_name, - "learner_can_login_with_no_password": False, - }, - "preset": "formal", - "superuser": None, - "language_id": None, - "device_name": None, - "settings": { - "landing_page": "learn", - "allow_other_browsers_to_connect": False, - }, - "allow_guest_access": False, - } - self.__kolibri_daemon.kolibri_api_post_async( - "/api/device/deviceprovision/", - result_cb=self.__on_kolibri_api_deviceprovision_response, - request_body=request_body_data, - ) - - def __on_kolibri_api_deviceprovision_response(self, data: dict): - logger.info("Device provisioned.") - self.props.is_facility_ready = True + self.props.login_token = login_token - def __on_await_facility_ready_and_login_token( - self, is_facility_ready: bool, login_token: str + def __on_await_kolibri_is_started_and_login_token( + self, is_started: bool, login_token: str ): if self.props.is_session_cookie_ready: return - login_url = self.__kolibri_daemon.get_absolute_url( - self.AUTOLOGIN_URL_TEMPLATE.format(token=login_token) - ) - - self.__login_webview.load_uri(login_url) - - def __kolibri_daemon_on_login_token_ready( - self, kolibri_daemon: KolibriDaemonManager, login_token: typing.Optional[str] - ): - self.props.login_token = login_token - if login_token is None: # If we are unable to get a login token, pretend the session cookie # is ready so the app will proceed as usual. This should only happen # in an edge case where kolibri-daemon is running on the system bus # but is unable to communicate with AccountsService. self.props.is_session_cookie_ready = True + elif self.__kolibri_daemon.do_automatic_login: + login_url = self.__kolibri_daemon.get_absolute_url( + self.AUTOLOGIN_URL_TEMPLATE.format(token=login_token) + ) + self.__login_webview.load_uri(login_url) def __kolibri_daemon_on_notify_app_key_cookie( self, kolibri_daemon: KolibriDaemonManager, pspec: GObject.ParamSpec = None diff --git a/src/kolibri_gnome/kolibri_daemon_manager.py b/src/kolibri_gnome/kolibri_daemon_manager.py index 393e0d8..263eb88 100644 --- a/src/kolibri_gnome/kolibri_daemon_manager.py +++ b/src/kolibri_gnome/kolibri_daemon_manager.py @@ -14,7 +14,7 @@ from gi.repository import Soup from kolibri_app.config import DAEMON_APPLICATION_ID from kolibri_app.config import DAEMON_MAIN_OBJECT_PATH -from kolibri_app.globals import APP_FORCE_AUTOMATIC_LOGIN +from kolibri_app.globals import APP_DISABLE_AUTOMATIC_LOGIN from .utils import GioInputStreamIO @@ -50,9 +50,7 @@ def __init__(self): g_bus_type = KolibriDaemonDBus.get_default_bus_type() - self.__do_automatic_login = ( - g_bus_type == Gio.BusType.SYSTEM or APP_FORCE_AUTOMATIC_LOGIN - ) + self.__do_automatic_login = not APP_DISABLE_AUTOMATIC_LOGIN self.__dbus_proxy = KolibriDaemonDBus.MainProxy( g_bus_type=g_bus_type,