diff --git a/zeppelin-web-angular/e2e/tests/share/note-import/note-import-modal.spec.ts b/zeppelin-web-angular/e2e/tests/share/note-import/note-import-modal.spec.ts index 229967d719b..6361759afdd 100644 --- a/zeppelin-web-angular/e2e/tests/share/note-import/note-import-modal.spec.ts +++ b/zeppelin-web-angular/e2e/tests/share/note-import/note-import-modal.spec.ts @@ -10,6 +10,8 @@ * limitations under the License. */ +import { createServer } from 'node:http'; + import { test, expect } from '@playwright/test'; import { HomePage } from '../../../models/home-page'; import { NoteImportModal } from '../../../models/note-import-modal'; @@ -69,6 +71,46 @@ test.describe('Note Import Modal', () => { await expect(noteImportModal.importNoteButton).toBeEnabled(); }); + test('Given URL tab is selected, When importing from wildcard CORS origin, Then response should be readable', async () => { + const corsServer = createServer((request, response) => { + response.setHeader('Access-Control-Allow-Headers', 'X-Requested-With'); + response.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS'); + response.setHeader('Access-Control-Allow-Origin', '*'); + response.setHeader('Connection', 'close'); + + if (request.method === 'OPTIONS') { + response.writeHead(204); + response.end(); + return; + } + + response.setHeader('Content-Type', 'application/json'); + response.end(JSON.stringify({ name: 'Missing paragraphs' })); + }); + + await new Promise((resolve, reject) => { + corsServer.once('error', reject); + corsServer.listen(0, '127.0.0.1', resolve); + }); + + try { + const address = corsServer.address(); + if (!address || typeof address === 'string') { + throw new Error('Failed to bind CORS test server'); + } + + await noteImportModal.switchToUrlTab(); + await noteImportModal.setImportUrl(`http://127.0.0.1:${address.port}/note.json`); + await noteImportModal.clickImportNote(); + + await expect(noteImportModal.errorAlert).toHaveText('Invalid JSON'); + } finally { + await new Promise((resolve, reject) => { + corsServer.close(error => (error ? reject(error) : resolve())); + }); + } + }); + test('Given Import Note modal is open, When entering import name, Then name should be set', async () => { const importName = `Imported Note ${Date.now()}`; await noteImportModal.setImportAsName(importName); diff --git a/zeppelin-web-angular/src/app/app-http.interceptor.ts b/zeppelin-web-angular/src/app/app-http.interceptor.ts index b1e287622ac..6a6a4a18533 100644 --- a/zeppelin-web-angular/src/app/app-http.interceptor.ts +++ b/zeppelin-web-angular/src/app/app-http.interceptor.ts @@ -28,7 +28,7 @@ export class AppHttpInterceptor implements HttpInterceptor { intercept(httpRequest: HttpRequest, next: HttpHandler): Observable> { let httpRequestUpdated = httpRequest.clone({ withCredentials: true }); if (environment.production) { - httpRequestUpdated = httpRequest.clone({ setHeaders: { 'X-Requested-With': 'XMLHttpRequest' } }); + httpRequestUpdated = httpRequestUpdated.clone({ setHeaders: { 'X-Requested-With': 'XMLHttpRequest' } }); } return next.handle(httpRequestUpdated).pipe( map(event => { diff --git a/zeppelin-web-angular/src/app/share/note-import/note-import.component.ts b/zeppelin-web-angular/src/app/share/note-import/note-import.component.ts index a73627b9ba0..ef5fdb7f40e 100644 --- a/zeppelin-web-angular/src/app/share/note-import/note-import.component.ts +++ b/zeppelin-web-angular/src/app/share/note-import/note-import.component.ts @@ -10,7 +10,7 @@ * limitations under the License. */ -import { HttpClient } from '@angular/common/http'; +import { HttpBackend, HttpClient } from '@angular/common/http'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core'; import { ConfigurationService, MessageService, TicketService } from '@zeppelin/services'; @@ -37,6 +37,7 @@ export class NoteImportComponent extends MessageListenersManager implements OnIn errorText?: string; importLoading = false; wsMaxLimit?: number; + private readonly externalHttpClient: HttpClient; @MessageListener(OP.IMPORT_NOTE) noteImported(_: MessageReceiveDataTypeMap[OP.IMPORT_NOTE]) { @@ -46,7 +47,7 @@ export class NoteImportComponent extends MessageListenersManager implements OnIn importNote() { this.errorText = ''; this.importLoading = true; - this.httpClient.get(this.importUrl ?? '').subscribe( + this.externalHttpClient.get(this.importUrl ?? '').subscribe( data => { this.importLoading = false; this.processImportJson(data); @@ -106,9 +107,10 @@ export class NoteImportComponent extends MessageListenersManager implements OnIn private configurationService: ConfigurationService, private cdr: ChangeDetectorRef, private nzModalRef: NzModalRef, - private httpClient: HttpClient + httpBackend: HttpBackend ) { super(messageService); + this.externalHttpClient = new HttpClient(httpBackend); } async ngOnInit() {