mirror of https://github.com/grafana/grafana
Frontend Sandbox: Create a plugin sandbox enable registry. Use enable list instead of disable list (#94809)
* Use a enable configuration to enable frontend sandbox * Modify settings to load enableFrontendSandbox * Check for signature type * Update commment * Fix e2e tests for the frontend sandbox * Modify logic so a custom check function is used instead of a list of checks * Fixes flaky test * fix comment * Update comment * Empty commit * Empty commitpull/94894/head^2
parent
beac7de4df
commit
f248a55576
@ -1,128 +0,0 @@ |
|||||||
import panelSandboxDashboard from '../dashboards/PanelSandboxDashboard.json'; |
|
||||||
import { e2e } from '../utils'; |
|
||||||
|
|
||||||
const DASHBOARD_ID = 'c46b2460-16b7-42a5-82d1-b07fbf431950'; |
|
||||||
|
|
||||||
describe('Panel sandbox', () => { |
|
||||||
beforeEach(() => { |
|
||||||
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'), true); |
|
||||||
return e2e.flows.importDashboard(panelSandboxDashboard, 1000, true); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('Sandbox disabled', () => { |
|
||||||
beforeEach(() => { |
|
||||||
cy.window().then((win) => { |
|
||||||
win.localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=0'); |
|
||||||
}); |
|
||||||
cy.reload(); |
|
||||||
}); |
|
||||||
|
|
||||||
it('Add iframes to body', () => { |
|
||||||
// this button adds iframes to the body
|
|
||||||
cy.get('[data-testid="button-create-iframes"]').click(); |
|
||||||
|
|
||||||
const iframeIds = [ |
|
||||||
'createElementIframe', |
|
||||||
'innerHTMLIframe', |
|
||||||
'appendIframe', |
|
||||||
'prependIframe', |
|
||||||
'afterIframe', |
|
||||||
'beforeIframe', |
|
||||||
'outerHTMLIframe', |
|
||||||
'parseFromStringIframe', |
|
||||||
'insertBeforeIframe', |
|
||||||
'replaceChildIframe', |
|
||||||
]; |
|
||||||
iframeIds.forEach((id) => { |
|
||||||
cy.get(`#${id}`).should('exist'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
it('Reaches out of panel div', () => { |
|
||||||
// this button reaches out of the panel div and modifies the element dataset
|
|
||||||
cy.get('[data-testid="button-reach-out"]').click(); |
|
||||||
|
|
||||||
cy.get('[data-sandbox-test="true"]').should('exist'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('Reaches out of the panel editor', () => { |
|
||||||
e2e.flows.openDashboard({ |
|
||||||
uid: DASHBOARD_ID, |
|
||||||
queryParams: { |
|
||||||
editPanel: 1, |
|
||||||
}, |
|
||||||
}); |
|
||||||
|
|
||||||
cy.get('[data-testid="panel-editor-custom-editor-input"]').should('not.be.disabled'); |
|
||||||
cy.get('[data-testid="panel-editor-custom-editor-input"]').type('x', { force: true }); |
|
||||||
cy.get('[data-sandbox-test="panel-editor"]').should('exist'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('Sandbox enabled', () => { |
|
||||||
beforeEach(() => { |
|
||||||
cy.window().then((win) => { |
|
||||||
win.localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=1'); |
|
||||||
}); |
|
||||||
cy.reload(); |
|
||||||
}); |
|
||||||
|
|
||||||
it('Does not add iframes to body', () => { |
|
||||||
// this button adds 3 iframes to the body
|
|
||||||
cy.get('[data-testid="button-create-iframes"]').click(); |
|
||||||
cy.wait(100); // small delay to prevent false positives from too fast tests
|
|
||||||
|
|
||||||
const iframeIds = [ |
|
||||||
'createElementIframe', |
|
||||||
'innerHTMLIframe', |
|
||||||
'appendIframe', |
|
||||||
'prependIframe', |
|
||||||
'afterIframe', |
|
||||||
'beforeIframe', |
|
||||||
'outerHTMLIframe', |
|
||||||
'parseFromStringIframe', |
|
||||||
'insertBeforeIframe', |
|
||||||
'replaceChildIframe', |
|
||||||
]; |
|
||||||
iframeIds.forEach((id) => { |
|
||||||
cy.get(`#${id}`).should('not.exist'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
it('Does not reaches out of panel div', () => { |
|
||||||
// this button reaches out of the panel div and modifies the element dataset
|
|
||||||
cy.get('[data-testid="button-reach-out"]').click(); |
|
||||||
cy.wait(100); // small delay to prevent false positives from too fast tests
|
|
||||||
cy.get('[data-sandbox-test="true"]').should('not.exist'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('Does not Reaches out of the panel editor', () => { |
|
||||||
e2e.flows.openDashboard({ |
|
||||||
uid: DASHBOARD_ID, |
|
||||||
queryParams: { |
|
||||||
editPanel: 1, |
|
||||||
}, |
|
||||||
}); |
|
||||||
|
|
||||||
cy.get('[data-testid="panel-editor-custom-editor-input"]').should('not.be.disabled'); |
|
||||||
cy.get('[data-testid="panel-editor-custom-editor-input"]').type('x', { force: true }); |
|
||||||
cy.wait(100); // small delay to prevent false positives from too fast tests
|
|
||||||
cy.get('[data-sandbox-test="panel-editor"]').should('not.exist'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('Can access specific window global variables', () => { |
|
||||||
cy.get('[data-testid="button-test-globals"]').click(); |
|
||||||
cy.get('[data-sandbox-global="Prism"]').should('be.visible'); |
|
||||||
cy.get('[data-sandbox-global="jQuery"]').should('be.visible'); |
|
||||||
cy.get('[data-sandbox-global="location"]').should('be.visible'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
afterEach(() => { |
|
||||||
e2e.flows.revertAllChanges(); |
|
||||||
}); |
|
||||||
|
|
||||||
after(() => { |
|
||||||
return cy.clearCookies(); |
|
||||||
}); |
|
||||||
}); |
|
@ -1,71 +0,0 @@ |
|||||||
import { e2e } from '../utils'; |
|
||||||
|
|
||||||
const APP_ID = 'sandbox-app-test'; |
|
||||||
|
|
||||||
describe('Datasource sandbox', () => { |
|
||||||
before(() => { |
|
||||||
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'), true); |
|
||||||
cy.request({ |
|
||||||
url: `${Cypress.env('BASE_URL')}/api/plugins/${APP_ID}/settings`, |
|
||||||
method: 'POST', |
|
||||||
body: { |
|
||||||
enabled: true, |
|
||||||
}, |
|
||||||
}); |
|
||||||
}); |
|
||||||
beforeEach(() => { |
|
||||||
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'), true); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('App Page', () => { |
|
||||||
describe('Sandbox disabled', () => { |
|
||||||
beforeEach(() => { |
|
||||||
cy.window().then((win) => { |
|
||||||
win.localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=0'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
it('Loads the app page without the sandbox div wrapper', () => { |
|
||||||
cy.visit(`/a/${APP_ID}`); |
|
||||||
cy.wait(200); // wait to prevent false positives because cypress checks too fast
|
|
||||||
cy.get('div[data-plugin-sandbox="sandbox-app-test"]').should('not.exist'); |
|
||||||
cy.get('div[data-testid="sandbox-app-test-page-one"]').should('exist'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('Loads the app configuration without the sandbox div wrapper', () => { |
|
||||||
cy.visit(`/plugins/${APP_ID}`); |
|
||||||
cy.wait(200); // wait to prevent false positives because cypress checks too fast
|
|
||||||
cy.get('div[data-plugin-sandbox="sandbox-app-test"]').should('not.exist'); |
|
||||||
cy.get('div[data-testid="sandbox-app-test-config-page"]').should('exist'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('Sandbox enabled', () => { |
|
||||||
beforeEach(() => { |
|
||||||
cy.window().then((win) => { |
|
||||||
win.localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=1'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
it('Loads the app page with the sandbox div wrapper', () => { |
|
||||||
cy.visit(`/a/${APP_ID}`); |
|
||||||
cy.get('div[data-plugin-sandbox="sandbox-app-test"]').should('exist'); |
|
||||||
cy.get('div[data-testid="sandbox-app-test-page-one"]').should('exist'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('Loads the app configuration with the sandbox div wrapper', () => { |
|
||||||
cy.visit(`/plugins/${APP_ID}`); |
|
||||||
cy.get('div[data-plugin-sandbox="sandbox-app-test"]').should('exist'); |
|
||||||
cy.get('div[data-testid="sandbox-app-test-config-page"]').should('exist'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
afterEach(() => { |
|
||||||
e2e.flows.revertAllChanges(); |
|
||||||
}); |
|
||||||
|
|
||||||
after(() => { |
|
||||||
cy.clearCookies(); |
|
||||||
}); |
|
||||||
}); |
|
@ -1,155 +0,0 @@ |
|||||||
import { random } from 'lodash'; |
|
||||||
|
|
||||||
import { e2e } from '../utils'; |
|
||||||
|
|
||||||
const DATASOURCE_ID = 'sandbox-test-datasource'; |
|
||||||
let DATASOURCE_CONNECTION_ID = ''; |
|
||||||
const DATASOURCE_TYPED_NAME = 'SandboxDatasourceInstance'; |
|
||||||
|
|
||||||
describe('Datasource sandbox', () => { |
|
||||||
before(() => { |
|
||||||
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'), true); |
|
||||||
|
|
||||||
e2e.pages.AddDataSource.visit(); |
|
||||||
e2e.pages.AddDataSource.dataSourcePluginsV2('Sandbox datasource test plugin') |
|
||||||
.scrollIntoView() |
|
||||||
.should('be.visible') // prevents flakiness
|
|
||||||
.click(); |
|
||||||
e2e.pages.DataSource.name().clear(); |
|
||||||
e2e.pages.DataSource.name().type(DATASOURCE_TYPED_NAME); |
|
||||||
e2e.pages.DataSource.saveAndTest().click(); |
|
||||||
cy.url().then((url) => { |
|
||||||
const split = url.split('/'); |
|
||||||
DATASOURCE_CONNECTION_ID = split[split.length - 1]; |
|
||||||
}); |
|
||||||
}); |
|
||||||
beforeEach(() => { |
|
||||||
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'), true); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('Config Editor', () => { |
|
||||||
describe('Sandbox disabled', () => { |
|
||||||
beforeEach(() => { |
|
||||||
cy.window().then((win) => { |
|
||||||
win.localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=0'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
it('Should not render a sandbox wrapper around the datasource config editor', () => { |
|
||||||
e2e.pages.EditDataSource.visit(DATASOURCE_CONNECTION_ID); |
|
||||||
cy.wait(300); // wait to prevent false positives because cypress checks too fast
|
|
||||||
cy.get(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`).should('not.exist'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('Sandbox enabled', () => { |
|
||||||
beforeEach(() => { |
|
||||||
cy.window().then((win) => { |
|
||||||
win.localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=1'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
it('Should render a sandbox wrapper around the datasource config editor', () => { |
|
||||||
e2e.pages.EditDataSource.visit(DATASOURCE_CONNECTION_ID); |
|
||||||
cy.get(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`).should('exist'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('Should store values in jsonData and secureJsonData correctly', () => { |
|
||||||
e2e.pages.EditDataSource.visit(DATASOURCE_CONNECTION_ID); |
|
||||||
|
|
||||||
const valueToStore = 'test' + random(100); |
|
||||||
|
|
||||||
cy.get('[data-testid="sandbox-config-editor-query-input"]').should('not.be.disabled'); |
|
||||||
cy.get('[data-testid="sandbox-config-editor-query-input"]').type(valueToStore); |
|
||||||
cy.get('[data-testid="sandbox-config-editor-query-input"]').should('have.value', valueToStore); |
|
||||||
|
|
||||||
e2e.pages.DataSource.saveAndTest().click(); |
|
||||||
e2e.pages.DataSource.alert().should('exist').contains('Sandbox Success', {}); |
|
||||||
|
|
||||||
// validate the value was stored
|
|
||||||
e2e.pages.EditDataSource.visit(DATASOURCE_CONNECTION_ID); |
|
||||||
cy.get('[data-testid="sandbox-config-editor-query-input"]').should('not.be.disabled'); |
|
||||||
cy.get('[data-testid="sandbox-config-editor-query-input"]').should('have.value', valueToStore); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('Explore Page', () => { |
|
||||||
describe('Sandbox disabled', () => { |
|
||||||
beforeEach(() => { |
|
||||||
cy.window().then((win) => { |
|
||||||
win.localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=0'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
it('Should not wrap the query editor in a sandbox wrapper', () => { |
|
||||||
e2e.pages.Explore.visit(); |
|
||||||
e2e.components.DataSourcePicker.container().should('be.visible').click(); |
|
||||||
cy.contains(DATASOURCE_TYPED_NAME).scrollIntoView().should('be.visible').click(); |
|
||||||
// make sure the datasource was correctly selected and rendered
|
|
||||||
e2e.components.Breadcrumbs.breadcrumb(DATASOURCE_TYPED_NAME).should('be.visible'); |
|
||||||
|
|
||||||
cy.wait(300); // wait to prevent false positives because cypress checks too fast
|
|
||||||
cy.get(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`).should('not.exist'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('Should accept values when typed', () => { |
|
||||||
e2e.pages.Explore.visit(); |
|
||||||
e2e.components.DataSourcePicker.container().should('be.visible').click(); |
|
||||||
cy.contains(DATASOURCE_TYPED_NAME).scrollIntoView().should('be.visible').click(); |
|
||||||
|
|
||||||
// make sure the datasource was correctly selected and rendered
|
|
||||||
e2e.components.Breadcrumbs.breadcrumb(DATASOURCE_TYPED_NAME).should('be.visible'); |
|
||||||
|
|
||||||
const valueToType = 'test' + random(100); |
|
||||||
|
|
||||||
cy.get('[data-testid="sandbox-query-editor-query-input"]').should('not.be.disabled'); |
|
||||||
cy.get('[data-testid="sandbox-query-editor-query-input"]').type(valueToType); |
|
||||||
cy.get('[data-testid="sandbox-query-editor-query-input"]').should('have.value', valueToType); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('Sandbox enabled', () => { |
|
||||||
beforeEach(() => { |
|
||||||
cy.window().then((win) => { |
|
||||||
win.localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=1'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
it('Should wrap the query editor in a sandbox wrapper', () => { |
|
||||||
e2e.pages.Explore.visit(); |
|
||||||
e2e.components.DataSourcePicker.container().should('be.visible').click(); |
|
||||||
cy.contains(DATASOURCE_TYPED_NAME).scrollIntoView().should('be.visible').click(); |
|
||||||
// make sure the datasource was correctly selected and rendered
|
|
||||||
e2e.components.Breadcrumbs.breadcrumb(DATASOURCE_TYPED_NAME).should('be.visible'); |
|
||||||
|
|
||||||
cy.get(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`).should('exist'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('Should accept values when typed', () => { |
|
||||||
e2e.pages.Explore.visit(); |
|
||||||
e2e.components.DataSourcePicker.container().should('be.visible').click(); |
|
||||||
cy.contains(DATASOURCE_TYPED_NAME).scrollIntoView().should('be.visible').click(); |
|
||||||
|
|
||||||
// make sure the datasource was correctly selected and rendered
|
|
||||||
e2e.components.Breadcrumbs.breadcrumb(DATASOURCE_TYPED_NAME).should('be.visible'); |
|
||||||
|
|
||||||
const valueToType = 'test' + random(100); |
|
||||||
|
|
||||||
cy.get('[data-testid="sandbox-query-editor-query-input"]').should('not.be.disabled'); |
|
||||||
cy.get('[data-testid="sandbox-query-editor-query-input"]').type(valueToType); |
|
||||||
cy.get('[data-testid="sandbox-query-editor-query-input"]').should('have.value', valueToType); |
|
||||||
|
|
||||||
// typing the query editor should reflect in the url
|
|
||||||
cy.url().should('include', valueToType); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
afterEach(() => { |
|
||||||
e2e.flows.revertAllChanges(); |
|
||||||
}); |
|
||||||
|
|
||||||
after(() => { |
|
||||||
cy.clearCookies(); |
|
||||||
}); |
|
||||||
}); |
|
@ -0,0 +1,92 @@ |
|||||||
|
import { PluginMeta, PluginSignatureType } from '@grafana/data'; |
||||||
|
import { config } from '@grafana/runtime'; |
||||||
|
|
||||||
|
import { getPluginSettings } from '../pluginSettings'; |
||||||
|
|
||||||
|
import { |
||||||
|
shouldLoadPluginInFrontendSandbox, |
||||||
|
setSandboxEnabledCheck, |
||||||
|
isPluginFrontendSandboxEnabled, |
||||||
|
} from './sandbox_plugin_loader_registry'; |
||||||
|
|
||||||
|
jest.mock('@grafana/runtime', () => ({ |
||||||
|
config: { |
||||||
|
featureToggles: { pluginsFrontendSandbox: true }, |
||||||
|
buildInfo: { env: 'production' }, |
||||||
|
enableFrontendSandboxForPlugins: [], |
||||||
|
}, |
||||||
|
})); |
||||||
|
|
||||||
|
jest.mock('../pluginSettings', () => ({ |
||||||
|
getPluginSettings: jest.fn(), |
||||||
|
})); |
||||||
|
|
||||||
|
const getPluginSettingsMock = getPluginSettings as jest.MockedFunction<typeof getPluginSettings>; |
||||||
|
|
||||||
|
const fakePlugin: PluginMeta = { |
||||||
|
id: 'test-plugin', |
||||||
|
name: 'Test Plugin', |
||||||
|
} as PluginMeta; |
||||||
|
|
||||||
|
describe('Sandbox eligibility checks', () => { |
||||||
|
const originalNodeEnv = process.env.NODE_ENV; |
||||||
|
|
||||||
|
beforeEach(() => { |
||||||
|
jest.clearAllMocks(); |
||||||
|
config.enableFrontendSandboxForPlugins = []; |
||||||
|
config.featureToggles.pluginsFrontendSandbox = true; |
||||||
|
process.env.NODE_ENV = 'development'; |
||||||
|
}); |
||||||
|
|
||||||
|
afterEach(() => { |
||||||
|
process.env.NODE_ENV = originalNodeEnv; |
||||||
|
}); |
||||||
|
|
||||||
|
test('shouldLoadPluginInFrontendSandbox returns false for Angular plugins', async () => { |
||||||
|
const result = await shouldLoadPluginInFrontendSandbox({ isAngular: true, pluginId: 'test-plugin' }); |
||||||
|
expect(result).toBe(false); |
||||||
|
}); |
||||||
|
|
||||||
|
test('shouldLoadPluginInFrontendSandbox returns false when feature toggle is off', async () => { |
||||||
|
config.featureToggles.pluginsFrontendSandbox = false; |
||||||
|
const result = await shouldLoadPluginInFrontendSandbox({ pluginId: 'test-plugin' }); |
||||||
|
expect(result).toBe(false); |
||||||
|
}); |
||||||
|
|
||||||
|
test('shouldLoadPluginInFrontendSandbox returns false for Grafana-signed plugins', async () => { |
||||||
|
getPluginSettingsMock.mockResolvedValue({ ...fakePlugin, signatureType: PluginSignatureType.grafana }); |
||||||
|
const result = await shouldLoadPluginInFrontendSandbox({ pluginId: 'test-plugin' }); |
||||||
|
expect(result).toBe(false); |
||||||
|
}); |
||||||
|
|
||||||
|
test('shouldLoadPluginInFrontendSandbox returns true for eligible plugins in the list', async () => { |
||||||
|
getPluginSettingsMock.mockResolvedValue({ ...fakePlugin, signatureType: PluginSignatureType.community }); |
||||||
|
config.enableFrontendSandboxForPlugins = ['test-plugin']; |
||||||
|
const result = await shouldLoadPluginInFrontendSandbox({ pluginId: 'test-plugin' }); |
||||||
|
expect(result).toBe(true); |
||||||
|
}); |
||||||
|
|
||||||
|
test('isPluginFrontendSandboxEnabled returns false when plugin is not in the enabled list', async () => { |
||||||
|
config.enableFrontendSandboxForPlugins = ['other-plugin']; |
||||||
|
const result = await isPluginFrontendSandboxEnabled({ pluginId: 'test-plugin' }); |
||||||
|
expect(result).toBe(false); |
||||||
|
}); |
||||||
|
|
||||||
|
test('setSandboxEnabledCheck sets custom check function', async () => { |
||||||
|
const customCheck = jest.fn().mockResolvedValue(true); |
||||||
|
setSandboxEnabledCheck(customCheck); |
||||||
|
const result = await shouldLoadPluginInFrontendSandbox({ pluginId: 'test-plugin' }); |
||||||
|
expect(customCheck).toHaveBeenCalledWith({ pluginId: 'test-plugin' }); |
||||||
|
expect(result).toBe(true); |
||||||
|
}); |
||||||
|
|
||||||
|
test('setSandboxEnabledCheck has precedence over default', async () => { |
||||||
|
const customCheck = jest.fn().mockResolvedValue(false); |
||||||
|
setSandboxEnabledCheck(customCheck); |
||||||
|
// this should be ignored by the custom check
|
||||||
|
config.enableFrontendSandboxForPlugins = ['test-plugin']; |
||||||
|
const result = await shouldLoadPluginInFrontendSandbox({ pluginId: 'test-plugin' }); |
||||||
|
expect(customCheck).toHaveBeenCalledWith({ pluginId: 'test-plugin' }); |
||||||
|
expect(result).toBe(false); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,78 @@ |
|||||||
|
import { PluginSignatureType } from '@grafana/data'; |
||||||
|
import { config } from '@grafana/runtime'; |
||||||
|
|
||||||
|
import { getPluginSettings } from '../pluginSettings'; |
||||||
|
|
||||||
|
type SandboxEligibilityCheckParams = { |
||||||
|
isAngular?: boolean; |
||||||
|
pluginId: string; |
||||||
|
}; |
||||||
|
|
||||||
|
type SandboxEnabledCheck = (params: SandboxEligibilityCheckParams) => Promise<boolean>; |
||||||
|
|
||||||
|
/** |
||||||
|
* We allow core extensions to register their own |
||||||
|
* sandbox enabled checks. |
||||||
|
*/ |
||||||
|
let sandboxEnabledCheck: SandboxEnabledCheck = isPluginFrontendSandboxEnabled; |
||||||
|
|
||||||
|
export function setSandboxEnabledCheck(checker: SandboxEnabledCheck) { |
||||||
|
sandboxEnabledCheck = checker; |
||||||
|
} |
||||||
|
|
||||||
|
export async function shouldLoadPluginInFrontendSandbox({ |
||||||
|
isAngular, |
||||||
|
pluginId, |
||||||
|
}: SandboxEligibilityCheckParams): Promise<boolean> { |
||||||
|
// basic check if the plugin is eligible for the sandbox
|
||||||
|
if (!(await isPluginFrontendSandboxElegible({ isAngular, pluginId }))) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
return sandboxEnabledCheck({ isAngular, pluginId }); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This is a basic check that checks if the plugin is eligible to run in the sandbox. |
||||||
|
* It does not check if the plugin is actually enabled for the sandbox. |
||||||
|
*/ |
||||||
|
async function isPluginFrontendSandboxElegible({ |
||||||
|
isAngular, |
||||||
|
pluginId, |
||||||
|
}: SandboxEligibilityCheckParams): Promise<boolean> { |
||||||
|
// Only if the feature is not enabled no support for sandbox
|
||||||
|
if (!Boolean(config.featureToggles.pluginsFrontendSandbox)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
// no support for angular plugins
|
||||||
|
if (isAngular) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
// To fast-test and debug the sandbox in the browser (dev mode only).
|
||||||
|
const sandboxDisableQueryParam = location.search.includes('nosandbox') && config.buildInfo.env === 'development'; |
||||||
|
if (sandboxDisableQueryParam) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
// no sandbox in test mode. it often breaks e2e tests
|
||||||
|
if (process.env.NODE_ENV === 'test') { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
// don't run grafana-signed plugins in sandbox
|
||||||
|
const pluginMeta = await getPluginSettings(pluginId); |
||||||
|
if (pluginMeta.signatureType === PluginSignatureType.grafana) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Check if the plugin is enabled for the sandbox via configuration. |
||||||
|
*/ |
||||||
|
export async function isPluginFrontendSandboxEnabled({ pluginId }: SandboxEligibilityCheckParams): Promise<boolean> { |
||||||
|
return Boolean(config.enableFrontendSandboxForPlugins?.includes(pluginId)); |
||||||
|
} |
Loading…
Reference in new issue