diff --git a/doc/modules/ROOT/pages/install.adoc b/doc/modules/ROOT/pages/install.adoc index 3b1ad3e..07455d8 100644 --- a/doc/modules/ROOT/pages/install.adoc +++ b/doc/modules/ROOT/pages/install.adoc @@ -38,7 +38,9 @@ image::install_blender_addons.jpg[Install Blender Add-ons] 6. Setup add-on preferences if you are using a version of *Skybrush Studio for Blender* other than the free community edition: - API Key:: Enter the API key received that is used when communicating with the Skybrush Studio server. + API Key:: If you have received an API Key for communicating with the Skybrush Studio server, paste it here. + + License file:: If you have received a License file for communicating with the Skybrush Studio server, give its full path here to use it as your API Key. Server URL:: Enter the URL of a dedicated Skybrush Studio server if you are using a dedicated server. @@ -50,11 +52,17 @@ To turn on online access, open Blender and go to Edit -> Preferences -> System, === 5. Install Skybrush Studio Server +==== 5.1 Cloud server for community users + If you are a community user, you do _not_ need to install the *Skybrush Studio Server* as you can rely on our cloud-based community server. -If you are using a paid, professional version of *Skybrush Studio*, you will receive a precompiled copy of *Skybrush Studio Server* and a corresponding license file upon purchase of the software. +==== 5.2 Cloud server for pro users + +If you are using a paid, professional version of *Skybrush Studio*, you will receive a license file upon purchase of the software. This license file shall be used as an API key that enables your pro features while using the cloud-based *Skybrush Studio Server* (see setup instructions above on how to use your License file as your API Key). + +==== 5.3 Local server for VIP users -After successful installation of *Skybrush Studio Server*, your licence file named `skybrush-studio-server.cml` has to be copied to the folder where you installed the server itself. You might need system administrator privileges to write into this folder. +Upon special agreement, you may also receive a precompiled copy of *Skybrush Studio Server* for local use. In this case, after successful installation, your license file named `skybrush-studio-server.cml` has to be copied to the folder where you installed the server itself. You might need system administrator privileges to write into this folder. The terminal log of *Skybrush Studio Server* will give you instant feedback about the presence and content of your currently used license file upon execution of the server. diff --git a/doc/modules/ROOT/pages/panels/safety_and_export/export.adoc b/doc/modules/ROOT/pages/panels/safety_and_export/export.adoc index c427209..d572867 100644 --- a/doc/modules/ROOT/pages/panels/safety_and_export/export.adoc +++ b/doc/modules/ROOT/pages/panels/safety_and_export/export.adoc @@ -55,7 +55,7 @@ The validation report starts with a summary of flight statistics and safety test .IMPORTANT **** -The validation .pdf is created by a local or remote instance of *Skybrush Studio Server* running in the background. As creating the validation report is resource intensive, access to this feature might be disabled on our public server and might be only available through a paid license option. If you need but do not have access, mailto:support@collmot.com[contact us] for obtaining the proper *Skybrush Studio Server* licence. +The validation .pdf is created by a local or remote instance of *Skybrush Studio Server* running in the background. As creating the validation report is resource intensive, access to this feature might be disabled on our public server and might be only available through a paid license option. If you need but do not have access, mailto:support@collmot.com[contact us] for obtaining the proper *Skybrush Studio Server* license. **** If you press the btn:[Export to validation .pdf] button, you have to choose the path and filename of your output file. There are also some parameters you can setup conveniently: diff --git a/src/modules/sbstudio/api/base.py b/src/modules/sbstudio/api/base.py index 1ea192f..0afc4ae 100644 --- a/src/modules/sbstudio/api/base.py +++ b/src/modules/sbstudio/api/base.py @@ -1,6 +1,7 @@ import json import re +from base64 import b64encode from contextlib import contextmanager from gzip import compress from http.client import HTTPResponse @@ -106,6 +107,9 @@ def save_to_file(self, filename: Path) -> None: _API_KEY_REGEXP = re.compile(r"^[a-zA-Z0-9-_.]*$") +_LICENSE_API_KEY_REGEXP = re.compile( + r"^License [A-Za-z0-9+/=]{4,}([A-Za-z0-9+/=]{2})*$" +) class SkybrushStudioAPI: @@ -126,14 +130,19 @@ def validate_api_key(key: str) -> str: Raises: ValueError: if the key cannot be a valid API key """ - if not _API_KEY_REGEXP.match(key): - raise ValueError("Invalid API key") + if key.startswith("License"): + if not _LICENSE_API_KEY_REGEXP.match(key): + raise ValueError("Invalid license-type API key") + else: + if not _API_KEY_REGEXP.match(key): + raise ValueError("Invalid API key") return key def __init__( self, url: str = COMMUNITY_SERVER_URL, api_key: Optional[str] = None, + license_file: Optional[str] = None, ): """Constructor. @@ -141,12 +150,22 @@ def __init__( url: the root URL of the Skybrush Studio API; defaults to the public online service api_key: the API key used to authenticate with the server + license_file: the path to a license file to be used as the API Key """ self._api_key = None self._root = None # type: ignore self._request_context = create_default_context() - self.api_key = api_key + if api_key and license_file: + raise SkybrushStudioAPIError( + "Cannot use API key and license file at the same time" + ) + + if license_file: + self.api_key = self._convert_license_file_to_api_key(license_file) + else: + self.api_key = api_key + self.url = url @property @@ -158,6 +177,26 @@ def api_key(self) -> Optional[str]: def api_key(self, value: Optional[str]) -> None: self._api_key = self.validate_api_key(value) if value else None + def _convert_license_file_to_api_key(self, file: str) -> str: + """Parses a license file and transforms it to an API key. + + Returns: + the license file parsed as an API key + + Raises: + ValueError: if the file cannot be read as an API key + """ + if not Path(file).exists(): + raise ValueError("License file does not exist") + + try: + with open(file, "rb") as fp: + api_key = f"License {b64encode(fp.read()).decode('ascii')}" + except Exception: + raise ValueError("Could not read license file") from None + + return api_key + @property def url(self) -> str: """The URL where the API can be accessed.""" diff --git a/src/modules/sbstudio/plugin/api.py b/src/modules/sbstudio/plugin/api.py index 3676ddc..6c12b11 100644 --- a/src/modules/sbstudio/plugin/api.py +++ b/src/modules/sbstudio/plugin/api.py @@ -1,3 +1,5 @@ +import logging + from contextlib import contextmanager from functools import lru_cache from typing import Iterator, Optional, TypeVar @@ -13,21 +15,35 @@ T = TypeVar("T") +############################################################################# +# configure logger + +log = logging.getLogger(__name__) + @lru_cache(maxsize=1) -def _get_api_from_url_and_key(url: str, key: str): - """Constructs a Skybrush Studio API object from a root URL and an API key. +def _get_api_from_url_and_key_or_license(url: str, key: str, license_file: str): + """Constructs a Skybrush Studio API object from a root URL and an API key + or a license file. Memoized so we do not need to re-construct the same instance as long as the user does not change the add-on settings. """ global _fallback_api_key - result = SkybrushStudioAPI() - - result.api_key = key or _fallback_api_key - if url: - result.url = url + try: + result = SkybrushStudioAPI( + api_key=key or (None if license_file else _fallback_api_key), + license_file=license_file or None, + ) + if url: + result.url = url + except ValueError as ex: + log.error(f"Could not initialize Skybrush Studio API: {str(ex)}") + raise + except Exception as ex: + log.error(f"Unhandled exception in Skybrush Studio API initialization: {ex!r}") + raise # This is bad practice, but the default installation of Blender does not find # the SSL certificates on macOS and there are reports about similar problems @@ -48,12 +64,14 @@ def get_api() -> SkybrushStudioAPI: api_key: str server_url: str + license_file: str prefs = get_preferences() api_key = str(prefs.api_key).strip() + license_file = str(prefs.license_file).strip() server_url = str(prefs.server_url).strip() - return _get_api_from_url_and_key(server_url, api_key) + return _get_api_from_url_and_key_or_license(server_url, api_key, license_file) @contextmanager diff --git a/src/modules/sbstudio/plugin/model/global_settings.py b/src/modules/sbstudio/plugin/model/global_settings.py index e1ba6e4..205936d 100644 --- a/src/modules/sbstudio/plugin/model/global_settings.py +++ b/src/modules/sbstudio/plugin/model/global_settings.py @@ -17,6 +17,12 @@ class DroneShowAddonGlobalSettings(AddonPreferences): bl_idname = "ui_skybrush_studio" + license_file = StringProperty( + name="License file", + description="Full path to the license file to be used as the API Key", + subtype="FILE_PATH", + ) + api_key = StringProperty( name="API Key", description="API Key that is used when communicating with the Skybrush Studio server", @@ -45,6 +51,7 @@ def draw(self, context): layout = self.layout layout.prop(self, "api_key") + layout.prop(self, "license_file") layout.prop(self, "server_url") row = layout.row()