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:
Gina Häußge 2015-06-09 18:50:55 +02:00
parent 228fb21ceb
commit 592cad5b99
7 changed files with 108 additions and 59 deletions

View file

@ -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

View file

@ -91,11 +91,3 @@ SlicerPlugin
.. autoclass:: octoprint.plugin.SlicerPlugin
:members:
.. _sec-plugins-mixins-appplugin:
AppPlugin
---------
.. autoclass:: octoprint.plugin.AppPlugin
:members:

View file

@ -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

View file

@ -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

View file

@ -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 []

View file

@ -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:

View file

@ -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