Loading src/common/chat/gitlab_chat_view.test.ts +1 −0 Original line number Diff line number Diff line Loading @@ -56,6 +56,7 @@ describe('GitLabChatView', () => { webview.webview, context, 'gitlab_duo_chat', undefined, { slashCommands: defaultSlashCommands, }, Loading src/common/chat/gitlab_chat_view.ts +1 −0 Original line number Diff line number Diff line Loading @@ -173,6 +173,7 @@ export class GitLabChatView { this.#chatView.webview, this.#context, 'gitlab_duo_chat', undefined, initialState, ); Loading src/common/utils/webviews/prepare_webview_source.test.ts +29 −4 Original line number Diff line number Diff line Loading @@ -11,7 +11,7 @@ describe('prepareWebViewSource', () => { <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="Content-Security-Policy" content="img-src vscode-resource: https:; script-src 'nonce-{{nonce}}';"> <meta http-equiv="Content-Security-Policy" content="img-src vscode-resource: data: https:{{httpProtocol}}; script-src 'nonce-{{nonce}}';"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>GitLab Workflow</title> <script crossorigin type="module" src="/gitlab_duo_chat/assets/app.js"></script> Loading @@ -28,7 +28,7 @@ describe('prepareWebViewSource', () => { <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="Content-Security-Policy" content="img-src vscode-resource: https:; script-src 'nonce-123';"> <meta http-equiv="Content-Security-Policy" content="img-src vscode-resource: data: https:; script-src 'nonce-123';"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>GitLab Workflow</title> <script nonce="123" crossorigin type="module" src="file:///foo/bar/webviews/gitlab_duo_chat/assets/app.js"></script> Loading Loading @@ -58,15 +58,40 @@ describe('prepareWebViewSource', () => { }); it('returns WebView source with inserted nonce and assets', async () => { const result = await prepareWebviewSource(webview, context, 'gitlab_duo_chat'); const result = await prepareWebviewSource( webview, context, 'gitlab_duo_chat', 'https://gitlab.example.com', ); expect(result).toStrictEqual(expectedHTML); }); it('returns WebView source with HTTP and HTTPS CSP for HTTP GitLab instance', async () => { const result = await prepareWebviewSource( webview, context, 'gitlab_duo_chat', 'http://gitlab.example.com', ); const expectedHtmlWithHttp = expectedHTML.replace( 'img-src vscode-resource: data: https:;', 'img-src vscode-resource: data: https: http:;', ); expect(result).toStrictEqual(expectedHtmlWithHttp); }); it('returns WebView source with initial app state set', async () => { const initState = { foo: 'bar', }; const result = await prepareWebviewSource(webview, context, 'gitlab_duo_chat', initState); const result = await prepareWebviewSource( webview, context, 'gitlab_duo_chat', 'https://gitlab.example.com', initState, ); const expectedHtmlString = expectedHTML.replace( "data-initial-state=''", `data-initial-state='${JSON.stringify(initState)}'`, Loading src/common/utils/webviews/prepare_webview_source.ts +17 −2 Original line number Diff line number Diff line Loading @@ -7,8 +7,6 @@ const webviewResourcePaths = { styleUri: 'assets/index.css', } as const; type WebviewResources = Record<keyof typeof webviewResourcePaths, vscode.Uri>; const getWebviewUri = ( path: string, webview: vscode.Webview, Loading @@ -29,13 +27,29 @@ const getWebviewResources = ( return mapValues(webviewResourcePaths, path => getWebviewUri(path, webview, context, webviewKey)); }; /** * Generates HTTP protocol part for CSP based on GitLab instance protocol * @param gitlabInstanceUrl The GitLab instance URL to check protocol for * @returns ' http:' if HTTP instance, empty string otherwise */ const generateHttpProtocol = (gitlabInstanceUrl?: string): string => { try { const url = new URL(gitlabInstanceUrl || ''); return url.protocol === 'http:' ? ' http:' : ''; } catch { return ''; } }; export const prepareWebviewSource = async ( webview: vscode.Webview, context: vscode.ExtensionContext, webviewKey: string, instanceUrl: string | undefined, initialState?: object, ): Promise<string> => { const nonce = generateSecret(); const httpProtocol = generateHttpProtocol(instanceUrl); const { appScriptUri, styleUri } = getWebviewResources(webview, context, webviewKey); const fileUri = vscode.Uri.joinPath(context.extensionUri, 'webviews', webviewKey, 'index.html'); Loading @@ -45,6 +59,7 @@ export const prepareWebviewSource = async ( return fileContent .replace(/{{nonce}}/gm, nonce) .replace(/{{httpProtocol}}/gm, httpProtocol) .replace(/<script /g, `<script nonce="${nonce}" `) .replace(`/${webviewKey}/${webviewResourcePaths.styleUri}`, styleUri.toString()) .replace(`/${webviewKey}/${webviewResourcePaths.appScriptUri}`, appScriptUri.toString()) Loading src/desktop/ci/security_finding_controller.ts +1 −0 Original line number Diff line number Diff line Loading @@ -77,6 +77,7 @@ export class SecurityFindingWebviewController implements RepositoryRootWebviewPr panel.webview, this.#context, 'security_finding', projectInRepository.account.instanceUrl, ); panel.repositoryRoot = projectInRepository.pointer.repository.rootFsPath; Loading Loading
src/common/chat/gitlab_chat_view.test.ts +1 −0 Original line number Diff line number Diff line Loading @@ -56,6 +56,7 @@ describe('GitLabChatView', () => { webview.webview, context, 'gitlab_duo_chat', undefined, { slashCommands: defaultSlashCommands, }, Loading
src/common/chat/gitlab_chat_view.ts +1 −0 Original line number Diff line number Diff line Loading @@ -173,6 +173,7 @@ export class GitLabChatView { this.#chatView.webview, this.#context, 'gitlab_duo_chat', undefined, initialState, ); Loading
src/common/utils/webviews/prepare_webview_source.test.ts +29 −4 Original line number Diff line number Diff line Loading @@ -11,7 +11,7 @@ describe('prepareWebViewSource', () => { <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="Content-Security-Policy" content="img-src vscode-resource: https:; script-src 'nonce-{{nonce}}';"> <meta http-equiv="Content-Security-Policy" content="img-src vscode-resource: data: https:{{httpProtocol}}; script-src 'nonce-{{nonce}}';"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>GitLab Workflow</title> <script crossorigin type="module" src="/gitlab_duo_chat/assets/app.js"></script> Loading @@ -28,7 +28,7 @@ describe('prepareWebViewSource', () => { <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="Content-Security-Policy" content="img-src vscode-resource: https:; script-src 'nonce-123';"> <meta http-equiv="Content-Security-Policy" content="img-src vscode-resource: data: https:; script-src 'nonce-123';"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>GitLab Workflow</title> <script nonce="123" crossorigin type="module" src="file:///foo/bar/webviews/gitlab_duo_chat/assets/app.js"></script> Loading Loading @@ -58,15 +58,40 @@ describe('prepareWebViewSource', () => { }); it('returns WebView source with inserted nonce and assets', async () => { const result = await prepareWebviewSource(webview, context, 'gitlab_duo_chat'); const result = await prepareWebviewSource( webview, context, 'gitlab_duo_chat', 'https://gitlab.example.com', ); expect(result).toStrictEqual(expectedHTML); }); it('returns WebView source with HTTP and HTTPS CSP for HTTP GitLab instance', async () => { const result = await prepareWebviewSource( webview, context, 'gitlab_duo_chat', 'http://gitlab.example.com', ); const expectedHtmlWithHttp = expectedHTML.replace( 'img-src vscode-resource: data: https:;', 'img-src vscode-resource: data: https: http:;', ); expect(result).toStrictEqual(expectedHtmlWithHttp); }); it('returns WebView source with initial app state set', async () => { const initState = { foo: 'bar', }; const result = await prepareWebviewSource(webview, context, 'gitlab_duo_chat', initState); const result = await prepareWebviewSource( webview, context, 'gitlab_duo_chat', 'https://gitlab.example.com', initState, ); const expectedHtmlString = expectedHTML.replace( "data-initial-state=''", `data-initial-state='${JSON.stringify(initState)}'`, Loading
src/common/utils/webviews/prepare_webview_source.ts +17 −2 Original line number Diff line number Diff line Loading @@ -7,8 +7,6 @@ const webviewResourcePaths = { styleUri: 'assets/index.css', } as const; type WebviewResources = Record<keyof typeof webviewResourcePaths, vscode.Uri>; const getWebviewUri = ( path: string, webview: vscode.Webview, Loading @@ -29,13 +27,29 @@ const getWebviewResources = ( return mapValues(webviewResourcePaths, path => getWebviewUri(path, webview, context, webviewKey)); }; /** * Generates HTTP protocol part for CSP based on GitLab instance protocol * @param gitlabInstanceUrl The GitLab instance URL to check protocol for * @returns ' http:' if HTTP instance, empty string otherwise */ const generateHttpProtocol = (gitlabInstanceUrl?: string): string => { try { const url = new URL(gitlabInstanceUrl || ''); return url.protocol === 'http:' ? ' http:' : ''; } catch { return ''; } }; export const prepareWebviewSource = async ( webview: vscode.Webview, context: vscode.ExtensionContext, webviewKey: string, instanceUrl: string | undefined, initialState?: object, ): Promise<string> => { const nonce = generateSecret(); const httpProtocol = generateHttpProtocol(instanceUrl); const { appScriptUri, styleUri } = getWebviewResources(webview, context, webviewKey); const fileUri = vscode.Uri.joinPath(context.extensionUri, 'webviews', webviewKey, 'index.html'); Loading @@ -45,6 +59,7 @@ export const prepareWebviewSource = async ( return fileContent .replace(/{{nonce}}/gm, nonce) .replace(/{{httpProtocol}}/gm, httpProtocol) .replace(/<script /g, `<script nonce="${nonce}" `) .replace(`/${webviewKey}/${webviewResourcePaths.styleUri}`, styleUri.toString()) .replace(`/${webviewKey}/${webviewResourcePaths.appScriptUri}`, appScriptUri.toString()) Loading
src/desktop/ci/security_finding_controller.ts +1 −0 Original line number Diff line number Diff line Loading @@ -77,6 +77,7 @@ export class SecurityFindingWebviewController implements RepositoryRootWebviewPr panel.webview, this.#context, 'security_finding', projectInRepository.account.instanceUrl, ); panel.repositoryRoot = projectInRepository.pointer.repository.rootFsPath; Loading