diff --git a/changelogs/fragments/6770.yml b/changelogs/fragments/6770.yml new file mode 100644 index 000000000000..19dee5d37b46 --- /dev/null +++ b/changelogs/fragments/6770.yml @@ -0,0 +1,2 @@ +security: +- [CVE-2024-33883] Bump ejs from `3.1.7` to `3.1.10` ([#6770](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6770)) \ No newline at end of file diff --git a/changelogs/fragments/6899.yml b/changelogs/fragments/6899.yml new file mode 100644 index 000000000000..fe930956760c --- /dev/null +++ b/changelogs/fragments/6899.yml @@ -0,0 +1,2 @@ +feat: +- Remove endpoint validation for create data source saved object API ([#6899](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6899)) \ No newline at end of file diff --git a/changelogs/fragments/6928.yml b/changelogs/fragments/6928.yml new file mode 100644 index 000000000000..66ba2e4d9e86 --- /dev/null +++ b/changelogs/fragments/6928.yml @@ -0,0 +1,2 @@ +feat: +- [MD]Use placeholder for data source credentials fields when export saved object ([#6928](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6928)) \ No newline at end of file diff --git a/config/opensearch_dashboards.yml b/config/opensearch_dashboards.yml index 3ebcfe77cd16..dbff317c6d18 100644 --- a/config/opensearch_dashboards.yml +++ b/config/opensearch_dashboards.yml @@ -320,6 +320,7 @@ # savedObjects.permission.enabled: true # Set the value to true to enable workspace feature +# Please note, workspace will not work with multi-tenancy. To enable workspace feature, you need to disable multi-tenancy first with `opensearch_security.multitenancy.enabled: false` # workspace.enabled: false # Optional settings to specify saved object types to be deleted during migration. @@ -338,4 +339,4 @@ # Set the backend roles in groups or users, whoever has the backend roles or exactly match the user ids defined in this config will be regard as dashboard admin. # Dashboard admin will have the access to all the workspaces(workspace.enabled: true) and objects inside OpenSearch Dashboards. # opensearchDashboards.dashboardAdmin.groups: ["dashboard_admin"] -# opensearchDashboards.dashboardAdmin.users: ["dashboard_admin"] \ No newline at end of file +# opensearchDashboards.dashboardAdmin.users: ["dashboard_admin"] diff --git a/packages/osd-plugin-generator/package.json b/packages/osd-plugin-generator/package.json index bab54de44149..59ece58cfed4 100644 --- a/packages/osd-plugin-generator/package.json +++ b/packages/osd-plugin-generator/package.json @@ -11,7 +11,7 @@ "dependencies": { "@osd/cross-platform": "1.0.0", "@osd/dev-utils": "1.0.0", - "ejs": "^3.1.7", + "ejs": "^3.1.10", "execa": "^4.0.2", "inquirer": "^7.3.3", "normalize-path": "^3.0.0", diff --git a/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts b/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts index da477604c029..bfca6f88c5c2 100644 --- a/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts +++ b/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts @@ -28,7 +28,10 @@ * under the License. */ -import { exportSavedObjectsToStream } from './get_sorted_objects_for_export'; +import { + DATA_SOURCE_CREDENTIALS_PLACEHOLDER, + exportSavedObjectsToStream, +} from './get_sorted_objects_for_export'; import { savedObjectsClientMock } from '../service/saved_objects_client.mock'; import { Readable } from 'stream'; import { createPromiseFromStreams, createConcatStream } from '../../utils/streams'; @@ -706,6 +709,50 @@ describe('getSortedObjectsForExport()', () => { ]); }); + test('modifies return results to update `credentials` of data-source to use placeholder', async () => { + const createDataSourceSavedObject = (id: string, auth: any) => ({ + id, + type: 'data-source', + attributes: { auth }, + references: [], + }); + + const dataSourceNoAuthInfo = { type: 'no_auth' }; + const dataSourceBasicAuthInfo = { + type: 'username_password', + credentials: { username: 'foo', password: 'bar' }, + }; + + const redactedDataSourceBasicAuthInfo = { + type: 'username_password', + credentials: { + username: DATA_SOURCE_CREDENTIALS_PLACEHOLDER, + password: DATA_SOURCE_CREDENTIALS_PLACEHOLDER, + }, + }; + + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [ + createDataSourceSavedObject('1', dataSourceNoAuthInfo), + createDataSourceSavedObject('2', dataSourceBasicAuthInfo), + ], + }); + const exportStream = await exportSavedObjectsToStream({ + exportSizeLimit: 10000, + savedObjectsClient, + objects: [ + { type: 'data-source', id: '1' }, + { type: 'data-source', id: '2' }, + ], + }); + const response = await readStreamToCompletion(exportStream); + expect(response).toEqual([ + createDataSourceSavedObject('1', dataSourceNoAuthInfo), + createDataSourceSavedObject('2', redactedDataSourceBasicAuthInfo), + expect.objectContaining({ exportedCount: 2 }), + ]); + }); + test('includes nested dependencies when passed in', async () => { savedObjectsClient.bulkGet.mockResolvedValueOnce({ saved_objects: [ diff --git a/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts b/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts index 17e9af3c3e1b..229de09de440 100644 --- a/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts +++ b/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts @@ -34,6 +34,8 @@ import { SavedObjectsClientContract, SavedObject, SavedObjectsBaseOptions } from import { fetchNestedDependencies } from './inject_nested_depdendencies'; import { sortObjects } from './sort_objects'; +export const DATA_SOURCE_CREDENTIALS_PLACEHOLDER = 'pleaseUpdateCredentials'; + /** * Options controlling the export operation. * @public @@ -185,10 +187,40 @@ export async function exportSavedObjectsToStream({ ({ namespaces, ...object }) => object ); + // update the credential fields from "data-source" saved object to use placeholder to avoid exporting sensitive information + const redactedObjectsWithoutCredentials = redactedObjects.map>((object) => { + if (object.type === 'data-source') { + const { auth, ...rest } = object.attributes as { + auth: { type: string; credentials?: any }; + }; + const hasCredentials = auth && auth.credentials; + const updatedCredentials = hasCredentials + ? Object.keys(auth.credentials).reduce((acc, key) => { + acc[key] = DATA_SOURCE_CREDENTIALS_PLACEHOLDER; + return acc; + }, {} as { [key: string]: any }) + : undefined; + return { + ...object, + attributes: { + ...rest, + auth: { + type: auth.type, + ...(hasCredentials && { credentials: updatedCredentials }), + }, + }, + }; + } + return object; + }); + const exportDetails: SavedObjectsExportResultDetails = { exportedCount: exportedObjects.length, missingRefCount: missingReferences.length, missingReferences, }; - return createListStream([...redactedObjects, ...(excludeExportDetails ? [] : [exportDetails])]); + return createListStream([ + ...redactedObjectsWithoutCredentials, + ...(excludeExportDetails ? [] : [exportDetails]), + ]); } diff --git a/src/plugins/data_source/server/data_source_service.mock.ts b/src/plugins/data_source/server/data_source_service.mock.ts deleted file mode 100644 index 58145bddb506..000000000000 --- a/src/plugins/data_source/server/data_source_service.mock.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ -// eslint-disable-next-line @osd/eslint/no-restricted-paths -import { opensearchClientMock } from '../../../../src/core/server/opensearch/client/mocks'; -import { DataSourceServiceSetup } from './data_source_service'; - -const dataSourceClient = opensearchClientMock.createInternalClient(); -const create = () => - (({ - getDataSourceClient: jest.fn(() => Promise.resolve(dataSourceClient)), - getDataSourceLegacyClient: jest.fn(), - } as unknown) as jest.Mocked); - -export const dataSourceServiceSetupMock = { create }; diff --git a/src/plugins/data_source/server/plugin.ts b/src/plugins/data_source/server/plugin.ts index 229adf2bbe7b..bbf5a89d1b53 100644 --- a/src/plugins/data_source/server/plugin.ts +++ b/src/plugins/data_source/server/plugin.ts @@ -61,26 +61,16 @@ export class DataSourcePlugin implements Plugin { const dataSourcePluginStart = selfStart as DataSourcePluginStart; return dataSourcePluginStart.getAuthenticationMethodRegistry(); }); - const auditTrailPromise = core.getStartServices().then(([coreStart]) => coreStart.auditTrail); - const customApiSchemaRegistryPromise = core.getStartServices().then(([, , selfStart]) => { - const dataSourcePluginStart = selfStart as DataSourcePluginStart; - return dataSourcePluginStart.getCustomApiSchemaRegistry(); - }); const dataSourceSavedObjectsClientWrapper = new DataSourceSavedObjectsClientWrapper( - dataSourceServiceSetup, cryptographyServiceSetup, this.logger.get('data-source-saved-objects-client-wrapper-factory'), authRegistryPromise, - customApiSchemaRegistryPromise, config.endpointDeniedIPs ); @@ -114,12 +104,20 @@ export class DataSourcePlugin implements Plugin coreStart.auditTrail); + + const dataSourceService: DataSourceServiceSetup = await this.dataSourceService.setup(config); + + const customApiSchemaRegistryPromise = core.getStartServices().then(([, , selfStart]) => { + const dataSourcePluginStart = selfStart as DataSourcePluginStart; + return dataSourcePluginStart.getCustomApiSchemaRegistry(); + }); // Register data source plugin context to route handler context core.http.registerRouteHandlerContext( 'dataSource', this.createDataSourceRouteHandlerContext( - dataSourceServiceSetup, + dataSourceService, cryptographyServiceSetup, this.logger, auditTrailPromise, @@ -131,14 +129,14 @@ export class DataSourcePlugin implements Plugin { @@ -37,23 +34,16 @@ describe('DataSourceSavedObjectsClientWrapper', () => { const cryptographyMock = cryptographyServiceSetupMock.create(); const logger = loggingSystemMock.createLogger(); const authRegistryPromise = Promise.resolve(authRegistry); - const customApiSchemaRegistry = new CustomApiSchemaRegistry(); - const customApiSchemaRegistryPromise = Promise.resolve(customApiSchemaRegistry); - const dataSourceServiceSetup = dataSourceServiceSetupMock.create(); - const requestMock = httpServerMock.createOpenSearchDashboardsRequest(); const wrapperInstance = new DataSourceSavedObjectsClientWrapper( - dataSourceServiceSetup, cryptographyMock, logger, - authRegistryPromise, - customApiSchemaRegistryPromise + authRegistryPromise ); - const mockedClient = savedObjectsClientMock.create(); const wrapperClient = wrapperInstance.wrapperFactory({ client: mockedClient, typeRegistry: requestHandlerContext.savedObjects.typeRegistry, - request: requestMock, + request: httpServerMock.createOpenSearchDashboardsRequest(), }); const getSavedObject = (savedObject: Partial) => { @@ -80,9 +70,6 @@ describe('DataSourceSavedObjectsClientWrapper', () => { describe('createWithCredentialsEncryption', () => { beforeEach(() => { mockedClient.create.mockClear(); - jest - .spyOn(DataSourceConnectionValidator.prototype, 'validate') - .mockResolvedValue(Promise.resolve() as Promise); }); it('should create data source when auth type is NO_AUTH', async () => { const mockDataSourceAttributesWithNoAuth = attributes({ @@ -90,7 +77,6 @@ describe('DataSourceSavedObjectsClientWrapper', () => { type: AuthType.NoAuth, }, }); - await wrapperClient.create( DATA_SOURCE_SAVED_OBJECT_TYPE, mockDataSourceAttributesWithNoAuth, @@ -205,23 +191,6 @@ describe('DataSourceSavedObjectsClientWrapper', () => { ).rejects.toThrowError(`Invalid auth type: 'not_in_registry': Bad Request`); }); - it('endpoint validator datasource client should be created with request as param', async () => { - const mockDataSourceAttributesWithNoAuth = attributes({ - auth: { - type: AuthType.NoAuth, - }, - }); - - await wrapperClient.create( - DATA_SOURCE_SAVED_OBJECT_TYPE, - mockDataSourceAttributesWithNoAuth, - {} - ); - expect(dataSourceServiceSetup.getDataSourceClient).toBeCalledWith( - expect.objectContaining({ request: requestMock }) - ); - }); - describe('createWithCredentialsEncryption: Error handling', () => { it('should throw error when title is empty', async () => { const mockDataSourceAttributes = attributes({ @@ -252,23 +221,6 @@ describe('DataSourceSavedObjectsClientWrapper', () => { ).rejects.toThrowError(`"endpoint" attribute is not valid or allowed`); }); - it('should throw error when endpoint is not valid OpenSearch endpoint', async () => { - const mockDataSourceAttributes = attributes({ - auth: { - type: AuthType.NoAuth, - }, - }); - jest - .spyOn(DataSourceConnectionValidator.prototype, 'validate') - .mockImplementationOnce(() => { - throw new Error(); - }); - - await expect( - wrapperClient.create(DATA_SOURCE_SAVED_OBJECT_TYPE, mockDataSourceAttributes, {}) - ).rejects.toThrowError(`endpoint is not valid OpenSearch endpoint: Bad Request`); - }); - it('should throw error when auth is not present', async () => { await expect( wrapperClient.create(DATA_SOURCE_SAVED_OBJECT_TYPE, attributes(), {}) diff --git a/src/plugins/data_source/server/saved_objects/data_source_saved_objects_client_wrapper.ts b/src/plugins/data_source/server/saved_objects/data_source_saved_objects_client_wrapper.ts index 35990f05777f..fc49983fed39 100644 --- a/src/plugins/data_source/server/saved_objects/data_source_saved_objects_client_wrapper.ts +++ b/src/plugins/data_source/server/saved_objects/data_source_saved_objects_client_wrapper.ts @@ -4,8 +4,6 @@ */ import { - OpenSearchClient, - OpenSearchDashboardsRequest, SavedObjectsBulkCreateObject, SavedObjectsBulkResponse, SavedObjectsBulkUpdateObject, @@ -28,9 +26,6 @@ import { import { EncryptionContext, CryptographyServiceSetup } from '../cryptography_service'; import { isValidURL } from '../util/endpoint_validator'; import { IAuthenticationMethodRegistry } from '../auth_registry'; -import { DataSourceServiceSetup } from '../data_source_service'; -import { CustomApiSchemaRegistry } from '../schema_registry'; -import { DataSourceConnectionValidator } from '../routes/data_source_connection_validator'; import { DATA_SOURCE_TITLE_LENGTH_LIMIT } from '../util/constants'; /** @@ -52,10 +47,7 @@ export class DataSourceSavedObjectsClientWrapper { return await wrapperOptions.client.create(type, attributes, options); } - const encryptedAttributes = await this.validateAndEncryptAttributes( - attributes, - wrapperOptions.request - ); + const encryptedAttributes = await this.validateAndEncryptAttributes(attributes); return await wrapperOptions.client.create(type, encryptedAttributes, options); }; @@ -74,7 +66,7 @@ export class DataSourceSavedObjectsClientWrapper { return { ...object, - attributes: await this.validateAndEncryptAttributes(attributes, wrapperOptions.request), + attributes: await this.validateAndEncryptAttributes(attributes), }; }) ); @@ -148,19 +140,14 @@ export class DataSourceSavedObjectsClientWrapper { }; constructor( - private dataSourcesService: DataSourceServiceSetup, private cryptography: CryptographyServiceSetup, private logger: Logger, private authRegistryPromise: Promise, - private customApiSchemaRegistryPromise: Promise, private endpointBlockedIps?: string[] ) {} - private async validateAndEncryptAttributes( - attributes: T, - request?: OpenSearchDashboardsRequest - ) { - await this.validateAttributes(attributes, request); + private async validateAndEncryptAttributes(attributes: T) { + await this.validateAttributes(attributes); const { endpoint, auth } = attributes; @@ -264,58 +251,31 @@ export class DataSourceSavedObjectsClientWrapper { } } - private async validateAttributes( - attributes: T, - request?: OpenSearchDashboardsRequest - ) { + private async validateAttributes(attributes: T) { const { title, endpoint, auth } = attributes; - this.validateTitle(title); - await this.validateEndpoint(endpoint, attributes as DataSourceAttributes, request); + this.validateEndpoint(endpoint); await this.validateAuth(auth); } - private validateTitle(title: string) { - if (!title.trim().length) { - throw SavedObjectsErrorHelpers.createBadRequestError( - '"title" attribute must be a non-empty string' - ); - } - - if (title.length > DATA_SOURCE_TITLE_LENGTH_LIMIT) { + private validateEndpoint(endpoint: string) { + if (!isValidURL(endpoint, this.endpointBlockedIps)) { throw SavedObjectsErrorHelpers.createBadRequestError( - `"title" attribute is limited to ${DATA_SOURCE_TITLE_LENGTH_LIMIT} characters` + '"endpoint" attribute is not valid or allowed' ); } } - private async validateEndpoint( - endpoint: string, - attributes: DataSourceAttributes, - request?: OpenSearchDashboardsRequest - ) { - if (!isValidURL(endpoint, this.endpointBlockedIps)) { + private validateTitle(title: string) { + if (!title.trim().length) { throw SavedObjectsErrorHelpers.createBadRequestError( - '"endpoint" attribute is not valid or allowed' + '"title" attribute must be a non-empty string' ); } - try { - const dataSourceClient: OpenSearchClient = await this.dataSourcesService.getDataSourceClient({ - savedObjects: {} as any, - cryptography: this.cryptography, - testClientDataSourceAttr: attributes as DataSourceAttributes, - request, - authRegistry: await this.authRegistryPromise, - customApiSchemaRegistryPromise: this.customApiSchemaRegistryPromise, - }); - - const dataSourceValidator = new DataSourceConnectionValidator(dataSourceClient, attributes); - await dataSourceValidator.validate(); - } catch (err: any) { - this.logger.error(err); + if (title.length > DATA_SOURCE_TITLE_LENGTH_LIMIT) { throw SavedObjectsErrorHelpers.createBadRequestError( - `endpoint is not valid OpenSearch endpoint` + `"title" attribute is limited to ${DATA_SOURCE_TITLE_LENGTH_LIMIT} characters` ); } } diff --git a/yarn.lock b/yarn.lock index 7c0641bd2181..ff9c7b670574 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1182,6 +1182,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.23.2": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12" + integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.22.15", "@babel/template@^7.3.3": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" @@ -6125,6 +6132,11 @@ compare-versions@^5.0.1: resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-5.0.3.tgz#a9b34fea217472650ef4a2651d905f42c28ebfd7" integrity sha512-4UZlZP8Z99MGEY+Ovg/uJxJuvoXuN4M6B3hKaiackiHrgzQFEe3diJi1mf1PNHbFujM7FvLrK2bpgIaImbtZ1A== +complex.js@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.1.1.tgz#0675dac8e464ec431fb2ab7d30f41d889fb25c31" + integrity sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg== + component-emitter@^1.2.1, component-emitter@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" @@ -6887,6 +6899,11 @@ decimal.js@^10.2.1: resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== +decimal.js@^10.4.3: + version "10.4.3" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" + integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== + decode-uri-component@^0.2.0: version "0.2.2" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" @@ -7389,10 +7406,10 @@ eachr@^4.5.0: dependencies: typechecker "^6.2.0" -ejs@^3.1.7: - version "3.1.7" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.7.tgz#c544d9c7f715783dd92f0bddcf73a59e6962d006" - integrity sha512-BIar7R6abbUxDA3bfXrO4DSgwo8I+fB5/1zgujl3HLLjwd6+9iOnrT+t3grn2qbk9vOgBubXOFwX2m9axoFaGw== +ejs@^3.1.10: + version "3.1.10" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== dependencies: jake "^10.8.5" @@ -7889,6 +7906,11 @@ escalade@^3.0.2, escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +escape-latex@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/escape-latex/-/escape-latex-1.2.0.tgz#07c03818cf7dac250cce517f4fda1b001ef2bca1" + integrity sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw== + escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" @@ -8925,6 +8947,11 @@ fp-ts@^2.3.1: resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.11.9.tgz#bbc204e0932954b59c98a282635754a4b624a05e" integrity sha512-GhYlNKkCOfdjp71ocdtyaQGoqCswEoWDJLRr+2jClnBBq2dnSOtd6QxmJdALq8UhfqCyZZ0f0lxadU4OhwY9nw== +fraction.js@4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.4.tgz#b2bac8249a610c3396106da97c5a71da75b94b1c" + integrity sha512-pwiTgt0Q7t+GHZA4yaLjObx4vXmmdcS0iSJ19o8d/goUGgItX9UZWKWNnLHehxviD8wU2IWRsnR8cD5+yOJP2Q== + fraction.js@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" @@ -11048,6 +11075,11 @@ jake@^10.8.5: filelist "^1.0.1" minimatch "^3.0.4" +javascript-natural-sort@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59" + integrity sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw== + jest-canvas-mock@^2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/jest-canvas-mock/-/jest-canvas-mock-2.5.1.tgz#81509af658ef485e9a1bf39c64e06761517bdbcb" @@ -12335,6 +12367,21 @@ markdown-it@^12.3.2: mdurl "^1.0.1" uc.micro "^1.0.5" +mathjs@^11.8.2: + version "11.12.0" + resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-11.12.0.tgz#e933e5941930d44763ddfc5bfb08b90059449b2c" + integrity sha512-UGhVw8rS1AyedyI55DGz9q1qZ0p98kyKPyc9vherBkoueLntPfKtPBh14x+V4cdUWK0NZV2TBwqRFlvadscSuw== + dependencies: + "@babel/runtime" "^7.23.2" + complex.js "^2.1.1" + decimal.js "^10.4.3" + escape-latex "^1.2.0" + fraction.js "4.3.4" + javascript-natural-sort "^0.7.1" + seedrandom "^3.0.5" + tiny-emitter "^2.1.0" + typed-function "^4.1.1" + mathml-tag-names@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" @@ -12660,11 +12707,6 @@ minipass@^3.0.0, minipass@^3.1.1: dependencies: yallist "^4.0.0" -minipass@^4.0.0: - version "4.2.8" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.8.tgz#f0010f64393ecfc1d1ccb5f582bcaf45f48e1a3a" - integrity sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ== - minipass@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" @@ -15670,6 +15712,11 @@ secure-json-parse@^2.4.0: resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.4.0.tgz#5aaeaaef85c7a417f76271a4f5b0cc3315ddca85" integrity sha512-Q5Z/97nbON5t/L/sH6mY2EacfjVGwrCcSi5D3btRO2GZ8pf1K1UN7Z9H5J57hjVU2Qzxr1xO+FmBhOvEkzCMmg== +seedrandom@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" + integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== + selenium-webdriver@^4.0.0-alpha.7: version "4.0.0-alpha.7" resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.0.0-alpha.7.tgz#e3879d8457fd7ad8e4424094b7dc0540d99e6797" @@ -16999,6 +17046,11 @@ timsort@~0.3.0: resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= +tiny-emitter@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" + integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== + tiny-invariant@^1.0.2, tiny-invariant@^1.0.6: version "1.2.0" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9" @@ -17385,6 +17437,11 @@ typed-array-length@^1.0.4: for-each "^0.3.3" is-typed-array "^1.1.9" +typed-function@^4.1.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-4.2.1.tgz#19aa51847aa2dea9ef5e7fb7641c060179a74426" + integrity sha512-EGjWssW7Tsk4DGfE+5yluuljS1OGYWiI1J6e8puZz9nTMM51Oug8CD5Zo4gWMsOhq5BI+1bF+rWTm4Vbj3ivRA== + typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"