|
4 | 4 | * you may not use this file except in compliance with the Elastic License. |
5 | 5 | */ |
6 | 6 |
|
| 7 | +import { IExternalUrl } from 'src/core/public'; |
7 | 8 | import { UrlDrilldown, ActionContext, Config } from './url_drilldown'; |
8 | 9 | import { IEmbeddable } from '../../../../../../src/plugins/embeddable/public/lib/embeddables'; |
9 | 10 | import { DatatableColumnType } from '../../../../../../src/plugins/expressions/common'; |
| 11 | +import { of } from '../../../../../../src/plugins/kibana_utils'; |
10 | 12 | import { createPoint, rowClickData, TestEmbeddable } from './test/data'; |
11 | 13 | import { |
12 | 14 | VALUE_CLICK_TRIGGER, |
@@ -59,13 +61,27 @@ const mockEmbeddable = ({ |
59 | 61 |
|
60 | 62 | const mockNavigateToUrl = jest.fn(() => Promise.resolve()); |
61 | 63 |
|
62 | | -describe('UrlDrilldown', () => { |
63 | | - const urlDrilldown = new UrlDrilldown({ |
| 64 | +class TextExternalUrl implements IExternalUrl { |
| 65 | + constructor(private readonly isCorrect: boolean = true) {} |
| 66 | + |
| 67 | + public validateUrl(url: string): URL | null { |
| 68 | + return this.isCorrect ? new URL(url) : null; |
| 69 | + } |
| 70 | +} |
| 71 | + |
| 72 | +const createDrilldown = (isExternalUrlValid: boolean = true) => { |
| 73 | + const drilldown = new UrlDrilldown({ |
| 74 | + externalUrl: new TextExternalUrl(isExternalUrlValid), |
64 | 75 | getGlobalScope: () => ({ kibanaUrl: 'http://localhost:5601/' }), |
65 | 76 | getSyntaxHelpDocsLink: () => 'http://localhost:5601/docs', |
66 | 77 | getVariablesHelpDocsLink: () => 'http://localhost:5601/docs', |
67 | 78 | navigateToUrl: mockNavigateToUrl, |
68 | 79 | }); |
| 80 | + return drilldown; |
| 81 | +}; |
| 82 | + |
| 83 | +describe('UrlDrilldown', () => { |
| 84 | + const urlDrilldown = createDrilldown(); |
69 | 85 |
|
70 | 86 | test('license', () => { |
71 | 87 | expect(urlDrilldown.minimalLicense).toBe('gold'); |
@@ -125,6 +141,30 @@ describe('UrlDrilldown', () => { |
125 | 141 |
|
126 | 142 | await expect(urlDrilldown.isCompatible(config, context)).resolves.toBe(false); |
127 | 143 | }); |
| 144 | + |
| 145 | + test('not compatible if external URL is denied', async () => { |
| 146 | + const drilldown1 = createDrilldown(true); |
| 147 | + const drilldown2 = createDrilldown(false); |
| 148 | + const config: Config = { |
| 149 | + url: { |
| 150 | + template: `https://elasti.co/?{{event.value}}&{{rison context.panel.query}}`, |
| 151 | + }, |
| 152 | + openInNewTab: false, |
| 153 | + }; |
| 154 | + |
| 155 | + const context: ActionContext = { |
| 156 | + data: { |
| 157 | + data: mockDataPoints, |
| 158 | + }, |
| 159 | + embeddable: mockEmbeddable, |
| 160 | + }; |
| 161 | + |
| 162 | + const result1 = await drilldown1.isCompatible(config, context); |
| 163 | + const result2 = await drilldown2.isCompatible(config, context); |
| 164 | + |
| 165 | + expect(result1).toBe(true); |
| 166 | + expect(result2).toBe(false); |
| 167 | + }); |
128 | 168 | }); |
129 | 169 |
|
130 | 170 | describe('getHref & execute', () => { |
@@ -173,6 +213,42 @@ describe('UrlDrilldown', () => { |
173 | 213 | await expect(urlDrilldown.execute(config, context)).rejects.toThrowError(); |
174 | 214 | expect(mockNavigateToUrl).not.toBeCalled(); |
175 | 215 | }); |
| 216 | + |
| 217 | + test('should throw on denied external URL', async () => { |
| 218 | + const drilldown1 = createDrilldown(true); |
| 219 | + const drilldown2 = createDrilldown(false); |
| 220 | + const config: Config = { |
| 221 | + url: { |
| 222 | + template: `https://elasti.co/?{{event.value}}&{{rison context.panel.query}}`, |
| 223 | + }, |
| 224 | + openInNewTab: false, |
| 225 | + }; |
| 226 | + |
| 227 | + const context: ActionContext = { |
| 228 | + data: { |
| 229 | + data: mockDataPoints, |
| 230 | + }, |
| 231 | + embeddable: mockEmbeddable, |
| 232 | + }; |
| 233 | + |
| 234 | + const url = await drilldown1.getHref(config, context); |
| 235 | + await drilldown1.execute(config, context); |
| 236 | + |
| 237 | + expect(url).toMatchInlineSnapshot(`"https://elasti.co/?test&(language:kuery,query:test)"`); |
| 238 | + expect(mockNavigateToUrl).toBeCalledWith(url); |
| 239 | + |
| 240 | + const [, error1] = await of(drilldown2.getHref(config, context)); |
| 241 | + const [, error2] = await of(drilldown2.execute(config, context)); |
| 242 | + |
| 243 | + expect(error1).toBeInstanceOf(Error); |
| 244 | + expect(error1.message).toMatchInlineSnapshot( |
| 245 | + `"External URL [https://elasti.co/?test&(language:kuery,query:test)] was denied by ExternalUrl service. You can configure external URL policies using \\"externalUrl.policy\\" setting in kibana.yml."` |
| 246 | + ); |
| 247 | + expect(error2).toBeInstanceOf(Error); |
| 248 | + expect(error2.message).toMatchInlineSnapshot( |
| 249 | + `"External URL [https://elasti.co/?test&(language:kuery,query:test)] was denied by ExternalUrl service. You can configure external URL policies using \\"externalUrl.policy\\" setting in kibana.yml."` |
| 250 | + ); |
| 251 | + }); |
176 | 252 | }); |
177 | 253 |
|
178 | 254 | describe('variables', () => { |
|
0 commit comments