Apply skill-driven review fixes: security, hook isolation, tests, docs
Addresses findings from a 6-lens review against the project skills (defining-asset-contract, enforcing-plugin-contract, hardening-flask-config, integrating-plugin-hooks, pinning-flask-behavior, simplifying-python). Security (hardening-flask-config): - Load per-plugin COLLECTOR_API_KEY_<PLUGIN> from env in create_app. from_object only copies class attributes, so per-plugin keys (ADR-006) were dead in real deploys and silently fell back to the shared key. - EMPLOYEE_DB_USER/PASSWORD no longer default to root/rootpassword (no safe default for a secret; unset fails loud). Documented in .env.example + DEPLOY.md. - COLLECTOR_API_KEY + per-plugin + EMPLOYEE_DB_* added to .env.example/DEPLOY.md. Hook isolation (integrating-plugin-hooks): - collector _collector_plugins and dashboard get_navigation now re-raise in dev/test and log+isolate in prod, instead of silently swallowing a broken plugin hook. Plugin loader (enforcing-plugin-contract): - enable_plugin/install_plugin read dependencies+version from the manifest instead of instantiating the plugin class. - _register_plugin_components rejects a second plugin claiming an already-used api_prefix (reset per app in init_app). Tests (pinning-flask-behavior): - test_identifiers.py: gauge/maintenance round-trip on computer/printer/network create+update; per-type seed yields the 12 identifier keys. - contract tests for apply_collector_payload presence + schema-declarers-implement. - security tests for per-plugin key env loading + no employee-db password default. Docs/contract sync (defining-asset-contract): - PLUGIN-HOOKS.md documents apply_collector_payload; stale 0.2.0 -> 0.3.0. - ADR-006 documents apply_collector_payload + single-dispatch rationale. - ADR-001 enumerates the expanded shopdb.api import surface. Simplify (simplifying-python): - De-duplicate the 21-entry settings defaults: shared build_default_settings() used by both the /settings/seed route and the CLI (were drifting copies). - Remove dead AssetStatus import + redundant AssetType local import in computers plugin; comment the statusid=1 collector default. 153 tests pass (was 145), naming/style green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -36,10 +36,29 @@ The following are the public, versioned surface. Plugin authors may depend on th
|
||||
|
||||
- `AuditLog` API: `audit_log(action, entitytype, entityid, ...)` for plugins to record audit entries with consistent schema
|
||||
- `Setting` API: `plugin.get_setting(key)` and `plugin.set_setting(key, value)` for plugin-scoped config persisted via the core `Setting` model
|
||||
- `resolve_asset_position(asset)` for the documented position-resolution algorithm
|
||||
|
||||
#### Import surface (`shopdb.api`) - expanded in __contract_version__ 0.3.0
|
||||
|
||||
`shopdb.api` is the ONLY core module plugins may import (plus `shopdb.plugins.base`
|
||||
for the ABC). Deep imports of `shopdb.core.*`, `shopdb.extensions`, or
|
||||
`shopdb.utils.*` are contract violations enforced by the
|
||||
`test_plugins_only_import_contract_surface` test. The surface re-exports:
|
||||
|
||||
- Infrastructure: `db`, `cache`
|
||||
- Model bases: `BaseModel`, `AuditMixin`
|
||||
- Core models: `Asset`, `AssetType`, `AssetStatus`, `Vendor`, `Model`,
|
||||
`Communication`, `CommunicationType`, `Location`, `Setting`, `AuditLog`,
|
||||
`Application`, `AppVersion`, `OperatingSystem`
|
||||
- Responses: `success_response`, `error_response`, `paginated_response`, `ErrorCodes`
|
||||
- Pagination: `get_pagination_params`, `paginate_query`
|
||||
- Helpers: `audit_log`, `resolve_asset_position`; legacy `employee_connection`
|
||||
|
||||
Adding a name here is a minor (additive) contract change; removing one is major.
|
||||
|
||||
#### Plugin contract
|
||||
|
||||
- `BasePlugin` ABC and its hooks (search, navigation, dashboard, relationships, collector schema)
|
||||
- `BasePlugin` ABC and its hooks (search, navigation, dashboard, relationships, collector schema + `apply_collector_payload`)
|
||||
|
||||
#### Excluded from the contract for v1
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ This calls for a generalizable contract: any plugin that wants to accept externa
|
||||
|
||||
## Decision
|
||||
|
||||
`BasePlugin` gets one new optional hook:
|
||||
`BasePlugin` gets two new hooks (added in __contract_version__ 0.2.x -> the surface is carried at 0.3.0):
|
||||
|
||||
```python
|
||||
def get_collector_schema(self) -> Optional[dict]:
|
||||
@@ -29,9 +29,26 @@ def get_collector_schema(self) -> Optional[dict]:
|
||||
- 'fields': JSON Schema definitions for the rest of the payload.
|
||||
"""
|
||||
return None
|
||||
|
||||
def apply_collector_payload(self, payload: dict) -> dict:
|
||||
"""Idempotently upsert an asset from a validated collector payload.
|
||||
|
||||
Called by /api/collector/<pluginname> after identity validation.
|
||||
CONDITIONAL hook: required only when get_collector_schema returns
|
||||
non-None. Default raises NotImplementedError (the dispatcher returns
|
||||
500) so a schema-without-upsert fails loud. Returns a dict with at
|
||||
least 'action' ('created'|'updated'|'noop'), 'assetid', 'warnings'.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
```
|
||||
|
||||
Plugin loader auto-registers an endpoint at `/api/collector/<pluginname>` for each plugin returning a schema. Auth is API-key, separate from JWT. Per-plugin keys via env vars:
|
||||
The pairing (schema present => apply implemented) is enforced by the
|
||||
`test_schema_declaring_plugins_implement_apply` contract test.
|
||||
|
||||
A single dynamic dispatch route `/api/collector/<pluginname>` serves every
|
||||
plugin that returns a schema (rather than registering a blueprint per plugin),
|
||||
because Flask forbids `register_blueprint` after the first request and plugins
|
||||
can be enabled at runtime. Auth is API-key, separate from JWT. Per-plugin keys via env vars:
|
||||
|
||||
- `COLLECTOR_API_KEY_<PLUGINNAME>` (preferred, plugin-specific)
|
||||
- `COLLECTOR_API_KEY` (fallback, shared)
|
||||
@@ -125,7 +142,7 @@ Migration path:
|
||||
## References
|
||||
|
||||
- `shopdb/core/api/collector.py` (legacy endpoint to be removed)
|
||||
- `shopdb/plugins/base.py` (`get_collector_schema` hook to be added)
|
||||
- `shopdb/plugins/base.py` (`get_collector_schema` + `apply_collector_payload` hooks)
|
||||
- ADR-001 (asset model the collectors target)
|
||||
- ADR-002 (collector schema is part of plugin contract; changes to the hook signature are major bumps)
|
||||
- The PXE project (`/home/camp/projects/pxe/`) which feeds the computers collector
|
||||
|
||||
Reference in New Issue
Block a user