Deprecated AppPlugin mixin and introduced octoprint.accesscontrol.appkey hook instead
Plugins implementing the mixin will automatically be migrated to providing the single mixin method as hook handler.
This commit is contained in:
parent
228fb21ceb
commit
592cad5b99
7 changed files with 108 additions and 59 deletions
|
|
@ -12,6 +12,31 @@ Available plugin hooks
|
|||
.. contents::
|
||||
:local:
|
||||
|
||||
.. _sec-plugins-hook-accesscontrol-appkey:
|
||||
|
||||
octoprint.accesscontrol.appkey
|
||||
------------------------------
|
||||
|
||||
.. py:function:: hook(*args, **kwargs)
|
||||
|
||||
By handling this hook plugins may register additional :ref:`App session key providers <sec-api-apps-sessionkey>`
|
||||
within the system.
|
||||
|
||||
Overrides this to return your additional app information to be used for validating app session keys. You'll
|
||||
need to return a list of 3-tuples of the format (id, version, public key).
|
||||
|
||||
The ``id`` should be the (unique) identifier of the app. Using a domain prefix might make sense here, e.g.
|
||||
``org.octoprint.example.MyApp``.
|
||||
|
||||
``version`` should be a string specifying the version of the app for which the public key is valid. You can
|
||||
provide the string ``any`` here, in which case the provided public key will be valid for all versions of the
|
||||
app for which no specific public key is defined.
|
||||
|
||||
Finally, the public key is expected to be provided as a PKCS1 string without newlines.
|
||||
|
||||
:return: A list of 3-tuples as described above
|
||||
:rtype: list
|
||||
|
||||
.. _sec-plugins-hook-comm-protocol-action:
|
||||
|
||||
octoprint.comm.protocol.action
|
||||
|
|
|
|||
|
|
@ -91,11 +91,3 @@ SlicerPlugin
|
|||
.. autoclass:: octoprint.plugin.SlicerPlugin
|
||||
:members:
|
||||
|
||||
.. _sec-plugins-mixins-appplugin:
|
||||
|
||||
AppPlugin
|
||||
---------
|
||||
|
||||
.. autoclass:: octoprint.plugin.AppPlugin
|
||||
:members:
|
||||
|
||||
|
|
|
|||
|
|
@ -31,8 +31,20 @@ from octoprint.util import deprecated
|
|||
# singleton
|
||||
_instance = None
|
||||
|
||||
def _validate_plugin(phase, plugin_info):
|
||||
if phase == "after_load":
|
||||
if plugin_info.implementation is not None and isinstance(plugin_info.implementation, AppPlugin):
|
||||
# transform app plugin into hook
|
||||
import warnings
|
||||
warnings.warn("{name} uses deprecated plugin mixin AppPlugin, use octoprint.accesscontrol.appkey hook instead".format(name=plugin_info.key), DeprecationWarning)
|
||||
|
||||
hooks = plugin_info.hooks
|
||||
if not "octoprint.accesscontrol.appkey" in hooks:
|
||||
hooks["octoprint.accesscontrol.appkey"] = plugin_info.implementation.get_additional_apps
|
||||
setattr(plugin_info.instance, PluginInfo.attr_hooks, hooks)
|
||||
|
||||
def plugin_manager(init=False, plugin_folders=None, plugin_types=None, plugin_entry_points=None, plugin_disabled_list=None,
|
||||
plugin_restart_needing_hooks=None, plugin_obsolete_hooks=None):
|
||||
plugin_restart_needing_hooks=None, plugin_obsolete_hooks=None, plugin_validators=None):
|
||||
"""
|
||||
Factory method for initially constructing and consecutively retrieving the :class:`~octoprint.plugin.core.PluginManager`
|
||||
singleton.
|
||||
|
|
@ -57,6 +69,7 @@ def plugin_manager(init=False, plugin_folders=None, plugin_types=None, plugin_en
|
|||
to logging handlers
|
||||
plugin_obsolete_hooks (list): A list of hooks that have been declared obsolete. Plugins implementing them will
|
||||
not be enabled since they might depend on functionality that is no longer available.
|
||||
plugin_validators (list): A list of additional plugin validators through which to process each plugin.
|
||||
|
||||
Returns:
|
||||
PluginManager: A fully initialized :class:`~octoprint.plugin.core.PluginManager` instance to be used for plugin
|
||||
|
|
@ -103,6 +116,10 @@ def plugin_manager(init=False, plugin_folders=None, plugin_types=None, plugin_en
|
|||
plugin_obsolete_hooks = [
|
||||
"octoprint.comm.protocol.gcode"
|
||||
]
|
||||
if plugin_validators is None:
|
||||
plugin_validators = [
|
||||
_validate_plugin
|
||||
]
|
||||
|
||||
_instance = PluginManager(plugin_folders,
|
||||
plugin_types,
|
||||
|
|
@ -110,7 +127,8 @@ def plugin_manager(init=False, plugin_folders=None, plugin_types=None, plugin_en
|
|||
logging_prefix="octoprint.plugins.",
|
||||
plugin_disabled_list=plugin_disabled_list,
|
||||
plugin_restart_needing_hooks=plugin_restart_needing_hooks,
|
||||
plugin_obsolete_hooks=plugin_obsolete_hooks)
|
||||
plugin_obsolete_hooks=plugin_obsolete_hooks,
|
||||
plugin_validators=plugin_validators)
|
||||
else:
|
||||
raise ValueError("Plugin Manager not initialized yet")
|
||||
return _instance
|
||||
|
|
|
|||
|
|
@ -135,36 +135,42 @@ class PluginInfo(object):
|
|||
self._url = url
|
||||
self._license = license
|
||||
|
||||
def validate(self):
|
||||
# if the plugin still uses __plugin_implementations__, log a deprecation warning and put the first
|
||||
# item into __plugin_implementation__
|
||||
if hasattr(self.instance, self.__class__.attr_implementations):
|
||||
if not hasattr(self.instance, self.__class__.attr_implementation):
|
||||
# deprecation warning
|
||||
import warnings
|
||||
warnings.warn("{name} uses deprecated control property __plugin_implementations__, use __plugin_implementation__ instead - only the first implementation of {name} will be recognized".format(name=self.key), DeprecationWarning)
|
||||
def validate(self, phase, additional_validators=None):
|
||||
if phase == "before_load":
|
||||
# if the plugin still uses __plugin_init__, log a deprecation warning and move it to __plugin_load__
|
||||
if hasattr(self.instance, self.__class__.attr_init):
|
||||
if not hasattr(self.instance, self.__class__.attr_load):
|
||||
# deprecation warning
|
||||
import warnings
|
||||
warnings.warn("{name} uses deprecated control property __plugin_init__, use __plugin_load__ instead".format(name=self.key), DeprecationWarning)
|
||||
|
||||
# put first item into __plugin_implementation__
|
||||
implementations = getattr(self.instance, self.__class__.attr_implementations)
|
||||
if len(implementations) > 0:
|
||||
setattr(self.instance, self.__class__.attr_implementation, implementations[0])
|
||||
# move it
|
||||
init = getattr(self.instance, self.__class__.attr_init)
|
||||
setattr(self.instance, self.__class__.attr_load, init)
|
||||
|
||||
# delete __plugin_implementations__
|
||||
delattr(self.instance, self.__class__.attr_implementations)
|
||||
# delete __plugin_init__
|
||||
delattr(self.instance, self.__class__.attr_init)
|
||||
|
||||
# if the plugin still uses __plugin_init__, log a deprecation warning and move it to __plugin_load__
|
||||
if hasattr(self.instance, self.__class__.attr_init):
|
||||
if not hasattr(self.instance, self.__class__.attr_load):
|
||||
# deprecation warning
|
||||
import warnings
|
||||
warnings.warn("{name} uses deprecated control property __plugin_init__, use __plugin_load__ instead".format(name=self.key), DeprecationWarning)
|
||||
elif phase == "after_load":
|
||||
# if the plugin still uses __plugin_implementations__, log a deprecation warning and put the first
|
||||
# item into __plugin_implementation__
|
||||
if hasattr(self.instance, self.__class__.attr_implementations):
|
||||
if not hasattr(self.instance, self.__class__.attr_implementation):
|
||||
# deprecation warning
|
||||
import warnings
|
||||
warnings.warn("{name} uses deprecated control property __plugin_implementations__, use __plugin_implementation__ instead - only the first implementation of {name} will be recognized".format(name=self.key), DeprecationWarning)
|
||||
|
||||
# move it
|
||||
init = getattr(self.instance, self.__class__.attr_init)
|
||||
setattr(self.instance, self.__class__.attr_load, init)
|
||||
# put first item into __plugin_implementation__
|
||||
implementations = getattr(self.instance, self.__class__.attr_implementations)
|
||||
if len(implementations) > 0:
|
||||
setattr(self.instance, self.__class__.attr_implementation, implementations[0])
|
||||
|
||||
# delete __plugin_init__
|
||||
delattr(self.instance, self.__class__.attr_init)
|
||||
# delete __plugin_implementations__
|
||||
delattr(self.instance, self.__class__.attr_implementations)
|
||||
|
||||
if additional_validators is not None:
|
||||
for validator in additional_validators:
|
||||
validator(phase, self)
|
||||
|
||||
def __str__(self):
|
||||
if self.version:
|
||||
|
|
@ -422,7 +428,7 @@ class PluginManager(object):
|
|||
It is able to discover plugins both through possible file system locations as well as customizable entry points.
|
||||
"""
|
||||
|
||||
def __init__(self, plugin_folders, plugin_types, plugin_entry_points, logging_prefix=None, plugin_disabled_list=None, plugin_restart_needing_hooks=None, plugin_obsolete_hooks=None):
|
||||
def __init__(self, plugin_folders, plugin_types, plugin_entry_points, logging_prefix=None, plugin_disabled_list=None, plugin_restart_needing_hooks=None, plugin_obsolete_hooks=None, plugin_validators=None):
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
if logging_prefix is None:
|
||||
|
|
@ -436,6 +442,7 @@ class PluginManager(object):
|
|||
self.plugin_disabled_list = plugin_disabled_list
|
||||
self.plugin_restart_needing_hooks = plugin_restart_needing_hooks
|
||||
self.plugin_obsolete_hooks = plugin_obsolete_hooks
|
||||
self.plugin_validators = plugin_validators
|
||||
self.logging_prefix = logging_prefix
|
||||
|
||||
self.enabled_plugins = dict()
|
||||
|
|
@ -645,8 +652,9 @@ class PluginManager(object):
|
|||
plugin = self.plugins[name]
|
||||
|
||||
try:
|
||||
plugin.validate("before_load", additional_validators=self.plugin_validators)
|
||||
plugin.load()
|
||||
plugin.validate()
|
||||
plugin.validate("after_load", additional_validators=self.plugin_validators)
|
||||
self.on_plugin_loaded(name, plugin)
|
||||
|
||||
plugin.loaded = True
|
||||
|
|
|
|||
|
|
@ -1038,25 +1038,13 @@ class AppPlugin(OctoPrintPlugin):
|
|||
"""
|
||||
Using the :class:`AppPlugin mixin` plugins may register additional :ref:`App session key providers <sec-api-apps-sessionkey>`
|
||||
within the system.
|
||||
|
||||
.. deprecated:: 1.2.0
|
||||
|
||||
Refer to the :ref:`octoprint.accesscontrol.appkey hook <sec-plugins-hook-accesscontrol-appkey>` instead.
|
||||
|
||||
"""
|
||||
|
||||
def get_additional_apps(self):
|
||||
"""
|
||||
Overrides this to return your additional app information to be used for validating app session keys. You'll
|
||||
need to return a :class:`list` of 3-tuples of the format (id, version, public key).
|
||||
|
||||
The ``id`` should be the (unique) identifier of the app. Using a domain prefix might make sense here, e.g.
|
||||
``org.octoprint.example.MyApp``.
|
||||
|
||||
``version`` should be a string specifying the version of the app for which the public key is valid. You can
|
||||
provide the string ``any`` here, in which case the provided public key will be valid for all versions of the
|
||||
app for which no specific public key is defined.
|
||||
|
||||
Finally, the public key is expected to be provided as a PKCS1 string without newlines.
|
||||
|
||||
Returns:
|
||||
list: A list of 3-tuples as described above.
|
||||
"""
|
||||
|
||||
return []
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ eventManager = None
|
|||
loginManager = None
|
||||
pluginManager = None
|
||||
appSessionManager = None
|
||||
pluginLifecycleManager = None
|
||||
|
||||
principals = Principal(app)
|
||||
admin_permission = Permission(RoleNeed("admin"))
|
||||
|
|
@ -138,6 +139,7 @@ class Server():
|
|||
global loginManager
|
||||
global pluginManager
|
||||
global appSessionManager
|
||||
global pluginLifecycleManager
|
||||
global debug
|
||||
|
||||
from tornado.ioloop import IOLoop
|
||||
|
|
@ -606,7 +608,7 @@ class Server():
|
|||
|
||||
def _setup_blueprints(self):
|
||||
from octoprint.server.api import api
|
||||
from octoprint.server.apps import apps
|
||||
from octoprint.server.apps import apps, clear_registered_app
|
||||
import octoprint.server.views
|
||||
|
||||
app.register_blueprint(api, url_prefix="/api")
|
||||
|
|
@ -618,6 +620,12 @@ class Server():
|
|||
# and register a blueprint for serving the static files of asset plugins which are not blueprint plugins themselves
|
||||
self._register_asset_plugins()
|
||||
|
||||
global pluginLifecycleManager
|
||||
def clear_apps(name, plugin):
|
||||
clear_registered_app()
|
||||
pluginLifecycleManager.add_callback("enabled", clear_apps)
|
||||
pluginLifecycleManager.add_callback("disabled", clear_apps)
|
||||
|
||||
def _register_blueprint_plugins(self):
|
||||
blueprint_plugins = octoprint.plugin.plugin_manager().get_implementations(octoprint.plugin.BlueprintPlugin)
|
||||
for plugin in blueprint_plugins:
|
||||
|
|
|
|||
|
|
@ -86,9 +86,15 @@ def _get_registered_apps():
|
|||
if not "enabled" in app_data:
|
||||
apps[app]["enabled"] = True
|
||||
|
||||
app_plugins = octoprint.server.pluginManager.get_implementations(octoprint.plugin.AppPlugin)
|
||||
for plugin in app_plugins:
|
||||
additional_apps = plugin.get_additional_apps()
|
||||
hooks = octoprint.server.pluginManager.get_hooks("octoprint.accesscontrol.appkey")
|
||||
for name, hook in hooks.items():
|
||||
try:
|
||||
additional_apps = hook()
|
||||
except:
|
||||
import logging
|
||||
logging.getLogger(__name__).exception("Error while retrieving additional appkeys from plugin {name}".format(**locals()))
|
||||
continue
|
||||
|
||||
any_version_enabled = dict()
|
||||
|
||||
for app_data in additional_apps:
|
||||
|
|
@ -117,4 +123,8 @@ def _get_registered_apps():
|
|||
)
|
||||
|
||||
__registered_apps = apps
|
||||
return apps
|
||||
return apps
|
||||
|
||||
def clear_registered_app():
|
||||
global __registered_apps
|
||||
__registered_apps = None
|
||||
|
|
|
|||
Loading…
Reference in a new issue