Fixed #4241 - Add Content Security Policy (CSP) config

pull/4258/head
mertsincan 2023-08-11 02:34:02 +01:00
parent 590504d137
commit c42f45455c
22 changed files with 92 additions and 21 deletions

View File

@ -381,8 +381,8 @@ export default {
immediate: true, immediate: true,
handler(newValue) { handler(newValue) {
if (!newValue) { if (!newValue) {
loadStyle(); loadStyle(undefined, { nonce: this.$config?.csp?.nonce });
this.$options.css && this.$css.loadStyle(); this.$options.css && this.$css.loadStyle(undefined, { nonce: this.$config?.csp?.nonce });
} }
} }
} }
@ -395,7 +395,7 @@ export default {
this._hook('onCreated'); this._hook('onCreated');
}, },
beforeMount() { beforeMount() {
loadBaseStyle(); loadBaseStyle(undefined, { nonce: this.$config?.csp?.nonce });
this._loadGlobalStyles(); this._loadGlobalStyles();
this._hook('onBeforeMount'); this._hook('onBeforeMount');
}, },
@ -435,7 +435,7 @@ export default {
const globalCSS = this._getOptionValue(this.globalPT, 'global.css', this.$params); const globalCSS = this._getOptionValue(this.globalPT, 'global.css', this.$params);
ObjectUtils.isNotEmpty(globalCSS) && loadGlobalStyle(globalCSS); ObjectUtils.isNotEmpty(globalCSS) && loadGlobalStyle(globalCSS, { nonce: this.$config?.csp?.nonce });
}, },
_getHostInstance(instance) { _getHostInstance(instance) {
return instance ? (this.$options.hostName ? (instance.$.type.name === this.$options.hostName ? instance : this._getHostInstance(instance.$parentInstance)) : instance.$parentInstance) : undefined; return instance ? (this.$options.hostName ? (instance.$.type.name === this.$options.hostName ? instance : this._getHostInstance(instance.$parentInstance)) : instance.$parentInstance) : undefined;
@ -497,19 +497,22 @@ export default {
}, },
computed: { computed: {
globalPT() { globalPT() {
return ObjectUtils.getItemValue(this.$primevue.config.pt, { instance: this }); return ObjectUtils.getItemValue(this.$config.pt, { instance: this });
}, },
defaultPT() { defaultPT() {
return this._getOptionValue(this.$primevue.config.pt, this.$options.hostName || this.$.type.name, { instance: this }) || this.globalPT; return this._getOptionValue(this.$config.pt, this.$options.hostName || this.$.type.name, { instance: this }) || this.globalPT;
}, },
isUnstyled() { isUnstyled() {
return this.unstyled !== undefined ? this.unstyled : this.$primevue.config.unstyled; return this.unstyled !== undefined ? this.unstyled : this.$config.unstyled;
}, },
$params() { $params() {
return { instance: this, props: this.$props, state: this.$data, parentInstance: this.$parentInstance }; return { instance: this, props: this.$props, state: this.$data, parentInstance: this.$parentInstance };
}, },
$css() { $css() {
return { classes: undefined, inlineStyles: undefined, loadStyle: () => {}, loadCustomStyle: () => {}, ...(this._getHostInstance(this) || {}).$css, ...this.$options.css }; return { classes: undefined, inlineStyles: undefined, loadStyle: () => {}, loadCustomStyle: () => {}, ...(this._getHostInstance(this) || {}).$css, ...this.$options.css };
},
$config() {
return this.$primevue?.config;
} }
} }
}; };

View File

@ -57,6 +57,7 @@ const BaseDirective = {
$binding: binding, $binding: binding,
$el: $prevInstance['$el'] || undefined, $el: $prevInstance['$el'] || undefined,
$css: { classes: undefined, inlineStyles: undefined, loadStyle: () => {}, ...options?.css }, $css: { classes: undefined, inlineStyles: undefined, loadStyle: () => {}, ...options?.css },
$config: config,
/* computed instance variables */ /* computed instance variables */
defaultPT: config?.pt?.directives?.[name], defaultPT: config?.pt?.directives?.[name],
isUnstyled: el.unstyled !== undefined ? el.unstyled : config?.unstyled, isUnstyled: el.unstyled !== undefined ? el.unstyled : config?.unstyled,
@ -78,8 +79,10 @@ const BaseDirective = {
handleHook('created', el, binding, vnode, prevVnode); handleHook('created', el, binding, vnode, prevVnode);
}, },
beforeMount: (el, binding, vnode, prevVnode) => { beforeMount: (el, binding, vnode, prevVnode) => {
loadBaseStyle(); const config = binding?.instance?.$primevue?.config;
!el.$instance?.isUnstyled && el.$instance?.$css?.loadStyle();
loadBaseStyle(undefined, { nonce: config?.csp?.nonce });
!el.$instance?.isUnstyled && el.$instance?.$css?.loadStyle(undefined, { nonce: config?.csp?.nonce });
handleHook('beforeMount', el, binding, vnode, prevVnode); handleHook('beforeMount', el, binding, vnode, prevVnode);
}, },
mounted: (el, binding, vnode, prevVnode) => { mounted: (el, binding, vnode, prevVnode) => {

View File

@ -2582,6 +2582,7 @@ export default {
if (!this.responsiveStyleElement) { if (!this.responsiveStyleElement) {
this.responsiveStyleElement = document.createElement('style'); this.responsiveStyleElement = document.createElement('style');
this.responsiveStyleElement.type = 'text/css'; this.responsiveStyleElement.type = 'text/css';
DomHandler.setAttribute(this.responsiveStyleElement, 'nonce', this.$primevue?.config?.csp?.nonce);
document.body.appendChild(this.responsiveStyleElement); document.body.appendChild(this.responsiveStyleElement);
} }

View File

@ -516,6 +516,7 @@ export default {
if (!this.carouselStyle) { if (!this.carouselStyle) {
this.carouselStyle = document.createElement('style'); this.carouselStyle = document.createElement('style');
this.carouselStyle.type = 'text/css'; this.carouselStyle.type = 'text/css';
DomHandler.setAttribute(this.carouselStyle, 'nonce', this.$primevue?.config?.csp?.nonce);
document.body.appendChild(this.carouselStyle); document.body.appendChild(this.carouselStyle);
} }

View File

@ -103,6 +103,7 @@ export interface PrimeVueConfiguration {
zIndex?: PrimeVueZIndexOptions; zIndex?: PrimeVueZIndexOptions;
pt?: PrimeVuePTOptions; pt?: PrimeVuePTOptions;
unstyled?: boolean; unstyled?: boolean;
csp?: PrimeVueCSPOptions;
} }
export interface PrimeVueZIndexOptions { export interface PrimeVueZIndexOptions {
@ -112,6 +113,10 @@ export interface PrimeVueZIndexOptions {
tooltip?: number; tooltip?: number;
} }
export interface PrimeVueCSPOptions {
nonce?: string;
}
export interface PrimeVuePTOptions { export interface PrimeVuePTOptions {
accordion?: DefaultPTOptions<AccordionPassThroughOptions>; accordion?: DefaultPTOptions<AccordionPassThroughOptions>;
accordiontab?: DefaultPTOptions<AccordionTabPassThroughOptions>; accordiontab?: DefaultPTOptions<AccordionTabPassThroughOptions>;

View File

@ -132,7 +132,10 @@ export const defaultOptions = {
tooltip: 1100 tooltip: 1100
}, },
pt: undefined, pt: undefined,
unstyled: false unstyled: false,
csp: {
nonce: undefined
}
}; };
const PrimeVueSymbol = Symbol(); const PrimeVueSymbol = Symbol();

View File

@ -1852,12 +1852,14 @@ export default {
createStyleElement() { createStyleElement() {
this.styleElement = document.createElement('style'); this.styleElement = document.createElement('style');
this.styleElement.type = 'text/css'; this.styleElement.type = 'text/css';
DomHandler.setAttribute(this.styleElement, 'nonce', this.$primevue?.config?.csp?.nonce);
document.head.appendChild(this.styleElement); document.head.appendChild(this.styleElement);
}, },
createResponsiveStyle() { createResponsiveStyle() {
if (!this.responsiveStyleElement) { if (!this.responsiveStyleElement) {
this.responsiveStyleElement = document.createElement('style'); this.responsiveStyleElement = document.createElement('style');
this.responsiveStyleElement.type = 'text/css'; this.responsiveStyleElement.type = 'text/css';
DomHandler.setAttribute(this.responsiveStyleElement, 'nonce', this.$primevue?.config?.csp?.nonce);
document.head.appendChild(this.responsiveStyleElement); document.head.appendChild(this.responsiveStyleElement);
let tableSelector = `.p-datatable-wrapper ${this.virtualScrollerDisabled ? '' : '> .p-virtualscroller'} > .p-datatable-table`; let tableSelector = `.p-datatable-wrapper ${this.virtualScrollerDisabled ? '' : '> .p-virtualscroller'} > .p-datatable-table`;

View File

@ -259,6 +259,7 @@ export default {
if (!this.styleElement && !this.isUnstyled) { if (!this.styleElement && !this.isUnstyled) {
this.styleElement = document.createElement('style'); this.styleElement = document.createElement('style');
this.styleElement.type = 'text/css'; this.styleElement.type = 'text/css';
DomHandler.setAttribute(this.styleElement, 'nonce', this.$primevue?.config?.csp?.nonce);
document.head.appendChild(this.styleElement); document.head.appendChild(this.styleElement);
let innerHTML = ''; let innerHTML = '';

View File

@ -956,7 +956,7 @@ const classes = {
content: 'p-editor-content' content: 'p-editor-content'
}; };
const { load: loadStyle } = useStyle(styles, { name: 'editor' }); const { load: loadStyle } = useStyle(styles, { name: 'editor', manual: true });
export default { export default {
name: 'BaseEditor', name: 'BaseEditor',

View File

@ -424,6 +424,7 @@ export default {
if (!this.thumbnailsStyle) { if (!this.thumbnailsStyle) {
this.thumbnailsStyle = document.createElement('style'); this.thumbnailsStyle = document.createElement('style');
this.thumbnailsStyle.type = 'text/css'; this.thumbnailsStyle.type = 'text/css';
DomHandler.setAttribute(this.thumbnailsStyle, 'nonce', this.$primevue?.config?.csp?.nonce);
document.body.appendChild(this.thumbnailsStyle); document.body.appendChild(this.thumbnailsStyle);
} }

View File

@ -482,6 +482,7 @@ export default {
this.$el.setAttribute(this.attributeSelector, ''); this.$el.setAttribute(this.attributeSelector, '');
this.styleElement = document.createElement('style'); this.styleElement = document.createElement('style');
this.styleElement.type = 'text/css'; this.styleElement.type = 'text/css';
DomHandler.setAttribute(this.styleElement, 'nonce', this.$primevue?.config?.csp?.nonce);
document.head.appendChild(this.styleElement); document.head.appendChild(this.styleElement);
let innerHTML = ` let innerHTML = `

View File

@ -246,6 +246,7 @@ export default {
if (!this.styleElement && !this.isUnstyled) { if (!this.styleElement && !this.isUnstyled) {
this.styleElement = document.createElement('style'); this.styleElement = document.createElement('style');
this.styleElement.type = 'text/css'; this.styleElement.type = 'text/css';
DomHandler.setAttribute(this.styleElement, 'nonce', this.$primevue?.config?.csp?.nonce);
document.head.appendChild(this.styleElement); document.head.appendChild(this.styleElement);
let innerHTML = ''; let innerHTML = '';

View File

@ -52,7 +52,7 @@
</template> </template>
<script> <script>
import { UniqueComponentId } from 'primevue/utils'; import { DomHandler, UniqueComponentId } from 'primevue/utils';
import BasePaginator from './BasePaginator.vue'; import BasePaginator from './BasePaginator.vue';
import CurrrentPageReport from './CurrentPageReport.vue'; import CurrrentPageReport from './CurrentPageReport.vue';
import FirstPageLink from './FirstPageLink.vue'; import FirstPageLink from './FirstPageLink.vue';
@ -144,6 +144,7 @@ export default {
if (this.hasBreakpoints() && !this.isUnstyled) { if (this.hasBreakpoints() && !this.isUnstyled) {
this.styleElement = document.createElement('style'); this.styleElement = document.createElement('style');
this.styleElement.type = 'text/css'; this.styleElement.type = 'text/css';
DomHandler.setAttribute(this.styleElement, 'nonce', this.$primevue?.config?.csp?.nonce);
document.head.appendChild(this.styleElement); document.head.appendChild(this.styleElement);
let innerHTML = ''; let innerHTML = '';

View File

@ -779,6 +779,7 @@ export default {
this.$el.setAttribute(this.attributeSelector, ''); this.$el.setAttribute(this.attributeSelector, '');
this.styleElement = document.createElement('style'); this.styleElement = document.createElement('style');
this.styleElement.type = 'text/css'; this.styleElement.type = 'text/css';
DomHandler.setAttribute(this.styleElement, 'nonce', this.$primevue?.config?.csp?.nonce);
document.head.appendChild(this.styleElement); document.head.appendChild(this.styleElement);
let innerHTML = ` let innerHTML = `

View File

@ -24,7 +24,7 @@
<script> <script>
import Portal from 'primevue/portal'; import Portal from 'primevue/portal';
import ToastEventBus from 'primevue/toasteventbus'; import ToastEventBus from 'primevue/toasteventbus';
import { ObjectUtils, UniqueComponentId, ZIndexUtils } from 'primevue/utils'; import { DomHandler, ObjectUtils, UniqueComponentId, ZIndexUtils } from 'primevue/utils';
import BaseToast from './BaseToast.vue'; import BaseToast from './BaseToast.vue';
import ToastMessage from './ToastMessage.vue'; import ToastMessage from './ToastMessage.vue';
@ -113,6 +113,7 @@ export default {
if (!this.styleElement && !this.isUnstyled) { if (!this.styleElement && !this.isUnstyled) {
this.styleElement = document.createElement('style'); this.styleElement = document.createElement('style');
this.styleElement.type = 'text/css'; this.styleElement.type = 'text/css';
DomHandler.setAttribute(this.styleElement, 'nonce', this.$primevue?.config?.csp?.nonce);
document.head.appendChild(this.styleElement); document.head.appendChild(this.styleElement);
let innerHTML = ''; let innerHTML = '';

View File

@ -5,6 +5,7 @@ export interface StyleOptions {
name?: string; name?: string;
id?: string; id?: string;
media?: string; media?: string;
nonce?: string;
} }
export declare function useStyle( export declare function useStyle(
@ -15,6 +16,6 @@ export declare function useStyle(
name: string; name: string;
css: any; css: any;
unload: () => void; unload: () => void;
load: () => void; load: (css?: string, options?: any) => void;
isLoaded: boolean; isLoaded: boolean;
}; };

View File

@ -19,7 +19,7 @@ export function useStyle(css, options = {}) {
const styleRef = ref(null); const styleRef = ref(null);
const defaultDocument = DomHandler.isClient() ? window.document : undefined; const defaultDocument = DomHandler.isClient() ? window.document : undefined;
const { document = defaultDocument, immediate = true, manual = false, name = `style_${++_id}`, id = undefined, media = undefined } = options; const { document = defaultDocument, immediate = true, manual = false, name = `style_${++_id}`, id = undefined, media = undefined, nonce = undefined } = options;
let stop = () => {}; let stop = () => {};
@ -27,18 +27,21 @@ export function useStyle(css, options = {}) {
const load = (_css, _options = {}) => { const load = (_css, _options = {}) => {
if (!document) return; if (!document) return;
const [_name, _id] = [_options.name || name, _options.id || id]; const [_name, _id, _nonce] = [_options.name || name, _options.id || id, _options.nonce || nonce];
styleRef.value = document.querySelector(`style[data-primevue-style-id="${_name}"]`) || document.getElementById(_id) || document.createElement('style'); styleRef.value = document.querySelector(`style[data-primevue-style-id="${_name}"]`) || document.getElementById(_id) || document.createElement('style');
if (!styleRef.value.isConnected) { if (!styleRef.value.isConnected) {
cssRef.value = _css || css; cssRef.value = _css || css;
styleRef.value.type = 'text/css'; DomHandler.setAttributes(styleRef.value, {
_id && (styleRef.value.id = _id); type: 'text/css',
media && (styleRef.value.media = media); id: _id,
media,
nonce: _nonce
});
document.head.appendChild(styleRef.value); document.head.appendChild(styleRef.value);
name && styleRef.value.setAttribute('data-primevue-style-id', name); DomHandler.setAttribute(styleRef.value, 'data-primevue-style-id', name);
DomHandler.setAttributes(styleRef.value, _options); DomHandler.setAttributes(styleRef.value, _options);
} }

View File

@ -182,6 +182,12 @@ export default {
return undefined; return undefined;
}, },
setAttribute(element, attribute = '', value) {
if (element && value !== null && value !== undefined) {
element.setAttribute(attribute, value);
}
},
setAttributes(element, attributes = {}) { setAttributes(element, attributes = {}) {
if (element) { if (element) {
const computedStyles = (rule, value) => { const computedStyles = (rule, value) => {

View File

@ -24,6 +24,7 @@ export declare class DomHandler {
static find(el: HTMLElement, selector: string): any[]; static find(el: HTMLElement, selector: string): any[];
static findSingle(el: HTMLElement, selector: string): any; static findSingle(el: HTMLElement, selector: string): any;
static createElement(type: string, attributes: object, ...children: any): HTMLElement; static createElement(type: string, attributes: object, ...children: any): HTMLElement;
static setAttribute(el: HTMLElement, attribute: string, value: any): void;
static setAttributes(el: HTMLElement, attributes: object): void; static setAttributes(el: HTMLElement, attributes: object): void;
static getAttribute(el: HTMLElement, name: string): any; static getAttribute(el: HTMLElement, name: string): any;
static isAttributeEquals(el: HTMLElement, name: string, value: any): boolean; static isAttributeEquals(el: HTMLElement, name: string, value: any): boolean;

View File

@ -65,7 +65,7 @@ const styles = `
} }
`; `;
const { load: loadStyle } = useStyle(styles, { name: 'virtualscroller' }); const { load: loadStyle } = useStyle(styles, { name: 'virtualscroller', manual: true });
export default { export default {
name: 'BaseVirtualScroller', name: 'BaseVirtualScroller',

View File

@ -0,0 +1,23 @@
<template>
<DocSectionText v-bind="$attrs">
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP">nonce</a> value to use on dynamically generated style elements in core.</p>
<DocSectionCode :code="code1" hideToggleCode importCode hideCodeSandbox hideStackBlitz />
</DocSectionText>
</template>
<script>
export default {
data() {
return {
code1: {
basic: `app.use(PrimeVue, {
csp: {
nonce: '...'
}
});`
}
};
}
};
</script>

View File

@ -22,6 +22,7 @@ import PTDoc from '@/doc/configuration/PTDoc.vue';
import RippleDoc from '@/doc/configuration/RippleDoc'; import RippleDoc from '@/doc/configuration/RippleDoc';
import UnstyledDoc from '@/doc/configuration/UnstyledDoc'; import UnstyledDoc from '@/doc/configuration/UnstyledDoc';
import ZIndexDoc from '@/doc/configuration/ZIndexDoc'; import ZIndexDoc from '@/doc/configuration/ZIndexDoc';
import NonceDoc from '@/doc/configuration/csp/NonceDoc';
import LocaleApiDoc from '@/doc/configuration/locale/LocaleApiDoc'; import LocaleApiDoc from '@/doc/configuration/locale/LocaleApiDoc';
import RepositoryDoc from '@/doc/configuration/locale/RepositoryDoc'; import RepositoryDoc from '@/doc/configuration/locale/RepositoryDoc';
import SetLocaleDoc from '@/doc/configuration/locale/SetLocaleDoc'; import SetLocaleDoc from '@/doc/configuration/locale/SetLocaleDoc';
@ -60,6 +61,17 @@ export default {
label: 'ZIndex', label: 'ZIndex',
component: ZIndexDoc component: ZIndexDoc
}, },
{
id: 'csp',
label: 'Content Security Policy (CSP)',
children: [
{
id: 'nonce',
label: 'Nonce',
component: NonceDoc
}
]
},
{ {
id: 'locale', id: 'locale',
label: 'Locale', label: 'Locale',