mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
fix(http): add CSP nonce support to JsonpClientBackend
Add support for CSP nonces in JsonpClientBackend by injecting the CSP_NONCE token. This ensures that dynamically created script tags for JSONP requests include the required nonce attribute to comply with strict Content Security Policies.
This commit is contained in:
parent
df9eed4ff1
commit
39e382a756
3 changed files with 47 additions and 0 deletions
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import {DOCUMENT} from '../../index';
|
||||
import {
|
||||
CSP_NONCE,
|
||||
EnvironmentInjector,
|
||||
Inject,
|
||||
inject,
|
||||
|
|
@ -94,6 +95,7 @@ export class JsonpClientBackend implements HttpBackend {
|
|||
* A resolved promise that can be used to schedule microtasks in the event handlers.
|
||||
*/
|
||||
private readonly resolvedPromise = Promise.resolve();
|
||||
private readonly nonce = inject(CSP_NONCE, {optional: true});
|
||||
|
||||
constructor(
|
||||
private callbackMap: JsonpCallbackContext,
|
||||
|
|
@ -149,6 +151,12 @@ export class JsonpClientBackend implements HttpBackend {
|
|||
const node = this.document.createElement('script');
|
||||
node.src = url;
|
||||
|
||||
// Set the nonce for Content Security Policy compatibility. Without this,
|
||||
// JSONP requests will be blocked by strict-dynamic CSP policies.
|
||||
if (this.nonce) {
|
||||
node.setAttribute('nonce', this.nonce);
|
||||
}
|
||||
|
||||
// A JSONP request requires waiting for multiple callbacks. These variables
|
||||
// are closed over and track state across those callbacks.
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,16 @@ export class MockScriptElement {
|
|||
remove() {
|
||||
this.ownerDocument.removeNode(this);
|
||||
}
|
||||
|
||||
private attrs: Record<string, string> = {};
|
||||
|
||||
setAttribute(name: string, value: string): void {
|
||||
this.attrs[name] = value;
|
||||
}
|
||||
|
||||
getAttribute(name: string): string | null {
|
||||
return this.attrs.hasOwnProperty(name) ? this.attrs[name] : null;
|
||||
}
|
||||
}
|
||||
|
||||
export class MockDocument {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
import {DOCUMENT} from '../..';
|
||||
import {CSP_NONCE} from '@angular/core';
|
||||
import {HttpHeaders} from '../src/headers';
|
||||
import {
|
||||
JSONP_ERR_HEADERS_NOT_SUPPORTED,
|
||||
|
|
@ -43,6 +44,7 @@ describe('JsonpClientBackend', () => {
|
|||
JsonpClientBackend,
|
||||
{provide: JsonpCallbackContext, useValue: {}},
|
||||
{provide: DOCUMENT, useValue: mockDoc},
|
||||
{provide: CSP_NONCE, useValue: null},
|
||||
],
|
||||
});
|
||||
backend = TestBed.inject(JsonpClientBackend);
|
||||
|
|
@ -98,6 +100,33 @@ describe('JsonpClientBackend', () => {
|
|||
// executing.
|
||||
expect(document.mock!.ownerDocument).not.toEqual(document);
|
||||
});
|
||||
describe('CSP nonce', () => {
|
||||
it('sets nonce attribute on script element when CSP_NONCE token is provided', (done) => {
|
||||
TestBed.resetTestingModule();
|
||||
const mockDoc = new MockDocument();
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
JsonpClientBackend,
|
||||
{provide: JsonpCallbackContext, useValue: {}},
|
||||
{provide: DOCUMENT, useValue: mockDoc},
|
||||
{provide: CSP_NONCE, useValue: 'test-nonce-123'},
|
||||
],
|
||||
});
|
||||
const nonceBackend = TestBed.inject(JsonpClientBackend);
|
||||
nonceBackend.handle(SAMPLE_REQ).subscribe();
|
||||
|
||||
expect(mockDoc.mock!.getAttribute('nonce')).toBe('test-nonce-123');
|
||||
done();
|
||||
});
|
||||
|
||||
it('does not set nonce attribute when CSP_NONCE token is not provided', (done) => {
|
||||
backend.handle(SAMPLE_REQ).subscribe();
|
||||
|
||||
expect(document.mock!.getAttribute('nonce')).toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('throws an error', () => {
|
||||
it('when request method is not JSONP', () =>
|
||||
expect(() => backend.handle(SAMPLE_REQ.clone<never>({method: 'GET'}))).toThrowError(
|
||||
|
|
|
|||
Loading…
Reference in a new issue