diff options
author | Chocobozzz <me@florianbigard.com> | 2022-11-14 14:21:40 +0100 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2022-11-14 14:21:40 +0100 |
commit | 2198bb5a1981177b04dd94b2b1b6a90c5d7a5c25 (patch) | |
tree | 668365a50351c573949e066c1be440683795e262 | |
parent | b1934b7e9cdece7c0c38e05b0f905dc2ccab9167 (diff) | |
download | PeerTube-2198bb5a1981177b04dd94b2b1b6a90c5d7a5c25.tar.gz PeerTube-2198bb5a1981177b04dd94b2b1b6a90c5d7a5c25.tar.zst PeerTube-2198bb5a1981177b04dd94b2b1b6a90c5d7a5c25.zip |
Prevent XSS with ng-select
When using ng-option
See https://github.com/ng-select/ng-select/issues/1363
4 files changed, 28 insertions, 11 deletions
diff --git a/client/src/app/+admin/system/jobs/jobs.component.ts b/client/src/app/+admin/system/jobs/jobs.component.ts index d5da1b743..b8f3c3a68 100644 --- a/client/src/app/+admin/system/jobs/jobs.component.ts +++ b/client/src/app/+admin/system/jobs/jobs.component.ts | |||
@@ -2,6 +2,7 @@ import { SortMeta } from 'primeng/api' | |||
2 | import { Component, OnInit } from '@angular/core' | 2 | import { Component, OnInit } from '@angular/core' |
3 | import { Notifier, RestPagination, RestTable } from '@app/core' | 3 | import { Notifier, RestPagination, RestTable } from '@app/core' |
4 | import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage' | 4 | import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage' |
5 | import { escapeHTML } from '@shared/core-utils/renderer' | ||
5 | import { Job, JobState, JobType } from '@shared/models' | 6 | import { Job, JobState, JobType } from '@shared/models' |
6 | import { JobStateClient } from '../../../../types/job-state-client.type' | 7 | import { JobStateClient } from '../../../../types/job-state-client.type' |
7 | import { JobTypeClient } from '../../../../types/job-type-client.type' | 8 | import { JobTypeClient } from '../../../../types/job-type-client.type' |
@@ -142,7 +143,10 @@ export class JobsComponent extends RestTable implements OnInit { | |||
142 | 143 | ||
143 | private loadJobStateAndType () { | 144 | private loadJobStateAndType () { |
144 | const state = peertubeLocalStorage.getItem(JobsComponent.LOCAL_STORAGE_STATE) | 145 | const state = peertubeLocalStorage.getItem(JobsComponent.LOCAL_STORAGE_STATE) |
145 | if (state) this.jobState = state as JobState | 146 | |
147 | // FIXME: We use <ng-option> that doesn't escape HTML | ||
148 | // https://github.com/ng-select/ng-select/issues/1363 | ||
149 | if (state) this.jobState = escapeHTML(state) as JobState | ||
146 | 150 | ||
147 | const type = peertubeLocalStorage.getItem(JobsComponent.LOCAL_STORAGE_TYPE) | 151 | const type = peertubeLocalStorage.getItem(JobsComponent.LOCAL_STORAGE_TYPE) |
148 | if (type) this.jobType = type as JobType | 152 | if (type) this.jobType = type as JobType |
diff --git a/client/src/app/shared/shared-forms/select/select-channel.component.ts b/client/src/app/shared/shared-forms/select/select-channel.component.ts index 5fcae0050..26d6216ef 100644 --- a/client/src/app/shared/shared-forms/select/select-channel.component.ts +++ b/client/src/app/shared/shared-forms/select/select-channel.component.ts | |||
@@ -39,8 +39,10 @@ export class SelectChannelComponent implements ControlValueAccessor, OnChanges { | |||
39 | 39 | ||
40 | propagateChange = (_: any) => { /* empty */ } | 40 | propagateChange = (_: any) => { /* empty */ } |
41 | 41 | ||
42 | writeValue (id: number) { | 42 | writeValue (id: number | string) { |
43 | this.selectedId = id | 43 | this.selectedId = typeof id === 'string' |
44 | ? parseInt(id, 10) | ||
45 | : id | ||
44 | } | 46 | } |
45 | 47 | ||
46 | registerOnChange (fn: (_: any) => void) { | 48 | registerOnChange (fn: (_: any) => void) { |
diff --git a/client/src/app/shared/shared-video-miniature/video-filters-header.component.html b/client/src/app/shared/shared-video-miniature/video-filters-header.component.html index fe7a59bdb..9ddfd7dda 100644 --- a/client/src/app/shared/shared-video-miniature/video-filters-header.component.html +++ b/client/src/app/shared/shared-video-miniature/video-filters-header.component.html | |||
@@ -42,6 +42,7 @@ | |||
42 | formControlName="sort" | 42 | formControlName="sort" |
43 | [clearable]="false" | 43 | [clearable]="false" |
44 | [searchable]="false" | 44 | [searchable]="false" |
45 | [bindValue]="null" | ||
45 | > | 46 | > |
46 | <ng-option i18n value="-publishedAt">Sort by <strong>"Recently Added"</strong></ng-option> | 47 | <ng-option i18n value="-publishedAt">Sort by <strong>"Recently Added"</strong></ng-option> |
47 | <ng-option i18n value="-originallyPublishedAt">Sort by <strong>"Original Publication Date"</strong></ng-option> | 48 | <ng-option i18n value="-originallyPublishedAt">Sort by <strong>"Original Publication Date"</strong></ng-option> |
diff --git a/client/src/app/shared/shared-video-miniature/video-filters.model.ts b/client/src/app/shared/shared-video-miniature/video-filters.model.ts index 73a30ca08..4069ab4b5 100644 --- a/client/src/app/shared/shared-video-miniature/video-filters.model.ts +++ b/client/src/app/shared/shared-video-miniature/video-filters.model.ts | |||
@@ -1,7 +1,8 @@ | |||
1 | import { splitIntoArray, toBoolean } from '@app/helpers' | 1 | import { splitIntoArray, toBoolean } from '@app/helpers' |
2 | import { getAllPrivacies } from '@shared/core-utils' | 2 | import { getAllPrivacies } from '@shared/core-utils' |
3 | import { AttributesOnly } from '@shared/typescript-utils' | 3 | import { escapeHTML } from '@shared/core-utils/renderer' |
4 | import { BooleanBothQuery, NSFWPolicyType, VideoInclude, VideoPrivacy, VideoSortField } from '@shared/models' | 4 | import { BooleanBothQuery, NSFWPolicyType, VideoInclude, VideoPrivacy, VideoSortField } from '@shared/models' |
5 | import { AttributesOnly } from '@shared/typescript-utils' | ||
5 | 6 | ||
6 | type VideoFiltersKeys = { | 7 | type VideoFiltersKeys = { |
7 | [ id in keyof AttributesOnly<VideoFilters> ]: any | 8 | [ id in keyof AttributesOnly<VideoFilters> ]: any |
@@ -90,19 +91,28 @@ export class VideoFilters { | |||
90 | } | 91 | } |
91 | 92 | ||
92 | load (obj: Partial<AttributesOnly<VideoFilters>>) { | 93 | load (obj: Partial<AttributesOnly<VideoFilters>>) { |
93 | if (obj.sort !== undefined) this.sort = obj.sort | 94 | // FIXME: We may use <ng-option> that doesn't escape HTML so prefer to escape things |
95 | // https://github.com/ng-select/ng-select/issues/1363 | ||
96 | |||
97 | const escapeIfNeeded = (value: any) => { | ||
98 | if (typeof value === 'string') return escapeHTML(value) | ||
99 | |||
100 | return value | ||
101 | } | ||
102 | |||
103 | if (obj.sort !== undefined) this.sort = escapeIfNeeded(obj.sort) as VideoSortField | ||
94 | 104 | ||
95 | if (obj.nsfw !== undefined) this.nsfw = obj.nsfw | 105 | if (obj.nsfw !== undefined) this.nsfw = escapeIfNeeded(obj.nsfw) as BooleanBothQuery |
96 | 106 | ||
97 | if (obj.languageOneOf !== undefined) this.languageOneOf = splitIntoArray(obj.languageOneOf) | 107 | if (obj.languageOneOf !== undefined) this.languageOneOf = splitIntoArray(escapeIfNeeded(obj.languageOneOf)) |
98 | if (obj.categoryOneOf !== undefined) this.categoryOneOf = splitIntoArray(obj.categoryOneOf) | 108 | if (obj.categoryOneOf !== undefined) this.categoryOneOf = splitIntoArray(escapeIfNeeded(obj.categoryOneOf)) |
99 | 109 | ||
100 | if (obj.scope !== undefined) this.scope = obj.scope | 110 | if (obj.scope !== undefined) this.scope = escapeIfNeeded(obj.scope) as VideoFilterScope |
101 | if (obj.allVideos !== undefined) this.allVideos = toBoolean(obj.allVideos) | 111 | if (obj.allVideos !== undefined) this.allVideos = toBoolean(obj.allVideos) |
102 | 112 | ||
103 | if (obj.live !== undefined) this.live = obj.live | 113 | if (obj.live !== undefined) this.live = escapeIfNeeded(obj.live) as BooleanBothQuery |
104 | 114 | ||
105 | if (obj.search !== undefined) this.search = obj.search | 115 | if (obj.search !== undefined) this.search = escapeIfNeeded(obj.search) |
106 | 116 | ||
107 | this.buildActiveFilters() | 117 | this.buildActiveFilters() |
108 | } | 118 | } |