From 20f741651ba5a6ef0c8cbf39d11d0e8838f7059d Mon Sep 17 00:00:00 2001 From: Stamen Stoychev Date: Tue, 25 Mar 2025 10:54:00 +0200 Subject: [PATCH 1/3] fix(*): adding random function for unsecure context #15461 --- .../src/lib/grids/common/random.ts | 22 +++++++++++++++++++ .../lib/grids/filtering/excel-style/common.ts | 3 ++- .../src/lib/grids/grid-base.directive.ts | 3 ++- 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 projects/igniteui-angular/src/lib/grids/common/random.ts diff --git a/projects/igniteui-angular/src/lib/grids/common/random.ts b/projects/igniteui-angular/src/lib/grids/common/random.ts new file mode 100644 index 00000000000..ecd3beeb5d6 --- /dev/null +++ b/projects/igniteui-angular/src/lib/grids/common/random.ts @@ -0,0 +1,22 @@ +/** + * Use the function to get a random UUID string when secure context is not guaranteed making crypto.randomUUID unavailable. + * @returns A random UUID string. + */ +export function getUUID() { + if (typeof crypto.randomUUID === "function") { + return crypto.randomUUID(); + } + // Secure fallback using crypto.getRandomValues() + const bytes = new Uint8Array(16); + crypto.getRandomValues(bytes); + + // Set version (4) and variant (RFC 4122) + bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4 + bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant 1 + + return [...bytes] + .map((b, i) => + [4, 6, 8, 10].includes(i) ? `-${b.toString(16).padStart(2, "0")}` : b.toString(16).padStart(2, "0") + ) + .join(""); +} diff --git a/projects/igniteui-angular/src/lib/grids/filtering/excel-style/common.ts b/projects/igniteui-angular/src/lib/grids/filtering/excel-style/common.ts index d48448c1f46..31b38405266 100644 --- a/projects/igniteui-angular/src/lib/grids/filtering/excel-style/common.ts +++ b/projects/igniteui-angular/src/lib/grids/filtering/excel-style/common.ts @@ -1,6 +1,7 @@ import { isTree } from '../../../data-operations/expressions-tree-util'; import { FilteringLogic, IFilteringExpression } from '../../../data-operations/filtering-expression.interface'; import { IFilteringExpressionsTree } from '../../../data-operations/filtering-expressions-tree'; +import { getUUID } from '../../common/random'; /** * @hidden @internal @@ -30,7 +31,7 @@ export class ExpressionUI { constructor() { // Use IDs to identify expressions clearly and use to track them in template @for cycles. - this.expressionId = crypto.randomUUID(); + this.expressionId = getUUID(); } } diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts index 0b346c079b5..f729ec49ab9 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts @@ -179,6 +179,7 @@ import { IgxGridCellComponent } from './cell.component'; import { IgxGridValidationService } from './grid/grid-validation.service'; import { getCurrentResourceStrings } from '../core/i18n/resources'; import { isTree, recreateTreeFromFields } from '../data-operations/expressions-tree-util'; +import { getUUID } from './common/random'; interface IMatchInfoCache { row: any; @@ -3785,7 +3786,7 @@ export abstract class IgxGridBaseDirective implements GridType, const primaryColumn = this._columns.find(col => col.field === this.primaryKey); const idType = this.data.length ? this.resolveDataTypes(this.data[0][this.primaryKey]) : primaryColumn ? primaryColumn.dataType : 'string'; - return idType === 'string' ? crypto.randomUUID() : FAKE_ROW_ID--; + return idType === 'string' ? getUUID() : FAKE_ROW_ID--; } /** From 78ae75fce6bef6f6a9b0d9317da559788a294bfb Mon Sep 17 00:00:00 2001 From: Stamen Stoychev Date: Tue, 1 Apr 2025 14:59:26 +0300 Subject: [PATCH 2/3] chore(*): applying changes from review --- .../igniteui-angular/src/lib/grids/common/random.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/common/random.ts b/projects/igniteui-angular/src/lib/grids/common/random.ts index ecd3beeb5d6..4c7d0684653 100644 --- a/projects/igniteui-angular/src/lib/grids/common/random.ts +++ b/projects/igniteui-angular/src/lib/grids/common/random.ts @@ -2,8 +2,8 @@ * Use the function to get a random UUID string when secure context is not guaranteed making crypto.randomUUID unavailable. * @returns A random UUID string. */ -export function getUUID() { - if (typeof crypto.randomUUID === "function") { +export function getUUID(): `${string}-${string}-${string}-${string}-${string}` { + if (typeof crypto.randomUUID === 'function') { return crypto.randomUUID(); } // Secure fallback using crypto.getRandomValues() @@ -14,9 +14,6 @@ export function getUUID() { bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4 bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant 1 - return [...bytes] - .map((b, i) => - [4, 6, 8, 10].includes(i) ? `-${b.toString(16).padStart(2, "0")}` : b.toString(16).padStart(2, "0") - ) - .join(""); + const a = [...bytes].map((b) => b.toString(16).padStart(2, '0')).join(''); + return `${a.slice(0, 8)}-${a.slice(8, 12)}-${a.slice(12, 16)}-${a.slice(16, 20)}-${a.slice(20)}`; } From 3bb3d37e595b1e666e5403d017edbefaa1ba7267 Mon Sep 17 00:00:00 2001 From: Stamen Stoychev Date: Fri, 4 Apr 2025 15:17:49 +0300 Subject: [PATCH 3/3] fix(test): adding unit tests for new random func --- .../src/lib/grids/common/random.spec.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 projects/igniteui-angular/src/lib/grids/common/random.spec.ts diff --git a/projects/igniteui-angular/src/lib/grids/common/random.spec.ts b/projects/igniteui-angular/src/lib/grids/common/random.spec.ts new file mode 100644 index 00000000000..05da7a0e090 --- /dev/null +++ b/projects/igniteui-angular/src/lib/grids/common/random.spec.ts @@ -0,0 +1,26 @@ +import { getUUID } from './random'; + +describe('Random (crypto.randomUuid()) fallback unit tests', () => { + let originalRandomUuid = crypto.randomUUID; + + beforeAll(() => { + crypto.randomUUID = null; // Mock crypto.randomUUID to simulate a non-secure context + }); + + it('should generate a valid UUID', () => { + const uuid = getUUID(); + expect(uuid).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/); + }); + + it('should generate unique UUIDs', () => { + const uuids = new Set(); + for (let i = 0; i < 100; i++) { + uuids.add(getUUID()); + } + expect(uuids.size).toBe(100); // All UUIDs should be unique + }); + + afterAll(() => { + crypto.randomUUID = originalRandomUuid; // Restore the original function + }); +}); \ No newline at end of file