fix(ivy): validate props and attrs with "on" prefix at runtime (#28054)
Prior to this change we performed prop and attr name validation at compile time, which failed in case a given prop/attr is an input to a Directive (thus should not be a subject to this check). Since Directive matching in Ivy happens at runtime, the corresponding checks are now moved to runtime as well. PR Close #28054
This commit is contained in:
@ -10,7 +10,7 @@ import {Component, Directive, HostBinding, Input, NO_ERRORS_SCHEMA, ɵivyEnabled
|
||||
import {ComponentFixture, TestBed, getTestBed} from '@angular/core/testing';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {DomSanitizer} from '@angular/platform-browser/src/security/dom_sanitization_service';
|
||||
import {fixmeIvy} from '@angular/private/testing';
|
||||
import {fixmeIvy, modifiedInIvy, onlyInIvy} from '@angular/private/testing';
|
||||
|
||||
{
|
||||
if (ivyEnabled) {
|
||||
@ -52,47 +52,79 @@ function declareTests(config?: {useJit: boolean}) {
|
||||
afterEach(() => { getDOM().log = originalLog; });
|
||||
|
||||
describe('events', () => {
|
||||
it('should disallow binding to attr.on*', () => {
|
||||
const template = `<div [attr.onclick]="ctxProp"></div>`;
|
||||
TestBed.overrideComponent(SecuredComponent, {set: {template}});
|
||||
modifiedInIvy('on-prefixed attributes validation happens at runtime in Ivy')
|
||||
.it('should disallow binding to attr.on*', () => {
|
||||
const template = `<div [attr.onclick]="ctxProp"></div>`;
|
||||
TestBed.overrideComponent(SecuredComponent, {set: {template}});
|
||||
|
||||
expect(() => TestBed.createComponent(SecuredComponent))
|
||||
.toThrowError(
|
||||
/Binding to event attribute 'onclick' is disallowed for security reasons, please use \(click\)=.../);
|
||||
});
|
||||
expect(() => TestBed.createComponent(SecuredComponent))
|
||||
.toThrowError(
|
||||
/Binding to event attribute 'onclick' is disallowed for security reasons, please use \(click\)=.../);
|
||||
});
|
||||
|
||||
it('should disallow binding to on* with NO_ERRORS_SCHEMA', () => {
|
||||
const template = `<div [onclick]="ctxProp"></div>`;
|
||||
TestBed.overrideComponent(SecuredComponent, {set: {template}}).configureTestingModule({
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
});
|
||||
// this test is similar to the previous one, but since on-prefixed attributes validation now
|
||||
// happens at runtime, we need to invoke change detection to trigger elementProperty call
|
||||
onlyInIvy('on-prefixed attributes validation happens at runtime in Ivy')
|
||||
.it('should disallow binding to attr.on*', () => {
|
||||
const template = `<div [attr.onclick]="ctxProp"></div>`;
|
||||
TestBed.overrideComponent(SecuredComponent, {set: {template}});
|
||||
|
||||
expect(() => TestBed.createComponent(SecuredComponent))
|
||||
.toThrowError(
|
||||
/Binding to event property 'onclick' is disallowed for security reasons, please use \(click\)=.../);
|
||||
});
|
||||
expect(() => {
|
||||
const cmp = TestBed.createComponent(SecuredComponent);
|
||||
cmp.detectChanges();
|
||||
})
|
||||
.toThrowError(
|
||||
/Binding to event attribute 'onclick' is disallowed for security reasons, please use \(click\)=.../);
|
||||
});
|
||||
|
||||
fixmeIvy(
|
||||
'FW-786: Element properties and directive inputs are not distinguished for sanitisation purposes')
|
||||
.it('should disallow binding to on* unless it is consumed by a directive', () => {
|
||||
const template = `<div [onPrefixedProp]="ctxProp" [onclick]="ctxProp"></div>`;
|
||||
modifiedInIvy('on-prefixed attributes validation happens at runtime in Ivy')
|
||||
.it('should disallow binding to on* with NO_ERRORS_SCHEMA', () => {
|
||||
const template = `<div [onclick]="ctxProp"></div>`;
|
||||
TestBed.overrideComponent(SecuredComponent, {set: {template}}).configureTestingModule({
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
});
|
||||
|
||||
// should not throw for inputs starting with "on"
|
||||
let cmp: ComponentFixture<SecuredComponent> = undefined !;
|
||||
expect(() => cmp = TestBed.createComponent(SecuredComponent)).not.toThrow();
|
||||
|
||||
// must bind to the directive not to the property of the div
|
||||
const value = cmp.componentInstance.ctxProp = {};
|
||||
cmp.detectChanges();
|
||||
const div = cmp.debugElement.children[0];
|
||||
expect(div.injector.get(OnPrefixDir).onclick).toBe(value);
|
||||
expect(getDOM().getProperty(div.nativeElement, 'onclick')).not.toBe(value);
|
||||
expect(getDOM().hasAttribute(div.nativeElement, 'onclick')).toEqual(false);
|
||||
expect(() => TestBed.createComponent(SecuredComponent))
|
||||
.toThrowError(
|
||||
/Binding to event property 'onclick' is disallowed for security reasons, please use \(click\)=.../);
|
||||
});
|
||||
|
||||
// this test is similar to the previous one, but since on-prefixed attributes validation now
|
||||
// happens at runtime, we need to invoke change detection to trigger elementProperty call
|
||||
onlyInIvy('on-prefixed attributes validation happens at runtime in Ivy')
|
||||
.it('should disallow binding to on* with NO_ERRORS_SCHEMA', () => {
|
||||
const template = `<div [onclick]="ctxProp"></div>`;
|
||||
TestBed.overrideComponent(SecuredComponent, {set: {template}}).configureTestingModule({
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
const cmp = TestBed.createComponent(SecuredComponent);
|
||||
cmp.detectChanges();
|
||||
})
|
||||
.toThrowError(
|
||||
/Binding to event property 'onclick' is disallowed for security reasons, please use \(click\)=.../);
|
||||
});
|
||||
|
||||
it('should disallow binding to on* unless it is consumed by a directive', () => {
|
||||
const template = `<div [onPrefixedProp]="ctxProp" [onclick]="ctxProp"></div>`;
|
||||
TestBed.overrideComponent(SecuredComponent, {set: {template}}).configureTestingModule({
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
});
|
||||
|
||||
// should not throw for inputs starting with "on"
|
||||
let cmp: ComponentFixture<SecuredComponent> = undefined !;
|
||||
expect(() => cmp = TestBed.createComponent(SecuredComponent)).not.toThrow();
|
||||
|
||||
// must bind to the directive not to the property of the div
|
||||
const value = cmp.componentInstance.ctxProp = {};
|
||||
cmp.detectChanges();
|
||||
const div = cmp.debugElement.children[0];
|
||||
expect(div.injector.get(OnPrefixDir).onclick).toBe(value);
|
||||
expect(getDOM().getProperty(div.nativeElement, 'onclick')).not.toBe(value);
|
||||
expect(getDOM().hasAttribute(div.nativeElement, 'onclick')).toEqual(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('safe HTML values', function() {
|
||||
|
Reference in New Issue
Block a user