Skip to content

Commit

Permalink
feat: Add the ability to deny specific plugins from loading
Browse files Browse the repository at this point in the history
As part of openwallet-foundation#1734, I found that the Revocation Notification V2 code
failed to properly run due to the fact that the V1 code was deleting the
DB objects before V2 could get to it. @dbluhm and I decided that adding
an option to deny specific plugins from loading was one of the better
solutions to the problem.

Before the plugin has been initialized, any plugins listed within
`--deny-plugins` will be unregistered to prevent them from initializing
and potentially interferring with other plugins that have conflicts.

Signed-off-by: Colton Wolkins (Indicio work address) <[email protected]>
  • Loading branch information
TheTechmage committed Apr 19, 2022
1 parent 67dc094 commit 7525af0
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 0 deletions.
17 changes: 17 additions & 0 deletions aries_cloudagent/config/argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,20 @@ def add_arguments(self, parser: ArgumentParser):
),
)

parser.add_argument(
"--deny-plugin",
dest="denied_plugins",
type=str,
action="append",
required=False,
metavar="<module>",
env_var="ACAPY_DENY_PLUGIN",
help=(
"Deny <module> plugin module from loading. Multiple "
"instances of this parameter can be specified."
),
)

parser.add_argument(
"--plugin-config",
dest="plugin_config",
Expand Down Expand Up @@ -611,6 +625,9 @@ def get_settings(self, args: Namespace) -> dict:
if args.external_plugins:
settings["external_plugins"] = args.external_plugins

if args.denied_plugins:
settings["denied_plugins"] = args.denied_plugins

if args.plugin_config:
with open(args.plugin_config, "r") as stream:
settings["plugin_config"] = yaml.safe_load(stream)
Expand Down
3 changes: 3 additions & 0 deletions aries_cloudagent/config/default_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,5 +136,8 @@ async def load_plugins(self, context: InjectionContext):
for plugin_path in self.settings.get("external_plugins", []):
plugin_registry.register_plugin(plugin_path)

for plugin_path in self.settings.get("deny_plugins", []):
plugin_registry.unregister_plugin(plugin_path)

# Register message protocols
await plugin_registry.init_context(context)
7 changes: 7 additions & 0 deletions aries_cloudagent/core/plugin_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,13 @@ def register_plugin(self, module_name: str) -> ModuleType:
# self._plugins[module_name] = mod
# return mod

def unregister_plugin(self, module_name: str):
"""Unregister a plugin module. MUST BE CALLED BEFORE INITIALIZATION."""
if module_name not in self._plugins:
return

del self._plugins[module_name]

def register_package(self, package_name: str) -> Sequence[ModuleType]:
"""Register all modules (sub-packages) under a given package name."""
try:
Expand Down
19 changes: 19 additions & 0 deletions aries_cloudagent/core/tests/test_plugin_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,25 @@ class MODULE:
]
assert self.registry.register_plugin("dummy") == obj

async def test_unregister_plugin_has_setup(self):
class MODULE:
setup = "present"

obj = MODULE()
with async_mock.patch.object(
ClassLoader, "load_module", async_mock.MagicMock()
) as load_module:
load_module.side_effect = [
obj, # module
None, # routes
None, # message types
None, # definition without versions attr
]
assert self.registry.register_plugin("dummy") == obj
assert "dummy" in self.registry._plugins.keys()
self.registry.unregister_plugin("dummy")
assert "dummy" not in self.registry._plugins.keys()

async def test_register_definitions_malformed(self):
class MODULE:
no_setup = "no setup attr"
Expand Down

0 comments on commit 7525af0

Please sign in to comment.