Files
grafana/public/app/features/plugins/importPanelPlugin.ts
Giuseppe Guerra 0db65d229e Plugins: Add Subresource Integrity checks (#93024)
* Plugins: Pass hashes for SRI to frontend

* Add SRI hashes to frontendsettings DTOs

* Add docstring

* TestSriHashes

* Fix typo

* Changed SriHashes to ModuleHash

* update loader_test compareOpts

* update ModuleHash error message

* Add TestModuleHash/no_module.js

* Add omitEmpty to moduleHash

* Add ModuleHash to api/plugins/${pluginId}/settings

* moved ModuleHash field

* feat(plugins): add moduleHash to bootData and plugin types

* feat(plugins): if moduleHash is available apply it to systemjs importmap

* Calculate ModuleHash for CDN provisioned plugins

* Add ModuleHash tests for TestCalculate

* adjust test case name

* removed .envrc

* Fix signature verification failing for internal plugins

* fix tests

* Add pluginsFilesystemSriChecks feature togglemk

* renamed FilesystemSriChecksEnabled

* refactor(plugin_loader): prefer extending type declaration over ts-error

* added a couple more tests

* Removed unused features

* Removed unused argument from signature.DefaultCalculator call

* Removed unused argument from bootstrap.DefaultConstructFunc

* Moved ModuleHash to pluginassets service

* update docstring

* lint

* Removed cdn dependency from manifest.Signature

* add tests

* fix extra parameters in tests

* "fix" tests

* removed outdated test

* removed unused cdn dependency in signature.DefaultCalculator

* reduce diff

* Cache returned values

* Add support for deeply nested plugins (more than 1 hierarchy level)

* simplify cache usage

* refactor TestService_ModuleHash_Cache

* removed unused testdata

* re-generate feature toggles

* use version for module hash cache

* Renamed feature toggle to pluginsSriChecks and use it for both cdn and filesystem

* Removed app/types/system-integrity.d.ts

* re-generate feature toggles

* re-generate feature toggles

* feat(plugins): put systemjs integrity hash behind feature flag

---------

Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com>
2024-10-04 14:55:09 +02:00

100 lines
2.8 KiB
TypeScript

import { ComponentType } from 'react';
import { PanelPlugin, PanelPluginMeta, PanelProps, PluginLoadingStrategy } from '@grafana/data';
import config from 'app/core/config';
import { getPanelPluginLoadError } from '../panel/components/PanelPluginError';
import { importPluginModule } from './plugin_loader';
const promiseCache: Record<string, Promise<PanelPlugin>> = {};
const panelPluginCache: Record<string, PanelPlugin> = {};
export function importPanelPlugin(id: string): Promise<PanelPlugin> {
const loaded = promiseCache[id];
if (loaded) {
return loaded;
}
const meta = getPanelPluginMeta(id);
if (!meta) {
throw new Error(`Plugin ${id} not found`);
}
promiseCache[id] = getPanelPlugin(meta);
if (id !== meta.type) {
promiseCache[meta.type] = promiseCache[id];
}
return promiseCache[id];
}
export function hasPanelPlugin(id: string): boolean {
return !!getPanelPluginMeta(id);
}
export function getPanelPluginMeta(id: string): PanelPluginMeta {
const v = config.panels[id];
if (!v) {
// Check alias values before failing
for (const p of Object.values(config.panels)) {
if (p.aliasIDs?.includes(id)) {
return p;
}
}
}
return v;
}
export function importPanelPluginFromMeta(meta: PanelPluginMeta): Promise<PanelPlugin> {
return getPanelPlugin(meta);
}
export function syncGetPanelPlugin(id: string): PanelPlugin | undefined {
return panelPluginCache[id];
}
function getPanelPlugin(meta: PanelPluginMeta): Promise<PanelPlugin> {
const fallbackLoadingStrategy = meta.loadingStrategy ?? PluginLoadingStrategy.fetch;
return importPluginModule({
path: meta.module,
version: meta.info?.version,
isAngular: meta.angular?.detected,
loadingStrategy: fallbackLoadingStrategy,
pluginId: meta.id,
moduleHash: meta.moduleHash,
})
.then((pluginExports) => {
if (pluginExports.plugin) {
return pluginExports.plugin;
} else if (pluginExports.PanelCtrl) {
const plugin = new PanelPlugin(null);
plugin.angularPanelCtrl = pluginExports.PanelCtrl;
return plugin;
}
throw new Error('missing export: plugin or PanelCtrl');
})
.then((plugin: PanelPlugin) => {
plugin.meta = meta;
panelPluginCache[meta.id] = plugin;
if (!plugin.panel && plugin.angularPanelCtrl) {
plugin.panel = getAngularPanelReactWrapper(plugin);
}
return plugin;
})
.catch((err) => {
// TODO, maybe a different error plugin
console.warn('Error loading panel plugin: ' + meta.id, err);
return getPanelPluginLoadError(meta, err);
});
}
let getAngularPanelReactWrapper = (plugin: PanelPlugin): ComponentType<PanelProps> | null => null;
export function setAngularPanelReactWrapper(wrapper: typeof getAngularPanelReactWrapper) {
getAngularPanelReactWrapper = wrapper;
}