forked from angular/angular
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core): add API to provide CSP nonce for inline stylesheets
Angular uses inline styles to insert the styles associated with a component. This violates the strict styles [Content Security Policy](https://web.dev/strict-csp/) which doesn't allow inline styles by default. One way to allow the styles to be applied is to set a `nonce` attribute on them, but because the code for inserting the stylesheets is deep inside the framework, users weren't able to provide it without accessing private APIs. These changes add a new `CSP_NONCE` injection token that will allow users to provide a nonce, if their app is using CSP. If the token isn't provided, the framework will look for an `ngCspNonce` attribute on the app's root node instead. The latter approach is provided as a convenience for apps that render the `index.html` through a server, e.g. `<app ngCspNonce="{% randomNonceAddedByTheServer %}"></app>`. This PR addresses adding the nonce to framework-generated styles. There will be follow-up PRs that add support for it in critical CSS tags in the CLI, and in Angular Material. Fixes angular#6361.
- Loading branch information
Showing
14 changed files
with
261 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
/*! | ||
* @license | ||
* Copyright Google LLC All Rights Reserved. | ||
* | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
|
||
import {Component, CSP_NONCE, destroyPlatform, ElementRef, inject, ViewEncapsulation} from '@angular/core'; | ||
import {bootstrapApplication} from '@angular/platform-browser'; | ||
import {withBody} from '@angular/private/testing'; | ||
|
||
describe('CSP integration', () => { | ||
beforeEach(destroyPlatform); | ||
afterEach(destroyPlatform); | ||
|
||
const testStyles = '.a { color: var(--csp-test-var, hotpink); }'; | ||
|
||
function findTestNonces(rootNode: ParentNode): string[] { | ||
const styles = rootNode.querySelectorAll('style'); | ||
const nonces: string[] = []; | ||
|
||
for (let i = 0; i < styles.length; i++) { | ||
const style = styles[i]; | ||
if (style.textContent?.includes('--csp-test-var') && style.nonce) { | ||
nonces.push(style.nonce); | ||
} | ||
} | ||
|
||
return nonces; | ||
} | ||
|
||
it('should use the predefined ngCspNonce when inserting styles with emulated encapsulation', | ||
withBody('<app ngCspNonce="emulated-nonce"></app>', async () => { | ||
@Component({ | ||
selector: 'uses-styles', | ||
template: '', | ||
styles: [testStyles], | ||
standalone: true, | ||
encapsulation: ViewEncapsulation.Emulated | ||
}) | ||
class UsesStyles { | ||
} | ||
|
||
@Component({ | ||
selector: 'app', | ||
standalone: true, | ||
template: '<uses-styles></uses-styles>', | ||
imports: [UsesStyles] | ||
}) | ||
class App { | ||
} | ||
|
||
const appRef = await bootstrapApplication(App); | ||
|
||
expect(findTestNonces(document)).toEqual(['emulated-nonce']); | ||
|
||
appRef.destroy(); | ||
})); | ||
|
||
it('should use the predefined ngCspNonce when inserting styles with no encapsulation', | ||
withBody('<app ngCspNonce="disabled-nonce"></app>', async () => { | ||
@Component({ | ||
selector: 'uses-styles', | ||
template: '', | ||
styles: [testStyles], | ||
standalone: true, | ||
encapsulation: ViewEncapsulation.None | ||
}) | ||
class UsesStyles { | ||
} | ||
|
||
@Component({ | ||
selector: 'app', | ||
standalone: true, | ||
template: '<uses-styles></uses-styles>', | ||
imports: [UsesStyles] | ||
}) | ||
class App { | ||
} | ||
|
||
const appRef = await bootstrapApplication(App); | ||
|
||
expect(findTestNonces(document)).toEqual(['disabled-nonce']); | ||
|
||
appRef.destroy(); | ||
})); | ||
|
||
|
||
it('should use the predefined ngCspNonce when inserting styles with shadow DOM encapsulation', | ||
withBody('<app ngCspNonce="shadow-nonce"></app>', async () => { | ||
if (!document.body.attachShadow) { | ||
return; | ||
} | ||
|
||
let usesStylesRootNode!: HTMLElement; | ||
|
||
@Component({ | ||
selector: 'uses-styles', | ||
template: '', | ||
styles: [testStyles], | ||
standalone: true, | ||
encapsulation: ViewEncapsulation.ShadowDom | ||
}) | ||
class UsesStyles { | ||
constructor() { | ||
usesStylesRootNode = inject(ElementRef).nativeElement; | ||
} | ||
} | ||
|
||
@Component({ | ||
selector: 'app', | ||
standalone: true, | ||
template: '<uses-styles></uses-styles>', | ||
imports: [UsesStyles] | ||
}) | ||
class App { | ||
} | ||
|
||
const appRef = await bootstrapApplication(App); | ||
|
||
expect(findTestNonces(usesStylesRootNode.shadowRoot!)).toEqual(['shadow-nonce']); | ||
|
||
appRef.destroy(); | ||
})); | ||
|
||
it('should prefer nonce provided through DI over one provided in the DOM', | ||
withBody('<app ngCspNonce="dom-nonce"></app>', async () => { | ||
@Component({selector: 'uses-styles', template: '', styles: [testStyles], standalone: true}) | ||
class UsesStyles { | ||
} | ||
|
||
@Component({ | ||
selector: 'app', | ||
standalone: true, | ||
template: '<uses-styles></uses-styles>', | ||
imports: [UsesStyles] | ||
}) | ||
class App { | ||
} | ||
|
||
const appRef = await bootstrapApplication(App, { | ||
providers: [{provide: CSP_NONCE, useValue: 'di-nonce'}], | ||
}); | ||
|
||
expect(findTestNonces(document)).toEqual(['di-nonce']); | ||
|
||
appRef.destroy(); | ||
})); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.