Skip to content

Commit

Permalink
fix(module:core): resolve CSP errors (#8059)
Browse files Browse the repository at this point in the history
The `ConfigService` needs to insert a `style` tag when registering theme. These changes use
the new `CSP_NONCE` token to avoid CSP issues.
  • Loading branch information
arturovt committed Sep 18, 2023
1 parent 24bc5e9 commit 295b333
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 24 deletions.
15 changes: 11 additions & 4 deletions components/core/config/config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/

import { Inject, Injectable, Optional } from '@angular/core';
import { CSP_NONCE, Inject, Injectable, Optional } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { filter, mapTo } from 'rxjs/operators';

Expand All @@ -27,11 +27,18 @@ export class NzConfigService {
/** Global config holding property. */
private readonly config: NzConfig;

constructor(@Optional() @Inject(NZ_CONFIG) defaultConfig?: NzConfig) {
private readonly cspNonce?: string | null;

constructor(
@Optional() @Inject(NZ_CONFIG) defaultConfig?: NzConfig,
@Optional() @Inject(CSP_NONCE) cspNonce?: string | null
) {
this.config = defaultConfig || {};
this.cspNonce = cspNonce;

if (this.config.theme) {
// If theme is set with NZ_CONFIG, register theme to make sure css variables work
registerTheme(this.getConfig().prefixCls?.prefixCls || defaultPrefixCls, this.config.theme);
registerTheme(this.getConfig().prefixCls?.prefixCls || defaultPrefixCls, this.config.theme, cspNonce);
}
}

Expand All @@ -53,7 +60,7 @@ export class NzConfigService {
set<T extends NzConfigKey>(componentName: T, value: NzConfig[T]): void {
this.config[componentName] = { ...this.config[componentName], ...value };
if (componentName === 'theme' && this.config.theme) {
registerTheme(this.getConfig().prefixCls?.prefixCls || defaultPrefixCls, this.config.theme);
registerTheme(this.getConfig().prefixCls?.prefixCls || defaultPrefixCls, this.config.theme, this.cspNonce);
}
this.configUpdated$.next(componentName);
}
Expand Down
4 changes: 2 additions & 2 deletions components/core/config/css-variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,11 @@ export function getStyle(globalPrefixCls: string, theme: Theme): string {
`.trim();
}

export function registerTheme(globalPrefixCls: string, theme: Theme): void {
export function registerTheme(globalPrefixCls: string, theme: Theme, cspNonce: string | null | undefined): void {
const style = getStyle(globalPrefixCls, theme);

if (canUseDom()) {
updateCSS(style, `${dynamicStyleMark}-dynamic-theme`);
updateCSS(style, `${dynamicStyleMark}-dynamic-theme`, { cspNonce });
} else {
warn(`NzConfigService: SSR do not support dynamic theme with css variables.`);
}
Expand Down
30 changes: 15 additions & 15 deletions components/core/util/dynamic-css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function getMark({ mark }: Options = {}): string {

interface Options {
attachTo?: Element;
csp?: { nonce?: string };
cspNonce?: string | null;
prepend?: boolean;
mark?: string;
}
Expand All @@ -35,24 +35,24 @@ function getContainer(option: Options): HTMLElement | Element {
return head || document.body;
}

export function injectCSS(css: string, option: Options = {}): HTMLStyleElement | null {
export function injectCSS(css: string, options: Options = {}): HTMLStyleElement | null {
if (!canUseDom()) {
return null;
}

const styleNode = document.createElement('style');
if (option.csp?.nonce) {
styleNode.nonce = option.csp?.nonce;
if (options.cspNonce) {
styleNode.nonce = options.cspNonce;
}
styleNode.innerHTML = css;

const container = getContainer(option);
const container = getContainer(options);
const { firstChild } = container;

if (option.prepend && container.prepend) {
if (options.prepend && container.prepend) {
// Use `prepend` first
container.prepend(styleNode);
} else if (option.prepend && firstChild) {
} else if (options.prepend && firstChild) {
// Fallback to `insertBefore` like IE not support `prepend`
container.insertBefore(styleNode, firstChild);
} else {
Expand All @@ -78,23 +78,23 @@ export function removeCSS(key: string, option: Options = {}): void {
existNode?.parentNode?.removeChild(existNode);
}

export function updateCSS(css: string, key: string, option: Options = {}): HTMLStyleElement | null {
const container = getContainer(option);
export function updateCSS(css: string, key: string, options: Options = {}): HTMLStyleElement | null {
const container = getContainer(options);

// Get real parent
if (!containerCache.has(container)) {
const placeholderStyle = injectCSS('', option);
const placeholderStyle = injectCSS('', options);
// @ts-ignore
const { parentNode } = placeholderStyle;
containerCache.set(container, parentNode);
parentNode.removeChild(placeholderStyle);
}

const existNode = findExistNode(key, option);
const existNode = findExistNode(key, options);

if (existNode) {
if (option.csp?.nonce && existNode.nonce !== option.csp?.nonce) {
existNode.nonce = option.csp?.nonce;
if (options.cspNonce && existNode.nonce !== options.cspNonce) {
existNode.nonce = options.cspNonce;
}

if (existNode.innerHTML !== css) {
Expand All @@ -104,7 +104,7 @@ export function updateCSS(css: string, key: string, option: Options = {}): HTMLS
return existNode;
}

const newNode = injectCSS(css, option);
newNode?.setAttribute(getMark(option), key);
const newNode = injectCSS(css, options);
newNode?.setAttribute(getMark(options), key);
return newNode;
}
6 changes: 5 additions & 1 deletion components/core/wave/nz-wave-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ export class NzWaveRenderer {
private triggerElement: HTMLElement,
private ngZone: NgZone,
private insertExtraNode: boolean,
private platformId: NzSafeAny
private platformId: NzSafeAny,
private cspNonce?: string | null
) {
this.platform = new Platform(this.platformId);
this.clickHandler = this.onClick.bind(this);
Expand Down Expand Up @@ -86,6 +87,9 @@ export class NzWaveRenderer {
if (this.isValidColor(waveColor)) {
if (!this.styleForPseudo) {
this.styleForPseudo = document.createElement('style');
if (this.cspNonce) {
this.styleForPseudo.nonce = this.cspNonce;
}
}

this.styleForPseudo.innerHTML = `
Expand Down
7 changes: 5 additions & 2 deletions components/core/wave/nz-wave.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

import {
CSP_NONCE,
Directive,
ElementRef,
Inject,
Expand Down Expand Up @@ -61,7 +62,8 @@ export class NzWaveDirective implements OnInit, OnDestroy {
private elementRef: ElementRef,
@Optional() @Inject(NZ_WAVE_GLOBAL_CONFIG) private config: NzWaveConfig,
@Optional() @Inject(ANIMATION_MODULE_TYPE) private animationType: string,
@Inject(PLATFORM_ID) private platformId: NzSafeAny
@Inject(PLATFORM_ID) private platformId: NzSafeAny,
@Optional() @Inject(CSP_NONCE) private cspNonce?: string | null
) {
this.waveDisabled = this.isConfigDisabled();
}
Expand Down Expand Up @@ -93,7 +95,8 @@ export class NzWaveDirective implements OnInit, OnDestroy {
this.elementRef.nativeElement,
this.ngZone,
this.nzWaveExtraNode,
this.platformId
this.platformId,
this.cspNonce
);
}
}
Expand Down

0 comments on commit 295b333

Please sign in to comment.