diff options
Diffstat (limited to 'rwa/support/sessionservice/service.py')
-rwxr-xr-x | rwa/support/sessionservice/service.py | 239 |
1 files changed, 149 insertions, 90 deletions
diff --git a/rwa/support/sessionservice/service.py b/rwa/support/sessionservice/service.py index 44e18e2..175ae17 100755 --- a/rwa/support/sessionservice/service.py +++ b/rwa/support/sessionservice/service.py @@ -30,6 +30,7 @@ import signal import time from threading import Thread from typing import Dict, Union +from uuid import uuid4 import click import dbus @@ -43,7 +44,7 @@ from gi.repository import GLib from .config import ALLOW_ONLY_ONE_SESSION, API_PATH, SUPPORTED_API_VERSIONS from .lock import is_locked, lock, unlock from .log import logging -from .session import Session +from .session import Session, combine from .trigger import TriggerServerThread @@ -76,9 +77,14 @@ class RWASupportSessionService(dbus.service.Object): self.sessions = {} self.settings = usersettings.Settings("org.ArcticaProject.RWASupportSessionService") - self.settings.add_setting("web_app_hosts", list, ["http://127.0.0.1:8000"]) + self.settings.add_setting("web_app_hosts", dict) self.settings.load_settings() + # Ensure default value for web app hosts settings + if not self.settings.web_app_hosts: + self.settings.web_app_hosts = {} + self.settings.save_settings() + super().__init__(name, "/RWASupportSessionService") logging.info("D-Bus service has been started.") @@ -90,7 +96,7 @@ class RWASupportSessionService(dbus.service.Object): :return: Whether the string is an URL. """ valid = validators.url(url) - logging.debug(f"Is {url} an URL: {valid}") + logging.debug(f"Is '{url}' an URL: {valid}") return valid def _get_web_app_hosts(self) -> str: @@ -98,9 +104,18 @@ class RWASupportSessionService(dbus.service.Object): Helper function: No D-Bus API. """ - hosts = self.settings.web_app_hosts + + logging.debug("Raw web_app_hosts: %s", self.settings.web_app_hosts.items()) + hosts = [ + self._build_host_dict(key, value) for key, value in self.settings.web_app_hosts.items() + ] return json.dumps(hosts) + def _build_host_dict(self, host_uuid: str, host: dict) -> dict: + """Include the host ID in the host dictionary.""" + host.update({'uuid': host_uuid}) + return host + @dbus.service.method("org.ArcticaProject.RWASupportSessionService", out_signature="s") def get_web_app_hosts(self) -> str: """Get all registered RWA.Support.WebApp hosts. @@ -111,12 +126,12 @@ class RWASupportSessionService(dbus.service.Object): :: - ["https://example.org", "http://127.0.0.1:8000"] + [{"url": "https://example.org", "uuid": <host_uuid>}, {"url": "http://127.0.0.1:8000", "uuid": <host_uuid>}] """ logging.info("D-Bus method call: %s()", "get_web_app_hosts") response = self._get_web_app_hosts() - logging.debug('The response to D-Bus caller: "%s"', response) + logging.info('The response to D-Bus caller: "%s"', response) return response def _do_api_handshake(self, host: str) -> Dict[str, str]: @@ -149,39 +164,39 @@ class RWASupportSessionService(dbus.service.Object): r = requests.post(url) except requests.exceptions.ConnectionError: - logging.warning(" resulted in a connection error.") + logging.warning("Handshake resulted in a connection error.") return {"status": "error", "type": "connection"} if not r.ok: - logging.warning(" resulted in a connection error.") + logging.warning("Handshake resulted in a connection error.") return {"status": "error", "type": "connection"} if not r.json()["allowed"]: - logging.warning(" was not permitted.") + logging.warning("Handshake was not permitted.") return {"status": "error", "type": "permission_denied"} if r.json().get("api_version") not in SUPPORTED_API_VERSIONS: - logging.warning(" resulted in a incompatible API version.") + logging.warning("Handshake resulted in a incompatible API version.") return {"status": "error", "type": "unsupported_server"} - logging.info(" was successful.") + logging.info("Handshake was successful.") return {"status": "success", "type": "valid_host"} @dbus.service.method( "org.ArcticaProject.RWASupportSessionService", in_signature="s", out_signature="s" ) - def add_web_app_host(self, host: str) -> str: + def add_web_app_host(self, host_url: str) -> str: """Add a RWA.Support.WebApp host. - :param host: Exact hostname of the RWA.Support.WebApp host (D-Bus string) - :return: All registered hosts as JSON array (D-Bus string) + :param host_url: Exact hostname of the RWA.Support.WebApp host (D-Bus string) + :return: The registered host as JSON object (D-Bus string) **Structure of returned JSON (success):** :: - ["https://example.org", "http://127.0.0.1:8000"] + {"status": "success", "host": {"url": "https://example.org", "uuid": <host_uuid>}} **Structure of returned JSON (error):** @@ -197,79 +212,108 @@ class RWASupportSessionService(dbus.service.Object): * ``invalid_url`` * ``duplicate`` """ - host = str(host).rstrip("/") + host_url = str(host_url).rstrip("/") - logging.info('D-Bus method call: %s("%s")', "add_web_app_host", host) + logging.info('D-Bus method call: %s("%s")', "add_web_app_host", host_url) - if not self._is_url(host): + if not self._is_url(host_url): logging.warning("Given URL is not valid!") - logging.debug('Did not add "%s" to "web_app_hosts" in user_settings', host) - return json.dumps({"status": "error", "type": "invalid_url"}) + logging.debug('Did not add "%s" to "web_app_hosts" in user_settings', host_url) - if host in self.settings.web_app_hosts: - logging.warning("Given URL is already present!") - logging.debug('Did not add "%s" to "web_app_hosts" in user_settings', host) - return json.dumps({"status": "error", "type": "duplicate"}) + response = json.dumps({"status": "error", "type": "invalid_url"}) + logging.info('The response to D-Bus caller: "%s"', response) + return response - res = self._do_api_handshake(host) + try: + for uuid, host in self.settings.web_app_hosts.items(): + if host_url == host['url']: + logging.warning("Given URL is already present!") + logging.debug('Did not add "%s" to "web_app_hosts" in user_settings', host_url) + + response = json.dumps({"status": "error", "type": "duplicate"}) + logging.info('The response to D-Bus caller: "%s"', response) + return response + except (KeyError, IndexError): + logging.warning( + 'Got an exception while trying to find given url ' + 'in already existing hosts!' + ) + + res = self._do_api_handshake(host_url) if res["status"] == "error": - logging.debug('Did not add "%s" to "web_app_hosts" in user_settings', host) - return json.dumps(res) + logging.debug('Did not add "%s" to "web_app_hosts" in user_settings', host_url) - self.settings.web_app_hosts.append(host) + response = json.dumps(res) + logging.info('The response to D-Bus caller: "%s"', response) + return response + + host_uuid = str(uuid4()) + host_object = {"url": host_url} + + self.settings.web_app_hosts[host_uuid] = host_object self.settings.save_settings() - logging.debug('Added "%s" to "web_app_hosts" in user_settings', host) - response = self._get_web_app_hosts() - logging.debug('The response to D-Bus caller: "%s"', response) + logging.info('Added "%s" to "web_app_hosts" in user_settings', host_url) + + response = {"status": "success", "host": self._build_host_dict(host_uuid, host_object)} + response = json.dumps(response) + logging.info('The response to D-Bus caller: "%s"', response) return response @dbus.service.method( "org.ArcticaProject.RWASupportSessionService", in_signature="i", out_signature="s" ) - def remove_web_app_host(self, host_idx: int) -> str: + def remove_web_app_host(self, host_uuid: str) -> str: """Remove a RWA.Support.WebApp host. - :param idx: Index of web app host (D-Bus integer) + :param host_uuid: ID of web app host (D-Bus string) :return: All registered hosts as JSON array (D-Bus string) **Structure of returned JSON:** :: - ["https://example.org", "http://127.0.0.1:8000"] + [{"url": "https://example.org", "uuid": <host_uuid>}, {"url": "http://127.0.0.1:8000", "uuid": <host_uuid>}] """ - logging.info("D-Bus method call: %s(%d)", "remove_web_app_host", host_idx) + logging.info("D-Bus method call: %s(%s)", "remove_web_app_host", host_uuid) - if host_idx >= 0 and host_idx < len(self.settings.web_app_hosts): - host = self.settings.web_app_hosts[host_idx] - del self.settings.web_app_hosts[host_idx] + if host_uuid in self.settings.web_app_hosts: + host_object = self.settings.web_app_hosts[host_uuid] + del self.settings.web_app_hosts[host_uuid] self.settings.save_settings() - logging.debug('Removed web_app_hosts[%d]="%s" in user settings', host_idx, host) + logging.info('Removed web_app_hosts[%s]="%s" in user settings', host_uuid, host_object) else: logging.warning("Given host index is not valid!") logging.debug( - "Did not remove web_app_hosts[%d] (not existent!) in " "user settings", host_idx + "Did not remove web_app_hosts[%s]" + "(not existent!) in " "user settings", host_uuid ) + return json.dumps({"status": "error", "type": "host_not_found"}) response = self._get_web_app_hosts() - logging.debug('The response to D-Bus caller: "%s"', response) + logging.info('The response to D-Bus caller: "%s"', response) return response @dbus.service.method( "org.ArcticaProject.RWASupportSessionService", in_signature="i", out_signature="s" ) - def start(self, host_idx: int) -> str: + def start(self, host_uuid: str) -> str: """Start a new remote session and register it in RWA.Support.WebApp. - :param host_idx: Index of web app host (D-Bus integer) + :param host_uuid: ID of web app host (D-Bus string) :return: Result as JSON (D-Bus string) **Structure of returned JSON (success):** :: - {"status": "success", "id": <pid>, "url": "<url>", "pin": <pin>} + { + "status": "success", + "host_uuid": "<host_uuid>", + "session_id": <session_id>, + "url": "<url>", + "pin": <pin> + } **Structure of returned JSON (error):** @@ -285,7 +329,7 @@ class RWASupportSessionService(dbus.service.Object): * ``permission_denied`` * ``unsupported_server`` """ - logging.info("D-Bus method call: %s(%d)", "start", host_idx) + logging.info("D-Bus method call: %s(%s)", "start", host_uuid) if ALLOW_ONLY_ONE_SESSION and len(self.sessions.values()) > 0: logging.warning( @@ -298,25 +342,26 @@ class RWASupportSessionService(dbus.service.Object): return response try: - host = self.settings.web_app_hosts[host_idx] - logging.debug('web_app_hosts[%d] is the following host: "%s"', host_idx, host) - except IndexError: - logging.error("web_app_hosts[%d] does not exist!", host_idx) + host_object = self.settings.web_app_hosts[host_uuid] + host_object = self._build_host_dict(host_uuid, host_object) + logging.debug('web_app_hosts[%s] is the following host: "%s"', host_uuid, host_object) + except (KeyError, IndexError): + logging.error("web_app_hosts[%s] does not exist!", host_uuid) response = json.dumps({"status": "error", "type": "host_not_found"}) logging.info("The response to the D-Bus caller: '%s'", response) return response # Check host by doing a handshake - res = self._do_api_handshake(host) + res = self._do_api_handshake(host_object["url"]) if res["status"] == "error": return json.dumps(res) # Start session try: - session = Session(host, self.trigger_service.port, self.mockup_mode) + session = Session(host_object, self.trigger_service.port, self.mockup_mode) # Add session to sessions list - self.sessions[session.pid] = session + self.sessions[session.combined_id] = session # Start session update service self._ensure_update_service() @@ -340,23 +385,25 @@ class RWASupportSessionService(dbus.service.Object): return response @dbus.service.method( - "org.ArcticaProject.RWASupportSessionService", in_signature="i", out_signature="s" + "org.ArcticaProject.RWASupportSessionService", in_signature="si", out_signature="s" ) - def status(self, pid: int) -> str: + def status(self, host_uuid: str, session_id: int) -> str: """Return the status of a session. .. note:: This uses the last status version got by the update service in the background. - :param pid: (Process) ID of session (D-Bus integer) + :param host_uuid: Host ID (D-Bus string) + :param session_id: Session ID (D-Bus integer) + :return: Session status as JSON (D-Bus string) **Structure of returned JSON:** :: - {"id": <pid>, "status": <status>} + {"host_uuid": "<host_uuid>", "session_id": <session_id>, "status": <status>} **Possible status options:** @@ -367,58 +414,67 @@ class RWASupportSessionService(dbus.service.Object): ``dead`` There was a problem, so that the session is dead. ============ ====================== """ - logging.info("D-Bus method call: %s(%d)", "status", pid) - response = self._get_status(pid) + logging.info("D-Bus method call: %s(%s, %d)", "status", host_uuid, session_id) + response = self._get_status(host_uuid, session_id) logging.info("The response to the D-Bus caller: '%s'", response) return response @dbus.service.method( - "org.ArcticaProject.RWASupportSessionService", in_signature="i", out_signature="s" + "org.ArcticaProject.RWASupportSessionService", in_signature="si", out_signature="s" ) - def refresh_status(self, pid: int) -> str: + def refresh_status(self, host_uuid: str, session_id: int) -> str: """Update status from WebApp before returning it here like :meth:`status`.""" - logging.info("D-Bus method call: %s(%d)", "refresh_status", pid) + logging.info("D-Bus method call: %s(%s, %d)", "refresh_status", host_uuid, session_id) - self._update_session(pid) - response = self._get_status(pid) + self._update_session(host_uuid, session_id) + response = self._get_status(host_uuid, session_id) logging.info("The response to the D-Bus caller: '%s'", response) return response @dbus.service.method( - "org.ArcticaProject.RWASupportSessionService", in_signature="i", out_signature="s" + "org.ArcticaProject.RWASupportSessionService", in_signature="si", out_signature="s" ) - def stop(self, pid: int) -> str: + def stop(self, host_uuid: str, session_id: int) -> str: """Stop a remote session. - :param pid: (Process) ID of session (D-Bus integer) + :param host_uuid: Host ID (D-Bus string) + :param session_id: Session ID (D-Bus integer) :return: Session status as JSON (D-Bus string) **Structure of returned JSON:** :: - {"id": <pid>, "status": "stopped"} + {"host_uuid": "<host_uuid>", "session_id": <session_id>, "status": "stopped"} """ - logging.info("D-Bus method call: %s(%d)", "stop", pid) + logging.info("D-Bus method call: %s(%s, %d)", "stop", host_uuid, session_id) + combined_id = combine(host_uuid, session_id) try: - session = self.sessions[pid] + session = self.sessions[combined_id] except KeyError: - logging.debug("D-Bus method stop(): sessions[%d] does not exist.", pid) - response = json.dumps({"pid": pid, "status": "stopped"}, sort_keys=True) + logging.debug("D-Bus method stop(): sessions[%s] does not exist.", combined_id) + response = json.dumps( + {"host_uuid": host_uuid, "session_id": session_id, "status": "stopped"}, sort_keys=True + ) logging.info("The response to the D-Bus caller: '%s'", response) return response session.stop() - response = json.dumps({"id": pid, "status": "stopped"}, sort_keys=True) + response = json.dumps( + {"host_uuid": host_uuid, "session_id": session_id, "status": "stopped"}, sort_keys=True + ) logging.info("The response to the D-Bus caller: '%s'", response) return response - def _get_status(self, pid: int) -> str: + def _get_status(self, host_uuid: str, session_id: int) -> str: + combined_id = combine(host_uuid, session_id) try: - session = self.sessions[pid] + session = self.sessions[combined_id] except KeyError: - logging.debug("_get_status(): self.sessions[%d] does not exist.", pid) - return json.dumps({"id": pid, "status": "dead"}, sort_keys=True) + logging.debug("_get_status(): self.sessions[%s] does not exist.", combined_id) + return json.dumps( + {"host_uuid": host_uuid, "session_id": session_id, "status": "dead"}, sort_keys=True + ) return json.dumps(session.status) def _ensure_update_service(self): @@ -427,13 +483,14 @@ class RWASupportSessionService(dbus.service.Object): self.update_thread = Thread(target=self._update_sessions) self.update_thread.start() - def _update_session(self, pid: int): + def _update_session(self, host_uuid: str, session_id: int): """Update the status of a session.""" + combined_id = combine(host_uuid, session_id) try: - session = self.sessions[pid] + session = self.sessions[combined_id] except KeyError: - logging.info(f"Update status for session #{pid} …") - logging.warning("Session #%d is dead.", pid) + logging.info(f"Update status for session #{session_id} on host {host_uuid} …") + logging.warning("Session %s is dead.", combined_id) return # Check if VNC process is still running @@ -441,23 +498,23 @@ class RWASupportSessionService(dbus.service.Object): if running: pass elif session.status_text == "stopped" and session.pid in self.sessions: - logging.info(f"Update status for session #{pid} …") - logging.warning("Session #%d is dead.", pid) + logging.info(f"Update status for session #{session_id} on host {host_uuid} …") + logging.warning("Session %s is dead.", combined_id) - del self.sessions[session.pid] + del self.sessions[combined_id] else: - logging.info(f"Update status for session #{pid} …") - logging.warning("VNC was stopped, so session #%d is dead.", pid) + logging.info(f"Update status for session #{session_id} on host {host_uuid} …") + logging.warning("VNC was stopped, so session %s is dead.", session.combined_id) session.stop() - del self.sessions[session.pid] + del self.sessions[combined_id] def _update_sessions(self): """Go through all running sessions and update their status using ``_update_session``.""" logging.info("Started update service for sessions.") while len(self.sessions.values()) > 0: for session in list(self.sessions.values()): - self._update_session(session.pid) + self._update_session(session.host_uuid, session.session_id) time.sleep(2) @@ -473,10 +530,12 @@ class RWASupportSessionService(dbus.service.Object): for session in self.sessions.values(): if session.session_id == session_id: r = session.trigger(data, method) - logging.info(f"Found matching session #{session.pid}") + logging.info( + f"Found matching session #{session.session_id} on host {session.host_uuid}: {r}" + ) return r - logging.warning(f"Given session ID does not exist!") + logging.warning("Given session ID does not exist!") return False def _stop_all(self): @@ -484,7 +543,7 @@ class RWASupportSessionService(dbus.service.Object): logging.info("Stop all sessions.") for session in list(self.sessions.values()): session.stop() - del self.sessions[session.pid] + del self.sessions[session.combined_id] def _stop_daemon(self): """Stop all sessions and this daemon.""" |