一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
247 行
7.0 KiB
TypeScript
247 行
7.0 KiB
TypeScript
|
|
import validateUntrustedManifest from './validateUntrustedManifest';
|
|
import { PluginManifest } from '@joplin/lib/services/plugins/utils/types';
|
|
|
|
const originalManifests = {
|
|
'joplin-plugin.this.is.a.test': {
|
|
id: 'joplin-plugin.this.is.a.test',
|
|
_npm_package_name: 'joplin-plugin-this-is-a-test',
|
|
version: '0.0.1',
|
|
},
|
|
} as unknown as Record<string, PluginManifest>;
|
|
|
|
// Note: Most of the checks below have additional tests in other files.
|
|
// This test suite is primarily to ensure that the checks are present.
|
|
|
|
describe('validateUntrustedManifest', () => {
|
|
test('should only allow valid plugin IDs', () => {
|
|
const badManifest = {
|
|
id: 'bad',
|
|
_npm_package_name: 'joplin-plugin-test',
|
|
version: '1.2.34',
|
|
};
|
|
|
|
expect(
|
|
() => validateUntrustedManifest(badManifest as unknown as PluginManifest, originalManifests, { source: 'npm' }),
|
|
).toThrow('ID cannot be shorter than');
|
|
|
|
|
|
const goodManifest = {
|
|
...badManifest,
|
|
id: 'this.plugin.has.a.better.id',
|
|
};
|
|
|
|
expect(
|
|
() => validateUntrustedManifest(goodManifest as unknown as PluginManifest, originalManifests, { source: 'npm' }),
|
|
).not.toThrow();
|
|
});
|
|
|
|
test('should only allow valid plugin versions', () => {
|
|
const badManifest = {
|
|
id: 'joplin-plugin.this.is.a.test',
|
|
_npm_package_name: 'joplin-plugin-this-is-a-test',
|
|
version: 'bad!',
|
|
};
|
|
|
|
expect(
|
|
() => validateUntrustedManifest(badManifest as unknown as PluginManifest, originalManifests, { source: 'npm' }),
|
|
).toThrow();
|
|
|
|
|
|
const goodManifest = {
|
|
...badManifest,
|
|
version: '0.0.2',
|
|
};
|
|
|
|
expect(
|
|
() => validateUntrustedManifest(goodManifest as unknown as PluginManifest, originalManifests, { source: 'npm' }),
|
|
).not.toThrow();
|
|
});
|
|
|
|
test('should only allow valid plugin platforms', () => {
|
|
const badManifest = {
|
|
id: 'com.example.a-plugin-for-a-fake-platform',
|
|
_npm_package_name: 'joplin-plugin-plugin-for-an-invalid-version',
|
|
version: '1.2.3',
|
|
platforms: [3, 4, 5],
|
|
};
|
|
|
|
expect(
|
|
() => validateUntrustedManifest(badManifest as unknown as PluginManifest, originalManifests, { source: 'npm' }),
|
|
).toThrow();
|
|
|
|
const goodManifest = {
|
|
...badManifest,
|
|
platforms: ['mobile', 'desktop'],
|
|
};
|
|
|
|
expect(
|
|
() => validateUntrustedManifest(goodManifest as unknown as PluginManifest, originalManifests, { source: 'npm' }),
|
|
).not.toThrow();
|
|
});
|
|
|
|
test('should not allow plugin authors to mark their own plugins as recommended', () => {
|
|
const newManifest1 = {
|
|
id: 'joplin-plugin.this.is.another.test',
|
|
_recommended: true,
|
|
_npm_package_name: 'joplin-plugin-another-test',
|
|
version: '1.2.3',
|
|
};
|
|
|
|
expect(
|
|
() => validateUntrustedManifest(newManifest1 as unknown as PluginManifest, originalManifests, { source: 'npm' }),
|
|
).toThrow('mark itself as recommended');
|
|
|
|
// Should also throw for falsey values
|
|
const newManifest2 = {
|
|
id: 'joplin-plugin.yet.another.test',
|
|
_recommended: '',
|
|
_npm_package_name: 'another-test',
|
|
version: '1.2.3',
|
|
};
|
|
|
|
expect(
|
|
() => validateUntrustedManifest(newManifest2 as unknown as PluginManifest, originalManifests, { source: 'npm' }),
|
|
).toThrow('mark itself as recommended');
|
|
});
|
|
|
|
test('should not allow changing the NPM package for an existing plugin', () => {
|
|
const newManifest = {
|
|
id: 'joplin-plugin.this.is.a.test',
|
|
_npm_package_name: 'joplin-plugin-test',
|
|
version: '0.0.2',
|
|
};
|
|
|
|
expect(
|
|
() => validateUntrustedManifest(newManifest as unknown as PluginManifest, originalManifests, { source: 'npm' }),
|
|
).toThrow();
|
|
});
|
|
|
|
test('should allow the same npm package to register its first repository URL', () => {
|
|
const newManifest = {
|
|
id: 'joplin-plugin.this.is.a.test',
|
|
_npm_package_name: 'joplin-plugin-this-is-a-test',
|
|
repository_url: 'https://github.com/example/this-is-a-test',
|
|
version: '0.0.2',
|
|
};
|
|
|
|
expect(
|
|
() => validateUntrustedManifest(
|
|
newManifest as unknown as PluginManifest,
|
|
originalManifests,
|
|
{ source: 'npm' },
|
|
),
|
|
).not.toThrow();
|
|
});
|
|
|
|
test('should require the same npm package after repository ownership is registered', () => {
|
|
const manifestsWithRepository = {
|
|
'joplin-plugin.this.is.a.test': {
|
|
...originalManifests['joplin-plugin.this.is.a.test'],
|
|
repository_url: 'https://github.com/example/this-is-a-test',
|
|
},
|
|
};
|
|
const newManifest = {
|
|
id: 'joplin-plugin.this.is.a.test',
|
|
_npm_package_name: 'joplin-plugin-attacker',
|
|
repository_url: 'https://github.com/example/this-is-a-test',
|
|
version: '0.0.2',
|
|
};
|
|
|
|
expect(
|
|
() => validateUntrustedManifest(
|
|
newManifest as unknown as PluginManifest,
|
|
manifestsWithRepository,
|
|
{ source: 'npm' },
|
|
),
|
|
).toThrow('has already been published under npm package');
|
|
});
|
|
|
|
test('should not allow the npm package to change an established repository URL', () => {
|
|
const manifestsWithRepository = {
|
|
'joplin-plugin.this.is.a.test': {
|
|
...originalManifests['joplin-plugin.this.is.a.test'],
|
|
repository_url: 'https://github.com/example/this-is-a-test',
|
|
},
|
|
};
|
|
const newManifest = {
|
|
id: 'joplin-plugin.this.is.a.test',
|
|
_npm_package_name: 'joplin-plugin-this-is-a-test',
|
|
repository_url: 'https://github.com/attacker/this-is-a-test',
|
|
version: '0.0.2',
|
|
};
|
|
|
|
expect(
|
|
() => validateUntrustedManifest(
|
|
newManifest as unknown as PluginManifest,
|
|
manifestsWithRepository,
|
|
{ source: 'npm' },
|
|
),
|
|
).toThrow('has already been published under repository');
|
|
});
|
|
|
|
test('should reject an unverified repository migration for a legacy npm plugin', () => {
|
|
const newManifest = {
|
|
id: 'joplin-plugin.this.is.a.test',
|
|
repository_url: 'https://github.com/attacker/this-is-a-test',
|
|
version: '0.0.2',
|
|
};
|
|
|
|
expect(
|
|
() => validateUntrustedManifest(
|
|
newManifest as unknown as PluginManifest,
|
|
originalManifests,
|
|
{ source: 'repository' },
|
|
),
|
|
).toThrow('A maintainer must verify and register its repository URL');
|
|
});
|
|
|
|
test('should require a repository URL for repository submissions', () => {
|
|
const manifest = {
|
|
id: 'com.example.repository-plugin',
|
|
version: '1.0.0',
|
|
};
|
|
|
|
expect(
|
|
() => validateUntrustedManifest(
|
|
manifest as unknown as PluginManifest,
|
|
originalManifests,
|
|
{ source: 'repository' },
|
|
),
|
|
).toThrow('must specify repository_url');
|
|
});
|
|
|
|
test('should not accept an npm package name from a repository submission', () => {
|
|
const manifest = {
|
|
id: 'com.example.repository-plugin',
|
|
version: '1.0.0',
|
|
repository_url: 'https://github.com/example/repository-plugin',
|
|
_npm_package_name: 'copied-package-name',
|
|
};
|
|
|
|
expect(
|
|
() => validateUntrustedManifest(
|
|
manifest as unknown as PluginManifest,
|
|
originalManifests,
|
|
{ source: 'repository' },
|
|
),
|
|
).toThrow('cannot specify _npm_package_name');
|
|
});
|
|
|
|
test('should accept a new repository submission with a repository URL', () => {
|
|
const manifest = {
|
|
id: 'com.example.repository-plugin',
|
|
version: '1.0.0',
|
|
repository_url: 'https://github.com/example/repository-plugin',
|
|
};
|
|
|
|
expect(
|
|
() => validateUntrustedManifest(
|
|
manifest as unknown as PluginManifest,
|
|
originalManifests,
|
|
{ source: 'repository' },
|
|
),
|
|
).not.toThrow();
|
|
});
|
|
});
|