Files
shopdb-flask/plugins/usb/plugin.py
cproudlock 7dfbe7bf8a
All checks were successful
CI / backend (push) Successful in 1m20s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
Add the get_permissions plugin hook (contract 0.10.0)
Plugins declare their own RBAC permissions instead of core accumulating
them: 36 permissions moved out of the core catalog into the 9 owning
plugins (core keeps the 19 its own blueprints enforce). The catalog is
resolved dynamically (core + enabled plugins) and feeds the roles grid,
the token scope picker and ceiling, and flask seed permissions;
installing or enabling a plugin seeds its permissions automatically. A
disabled plugin drops out of the assignable catalog while existing role
links keep working. New plugins - bundled or external - now bring their
permissions with zero core edits.

781 tests pass; live-verified with a machines.edit-scoped token.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 09:29:55 -04:00

122 lines
4.8 KiB
Python

"""USB plugin main class."""
import json
import logging
from pathlib import Path
from typing import List, Dict, Optional, Type
from flask import Flask, Blueprint
from shopdb.plugins.base import BasePlugin, PluginMeta
from shopdb.api import db
from .models import USBDevice, USBDeviceType, USBCheckout
from .api import usb_bp
logger = logging.getLogger(__name__)
class USBPlugin(BasePlugin):
"""
USB plugin - manages USB device tracking and checkouts.
Standalone plugin for tracking USB flash drives, external drives,
and other portable storage devices with checkout/checkin functionality.
"""
def __init__(self):
self._manifest = self._load_manifest()
def _load_manifest(self) -> Dict:
"""Load plugin manifest from JSON file."""
manifest_path = Path(__file__).parent / 'manifest.json'
if manifest_path.exists():
with open(manifest_path, 'r') as f:
return json.load(f)
return {}
@property
def meta(self) -> PluginMeta:
"""Return plugin metadata."""
return PluginMeta(
name=self._manifest.get('name', 'usb'),
version=self._manifest.get('version', '1.0.0'),
description=self._manifest.get('description', 'USB device checkout management'),
author=self._manifest.get('author', 'ShopDB Team'),
dependencies=self._manifest.get('dependencies', []),
core_version=self._manifest.get('core_version', '>=1.0.0'),
api_prefix=self._manifest.get('api_prefix', '/api/usb'),
)
def get_blueprint(self) -> Optional[Blueprint]:
"""Return Flask Blueprint with API routes."""
return usb_bp
def get_models(self) -> List[Type]:
"""Return list of SQLAlchemy model classes."""
return [USBDeviceType, USBDevice, USBCheckout]
def get_provisioning_note(self) -> Optional[Dict]:
return {
'mode_setting': 'usb_directory_mode',
'tables': ['usbdevicetypes', 'usbdevices', 'usbcheckouts'],
'note': ('Enabling this creates USB tracking tables in the shopdb '
'database (usbdevicetypes, usbdevices, usbcheckouts) for CMMC '
'removable-media check-in/out. Devices, check-in/out events, '
'and per-event log ids live here. Planned build-out: a USB-ID '
'standard, overdue-device email reminders, and DLP/Fabric '
'approval integration (see the captured design notes). A site '
'with an existing cmmc_usb database can use external mode '
'instead.'),
'docs': 'plugins/usb/README.md',
}
def get_config_schema(self) -> List[Dict]:
"""CMMC USB check-in/out database connection. Host/name/user are settings
the wizard can edit; the password stays in .env (emitted, not stored)."""
return [
{'key': 'cmmc_usb_db_host', 'label': 'USB DB host', 'type': 'text',
'secret': False, 'default': 'localhost',
'help': 'This DB must expose devices / checkinoutlog / users tables '
'(or views). See plugins/usb/README.md.'},
{'key': 'cmmc_usb_db_name', 'label': 'USB DB name', 'type': 'text',
'secret': False, 'default': 'cmmc_usb'},
{'key': 'cmmc_usb_db_user', 'label': 'USB DB user', 'type': 'text',
'secret': False},
{'key': 'cmmc_usb_db_password', 'label': 'USB DB password', 'type': 'password',
'secret': True, 'envvar': 'CMMC_USB_DB_PASSWORD',
'help': 'Stored in .env, not the database. The wizard shows the line to paste.'},
]
def init_app(self, app: Flask, db_instance) -> None:
"""Initialize plugin with Flask app."""
logger.info(f"USB plugin initialized (v{self.meta.version})")
def on_install(self, app: Flask) -> None:
"""Called when plugin is installed."""
logger.info("USB plugin installed")
def on_uninstall(self, app: Flask) -> None:
"""Called when plugin is uninstalled."""
logger.info("USB plugin uninstalled")
def get_navigation_items(self) -> List[Dict]:
"""Return navigation menu items."""
return [
{
'name': 'USB Devices',
'icon': 'usb',
'route': '/usb',
'position': 45,
},
]
def get_permissions(self) -> List:
"""Return the RBAC permissions this plugin owns."""
return [
('usb.view', 'View USB devices', 'usb'),
('usb.create', 'Create USB devices', 'usb'),
('usb.edit', 'Edit USB devices', 'usb'),
('usb.delete', 'Delete USB devices', 'usb'),
]