@gitlab/ui 66.36.1 → 67.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ # [67.0.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v66.37.0...v67.0.0) (2023-10-27)
2
+
3
+
4
+ ### Features
5
+
6
+ * **SafeHtml:** disallow potentially dangerous tags ([25926ab](https://gitlab.com/gitlab-org/gitlab-ui/commit/25926ab418ffbbbe657a6a1485d01c13368e2a67))
7
+
8
+
9
+ ### BREAKING CHANGES
10
+
11
+ * **SafeHtml:** This change improves safe-html defense
12
+ by adding style, mystlye and form tags to the forbidden
13
+ list.
14
+
15
+ # [66.37.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v66.36.1...v66.37.0) (2023-10-25)
16
+
17
+
18
+ ### Features
19
+
20
+ * **GlDuoChatMessage:** do not strip out copy-code ([14782a5](https://gitlab.com/gitlab-org/gitlab-ui/commit/14782a5d60a8e48dffb60fa120bddc61285d950a))
21
+
1
22
  ## [66.36.1](https://gitlab.com/gitlab-org/gitlab-ui/compare/v66.36.0...v66.36.1) (2023-10-24)
2
23
 
3
24
 
@@ -1,7 +1,6 @@
1
1
  import isEqual from 'lodash/isEqual';
2
2
  import cloneDeep from 'lodash/cloneDeep';
3
- import PortalVue from 'portal-vue';
4
- import Vue from 'vue';
3
+ import { PortalTarget } from 'portal-vue';
5
4
  import { GlTooltipDirective } from '../../../directives/tooltip';
6
5
  import GlIcon from '../icon/icon';
7
6
  import GlSearchBoxByClick from '../search_box_by_click/search_box_by_click';
@@ -9,7 +8,6 @@ import GlFilteredSearchTerm from './filtered_search_term';
9
8
  import { termTokenDefinition, createTerm, needDenormalization, denormalizeTokens, isEmptyTerm, INTENT_ACTIVATE_PREVIOUS, ensureTokenId, normalizeTokens } from './filtered_search_utils';
10
9
  import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
11
10
 
12
- Vue.use(PortalVue);
13
11
  let portalUuid = 0;
14
12
  function initialState() {
15
13
  return [createTerm()];
@@ -18,7 +16,8 @@ var script = {
18
16
  name: 'GlFilteredSearch',
19
17
  components: {
20
18
  GlSearchBoxByClick,
21
- GlIcon
19
+ GlIcon,
20
+ PortalTarget
22
21
  },
23
22
  directives: {
24
23
  GlTooltip: GlTooltipDirective
@@ -14,6 +14,9 @@ const concatIndicesUntilEmpty = arr => {
14
14
  };
15
15
  var script = {
16
16
  name: 'GlDuoChatMessage',
17
+ safeHtmlConfigExtension: {
18
+ ADD_TAGS: ['copy-code']
19
+ },
17
20
  components: {
18
21
  DocumentationSources,
19
22
  GlDuoUserFeedback
@@ -102,7 +105,7 @@ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=
102
105
  'gl-ml-auto gl-bg-blue-100 gl-text-blue-900 gl-rounded-bottom-right-none': _vm.isUserMessage,
103
106
  'gl-rounded-bottom-left-none gl-text-gray-900 gl-bg-white gl-border-1 gl-border-solid gl-border-gray-50':
104
107
  _vm.isAssistantMessage,
105
- }},[_c('div',{directives:[{name:"safe-html",rawName:"v-safe-html",value:(_vm.messageContent),expression:"messageContent"}],ref:"content"}),_vm._v(" "),(_vm.isAssistantMessage)?[(_vm.sources)?_c('documentation-sources',{attrs:{"sources":_vm.sources}}):_vm._e(),_vm._v(" "),_c('div',{staticClass:"gl-display-flex gl-align-items-flex-end gl-mt-4"},[_c('gl-duo-user-feedback',{on:{"feedback":function($event){return _vm.$emit('track-feedback', $event)}}})],1)]:_vm._e()],2)};
108
+ }},[_c('div',{directives:[{name:"safe-html",rawName:"v-safe-html:[$options.safeHtmlConfigExtension]",value:(_vm.messageContent),expression:"messageContent",arg:_vm.$options.safeHtmlConfigExtension}],ref:"content"}),_vm._v(" "),(_vm.isAssistantMessage)?[(_vm.sources)?_c('documentation-sources',{attrs:{"sources":_vm.sources}}):_vm._e(),_vm._v(" "),_c('div',{staticClass:"gl-display-flex gl-align-items-flex-end gl-mt-4"},[_c('gl-duo-user-feedback',{on:{"feedback":function($event){return _vm.$emit('track-feedback', $event)}}})],1)]:_vm._e()],2)};
106
109
  var __vue_staticRenderFns__ = [];
107
110
 
108
111
  /* style */
@@ -1,5 +1,6 @@
1
1
  // See https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1421#note_617098438
2
2
  // for more details
3
3
  const forbiddenDataAttrs = ['data-remote', 'data-url', 'data-type', 'data-method', 'data-disable-with', 'data-disabled', 'data-disable', 'data-turbo'];
4
+ const forbiddenTags = ['style', 'mstyle', 'form'];
4
5
 
5
- export { forbiddenDataAttrs };
6
+ export { forbiddenDataAttrs, forbiddenTags };
@@ -1,5 +1,5 @@
1
1
  import DOMPurify from 'dompurify';
2
- import { forbiddenDataAttrs } from './constants';
2
+ import { forbiddenDataAttrs, forbiddenTags } from './constants';
3
3
 
4
4
  const {
5
5
  sanitize
@@ -13,7 +13,8 @@ const {
13
13
  const DEFAULT_CONFIG = {
14
14
  RETURN_DOM_FRAGMENT: true,
15
15
  ALLOW_UNKNOWN_PROTOCOLS: true,
16
- FORBID_ATTR: [...forbiddenDataAttrs]
16
+ FORBID_ATTR: forbiddenDataAttrs,
17
+ FORBID_TAGS: forbiddenTags
17
18
  };
18
19
  const transform = (el, binding) => {
19
20
  if (binding.oldValue !== binding.value) {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Tue, 24 Oct 2023 21:07:01 GMT
3
+ * Generated on Fri, 27 Oct 2023 09:40:39 GMT
4
4
  */
5
5
 
6
6
  :root {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Tue, 24 Oct 2023 21:07:01 GMT
3
+ * Generated on Fri, 27 Oct 2023 09:40:39 GMT
4
4
  */
5
5
 
6
6
  :root.gl-dark {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Tue, 24 Oct 2023 21:07:01 GMT
3
+ * Generated on Fri, 27 Oct 2023 09:40:39 GMT
4
4
  */
5
5
 
6
6
  export const BLACK = "#fff";
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Tue, 24 Oct 2023 21:07:01 GMT
3
+ * Generated on Fri, 27 Oct 2023 09:40:39 GMT
4
4
  */
5
5
 
6
6
  export const BLACK = "#000";
@@ -1,6 +1,6 @@
1
1
 
2
2
  // Do not edit directly
3
- // Generated on Tue, 24 Oct 2023 21:07:01 GMT
3
+ // Generated on Fri, 27 Oct 2023 09:40:39 GMT
4
4
 
5
5
  $red-950: #fff4f3;
6
6
  $red-900: #fcf1ef;
@@ -1,6 +1,6 @@
1
1
 
2
2
  // Do not edit directly
3
- // Generated on Tue, 24 Oct 2023 21:07:01 GMT
3
+ // Generated on Fri, 27 Oct 2023 09:40:39 GMT
4
4
 
5
5
  $gl-line-height-52: 3.25rem;
6
6
  $gl-line-height-44: 2.75rem;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "66.36.1",
3
+ "version": "67.0.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -1,8 +1,7 @@
1
1
  <script>
2
2
  import isEqual from 'lodash/isEqual';
3
3
  import cloneDeep from 'lodash/cloneDeep';
4
- import PortalVue from 'portal-vue';
5
- import Vue from 'vue';
4
+ import { PortalTarget } from 'portal-vue';
6
5
  import { GlTooltipDirective } from '../../../directives/tooltip';
7
6
  import GlIcon from '../icon/icon.vue';
8
7
  import GlSearchBoxByClick from '../search_box_by_click/search_box_by_click.vue';
@@ -18,8 +17,6 @@ import {
18
17
  termTokenDefinition,
19
18
  } from './filtered_search_utils';
20
19
 
21
- Vue.use(PortalVue);
22
-
23
20
  let portalUuid = 0;
24
21
 
25
22
  function initialState() {
@@ -31,6 +28,7 @@ export default {
31
28
  components: {
32
29
  GlSearchBoxByClick,
33
30
  GlIcon,
31
+ PortalTarget,
34
32
  },
35
33
  directives: { GlTooltip: GlTooltipDirective },
36
34
  provide() {
@@ -15,6 +15,7 @@ describe('DuoChatMessage', () => {
15
15
  const findContent = () => wrapper.findComponent({ ref: 'content' });
16
16
  const findDocumentSources = () => wrapper.findComponent(DocumentationSources);
17
17
  const findUserFeedback = () => wrapper.findComponent(GlDuoUserFeedback);
18
+ const findCopyCodeButton = () => wrapper.find('copy-code');
18
19
  const mockMarkdownContent = 'foo **bar**';
19
20
 
20
21
  let renderMarkdown;
@@ -106,6 +107,10 @@ describe('DuoChatMessage', () => {
106
107
  findUserFeedback().vm.$emit('feedback', 'foo');
107
108
  expect(wrapper.emitted('track-feedback')).toEqual([['foo']]);
108
109
  });
110
+
111
+ it('does not strip out the <copy-code/> element from HTML output', () => {
112
+ expect(findCopyCodeButton().exists()).toBe(true);
113
+ });
109
114
  });
110
115
 
111
116
  describe('message output', () => {
@@ -15,6 +15,9 @@ const concatIndicesUntilEmpty = (arr) => {
15
15
 
16
16
  export default {
17
17
  name: 'GlDuoChatMessage',
18
+ safeHtmlConfigExtension: {
19
+ ADD_TAGS: ['copy-code'],
20
+ },
18
21
  components: {
19
22
  DocumentationSources,
20
23
  GlDuoUserFeedback,
@@ -102,7 +105,7 @@ export default {
102
105
  isAssistantMessage,
103
106
  }"
104
107
  >
105
- <div ref="content" v-safe-html="messageContent"></div>
108
+ <div ref="content" v-safe-html:[$options.safeHtmlConfigExtension]="messageContent"></div>
106
109
 
107
110
  <template v-if="isAssistantMessage">
108
111
  <documentation-sources v-if="sources" :sources="sources" />
@@ -10,3 +10,5 @@ export const forbiddenDataAttrs = [
10
10
  'data-disable',
11
11
  'data-turbo',
12
12
  ];
13
+
14
+ export const forbiddenTags = ['style', 'mstyle', 'form'];
@@ -1,5 +1,5 @@
1
1
  import DOMPurify from 'dompurify';
2
- import { forbiddenDataAttrs } from './constants';
2
+ import { forbiddenDataAttrs, forbiddenTags } from './constants';
3
3
 
4
4
  const { sanitize } = DOMPurify;
5
5
 
@@ -11,7 +11,8 @@ const { sanitize } = DOMPurify;
11
11
  const DEFAULT_CONFIG = {
12
12
  RETURN_DOM_FRAGMENT: true,
13
13
  ALLOW_UNKNOWN_PROTOCOLS: true,
14
- FORBID_ATTR: [...forbiddenDataAttrs],
14
+ FORBID_ATTR: forbiddenDataAttrs,
15
+ FORBID_TAGS: forbiddenTags,
15
16
  };
16
17
 
17
18
  const transform = (el, binding) => {
@@ -49,6 +49,21 @@ describe('safe html directive', () => {
49
49
  expect(wrapper.html()).toEqual('<div><a>click here</a></div>');
50
50
  });
51
51
 
52
+ it('should remove style tags', () => {
53
+ createComponent({ html: '<style>p {width:50%;}</style>' });
54
+ expect(wrapper.html()).toEqual('<div></div>');
55
+ });
56
+
57
+ it('should remove mstyle tags', () => {
58
+ createComponent({ html: '<math><mstyle displaystyle="true"></mstyle></math>' });
59
+ expect(wrapper.html()).toEqual('<div><math></math></div>');
60
+ });
61
+
62
+ it('should remove form tags', () => {
63
+ createComponent({ html: '<form method="post" action="path"></form>' });
64
+ expect(wrapper.html()).toEqual('<div></div>');
65
+ });
66
+
52
67
  it('should remove any existing children', () => {
53
68
  createComponent({
54
69
  template: `<div v-safe-html="rawHtml">foo <i>bar</i></div>`,