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,
handler(newValue) {
if (!newValue) {
loadStyle();
this.$options.css && this.$css.loadStyle();
loadStyle(undefined, { nonce: this.$config?.csp?.nonce });
this.$options.css && this.$css.loadStyle(undefined, { nonce: this.$config?.csp?.nonce });
}
}
}
@ -395,7 +395,7 @@ export default {
this._hook('onCreated');
},
beforeMount() {
loadBaseStyle();
loadBaseStyle(undefined, { nonce: this.$config?.csp?.nonce });
this._loadGlobalStyles();
this._hook('onBeforeMount');
},
@ -435,7 +435,7 @@ export default {
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) {
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: {
globalPT() {
return ObjectUtils.getItemValue(this.$primevue.config.pt, { instance: this });
return ObjectUtils.getItemValue(this.$config.pt, { instance: this });
},
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() {
return this.unstyled !== undefined ? this.unstyled : this.$primevue.config.unstyled;
return this.unstyled !== undefined ? this.unstyled : this.$config.unstyled;
},
$params() {
return { instance: this, props: this.$props, state: this.$data, parentInstance: this.$parentInstance };
},
$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,
$el: $prevInstance['$el'] || undefined,
$css: { classes: undefined, inlineStyles: undefined, loadStyle: () => {}, ...options?.css },
$config: config,
/* computed instance variables */
defaultPT: config?.pt?.directives?.[name],
isUnstyled: el.unstyled !== undefined ? el.unstyled : config?.unstyled,
@ -78,8 +79,10 @@ const BaseDirective = {
handleHook('created', el, binding, vnode, prevVnode);
},
beforeMount: (el, binding, vnode, prevVnode) => {
loadBaseStyle();
!el.$instance?.isUnstyled && el.$instance?.$css?.loadStyle();
const config = binding?.instance?.$primevue?.config;
loadBaseStyle(undefined, { nonce: config?.csp?.nonce });
!el.$instance?.isUnstyled && el.$instance?.$css?.loadStyle(undefined, { nonce: config?.csp?.nonce });
handleHook('beforeMount', el, binding, vnode, prevVnode);
},
mounted: (el, binding, vnode, prevVnode) => {

View File

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

View File

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

View File

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

View File

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

View File

@ -1852,12 +1852,14 @@ export default {
createStyleElement() {
this.styleElement = document.createElement('style');
this.styleElement.type = 'text/css';
DomHandler.setAttribute(this.styleElement, 'nonce', this.$primevue?.config?.csp?.nonce);
document.head.appendChild(this.styleElement);
},
createResponsiveStyle() {
if (!this.responsiveStyleElement) {
this.responsiveStyleElement = document.createElement('style');
this.responsiveStyleElement.type = 'text/css';
DomHandler.setAttribute(this.responsiveStyleElement, 'nonce', this.$primevue?.config?.csp?.nonce);
document.head.appendChild(this.responsiveStyleElement);
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) {
this.styleElement = document.createElement('style');
this.styleElement.type = 'text/css';
DomHandler.setAttribute(this.styleElement, 'nonce', this.$primevue?.config?.csp?.nonce);
document.head.appendChild(this.styleElement);
let innerHTML = '';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -19,7 +19,7 @@ export function useStyle(css, options = {}) {
const styleRef = ref(null);
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 = () => {};
@ -27,18 +27,21 @@ export function useStyle(css, options = {}) {
const load = (_css, _options = {}) => {
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');
if (!styleRef.value.isConnected) {
cssRef.value = _css || css;
styleRef.value.type = 'text/css';
_id && (styleRef.value.id = _id);
media && (styleRef.value.media = media);
DomHandler.setAttributes(styleRef.value, {
type: 'text/css',
id: _id,
media,
nonce: _nonce
});
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);
}

View File

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

View File

@ -24,6 +24,7 @@ export declare class DomHandler {
static find(el: HTMLElement, selector: string): any[];
static findSingle(el: HTMLElement, selector: string): any;
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 getAttribute(el: HTMLElement, name: string): any;
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 {
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 UnstyledDoc from '@/doc/configuration/UnstyledDoc';
import ZIndexDoc from '@/doc/configuration/ZIndexDoc';
import NonceDoc from '@/doc/configuration/csp/NonceDoc';
import LocaleApiDoc from '@/doc/configuration/locale/LocaleApiDoc';
import RepositoryDoc from '@/doc/configuration/locale/RepositoryDoc';
import SetLocaleDoc from '@/doc/configuration/locale/SetLocaleDoc';
@ -60,6 +61,17 @@ export default {
label: 'ZIndex',
component: ZIndexDoc
},
{
id: 'csp',
label: 'Content Security Policy (CSP)',
children: [
{
id: 'nonce',
label: 'Nonce',
component: NonceDoc
}
]
},
{
id: 'locale',
label: 'Locale',