import { OpenYoloInternalError } from '../protocol/errors' ; import { PostMessageType , verifyAckMessage , verifyPingMessage } from '../protocol/post_messages' ; import { MockWindow } from '../test_utils/frames' ; import { createMessageEvent } from '../test_utils/messages' ; import { JasmineTimeoutManager } from '../test_utils/timeout' ; import { AncestorOriginVerifier } from './ancestor_origin_verifier' ; describe ( 'AncestorOriginVerifier' , ( ) => { const TIMEOUT = 1000 ; let parentFrame ; let providerFrame ; let permittedOrigins ; let verifier ; let timeoutManager = new JasmineTimeoutManager ( ) ; beforeEach ( ( ) => { parentFrame = new MockWindow ( ) ; providerFrame = new MockWindow ( parentFrame ) ; permittedOrigins = [ 'https://www.example.com' , 'https://auth.example.com' ] ; verifier = new AncestorOriginVerifier ( providerFrame , permittedOrigins , TIMEOUT ) ; timeoutManager . install ( ) ; } ) ; afterEach ( ( ) => { timeoutManager . uninstall ( ) ; } ) ; describe ( 'verifyAncestorOrigin' , ( ) => { it ( 'should post a ping message and listen for response' , ( ) => { let addEventListenerSpy = spyOn ( providerFrame , 'addEventListener' ) ; let postMessageSpy = spyOn ( parentFrame , 'postMessage' ) ; verifier . verifyAncestorOrigin ( parentFrame , 0 ) ; expect ( addEventListenerSpy ) . toHaveBeenCalledWith ( 'message' , jasmine . anything ( ) ) ; expect ( postMessageSpy ) . toHaveBeenCalledWith ( jasmine . objectContaining ( { type : PostMessageType . verifyPing , data : jasmine . anything ( ) } ) , '*' ) ; } ) ; it ( 'should timeout if no response arrives' , ( done ) => { let expectFailNow = false ; verifier . verifyAncestorOrigin ( parentFrame , 0 ) . then ( ( ) => { done . fail ( 'Verification should not succeed' ) ; } , ( err ) => { expect ( expectFailNow ) . toBeTruthy ( 'Failed too early' ) ; expect ( err ) . toEqual ( OpenYoloInternalError . parentVerifyTimeout ( ) ) ; done ( ) ; } ) ; jasmine . clock ( ) . tick ( TIMEOUT - 1 ) ; expectFailNow = true ; jasmine . clock ( ) . tick ( 1 ) ; } ) ; it ( 'should resolve if a valid response arrives' , async function ( done ) { let parentOrigin = permittedOrigins [ 0 ] ; parentFrame . addEventListener ( 'message' , ( ev ) => { expect ( ev . data . type ) . toBe ( PostMessageType . verifyPing ) ; providerFrame . postMessageFromOrigin ( verifyAckMessage ( ev . data . data ) , null , parentOrigin , parentFrame ) ; } ) ; let result = await verifier . verifyAncestorOrigin ( parentFrame , 0 ) ; expect ( result ) . toEqual ( parentOrigin ) ; done ( ) ; } ) ; it ( 'should reject the promise if the response comes from an invalid origin' , async function ( done ) { let parentOrigin = 'https://www.3vil.com' ; let pingReceived = false ; parentFrame . addEventListener ( 'message' , ( ev ) => { expect ( ev . data . type ) . toBe ( PostMessageType . verifyPing ) ; pingReceived = true ; providerFrame . postMessageFromOrigin ( verifyAckMessage ( ev . data . data ) , null , parentOrigin , parentFrame ) ; } ) ; let verifyPromise = verifier . verifyAncestorOrigin ( parentFrame , 0 ) ; try { await verifyPromise ; done . fail ( 'Verification should not succeed' ) ; } catch ( err ) { expect ( pingReceived ) . toBeTruthy ( ) ; expect ( err ) . toEqual ( OpenYoloInternalError . untrustedOrigin ( parentOrigin ) ) ; done ( ) ; } } ) ; it ( 'should ignore different verification nonces' , async function ( done ) { let parentOrigin = permittedOrigins [ 0 ] ; parentFrame . addEventListener ( 'message' , ( ev ) => { expect ( ev . data . type ) . toBe ( PostMessageType . verifyPing ) ; providerFrame . postMessageFromOrigin ( verifyAckMessage ( 'differentNonce' ) , null , parentOrigin , parentFrame ) ; } ) ; let verifyPromise = verifier . verifyAncestorOrigin ( parentFrame , 0 ) ; jasmine . clock ( ) . tick ( TIMEOUT ) ; try { await verifyPromise ; done . fail ( 'Verification should not succeed' ) ; } catch ( err ) { expect ( err ) . toEqual ( OpenYoloInternalError . parentVerifyTimeout ( ) ) ; done ( ) ; } } ) ; it ( 'should ignore messages of the wrong type' , async function ( done ) { let parentOrigin = permittedOrigins [ 0 ] ; parentFrame . addEventListener ( 'message' , ( ev ) => { expect ( ev . data . type ) . toBe ( PostMessageType . verifyPing ) ; providerFrame . postMessageFromOrigin ( verifyPingMessage ( ev . data . data ) , null , parentOrigin , parentFrame ) ; } ) ; let verifyPromise = verifier . verifyAncestorOrigin ( parentFrame , 0 ) ; jasmine . clock ( ) . tick ( TIMEOUT ) ; try { await verifyPromise ; done . fail ( 'Verification should not succeed' ) ; } catch ( err ) { expect ( err ) . toEqual ( OpenYoloInternalError . parentVerifyTimeout ( ) ) ; done ( ) ; } } ) ; it ( 'should ignore messages that are not sourced from the window' , async function ( done ) { let parentOrigin = permittedOrigins [ 0 ] ; let pingReceived = false ; parentFrame . addEventListener ( 'message' , ( ev ) => { expect ( ev . data . type ) . toBe ( PostMessageType . verifyPing ) ; pingReceived = true ; providerFrame . dispatchEvent ( createMessageEvent ( verifyAckMessage ( ev . data . data ) , parentOrigin , undefined , new MockWindow ( ) ) ) ; } ) ; let verifyPromise = verifier . verifyAncestorOrigin ( parentFrame , 0 ) ; jasmine . clock ( ) . tick ( TIMEOUT ) ; try { await verifyPromise ; done . fail ( 'Verification should not succeed' ) ; } catch ( err ) { expect ( pingReceived ) . toBeTruthy ( ) ; expect ( err ) . toEqual ( OpenYoloInternalError . parentVerifyTimeout ( ) ) ; done ( ) ; } } ) ; } ) ; describe ( 'verify' , ( ) => { describe ( 'single parent allowed' , ( ) => { it ( 'should resolve the promise when parent is valid' , async function ( done ) { let parentOrigin = permittedOrigins [ 0 ] ; spyOn ( verifier , 'verifyAncestorOrigin' ) . and . returnValue ( Promise . resolve ( parentOrigin ) ) ; try { let result = await verifier . verify ( false ) ; expect ( result ) . toEqual ( [ parentOrigin ] ) ; done ( ) ; } catch ( err ) { done . fail ( 'Promise was rejected' ) ; } } ) ; it ( 'should reject the promise when the parent is invalid' , async function ( done ) { let parentOrigin = 'https://www.3vil.com' ; let expectedError = OpenYoloInternalError . untrustedOrigin ( parentOrigin ) ; spyOn ( verifier , 'verifyAncestorOrigin' ) . and . returnValue ( Promise . reject ( expectedError ) ) ; try { await verifier . verify ( false ) ; done . fail ( 'Promise should not resolve' ) ; } catch ( err ) { expect ( err ) . toEqual ( expectedError ) ; done ( ) ; } } ) ; it ( 'should reject the promise if more than one ancestor frame' , async function ( done ) { let ancestorFrame = new MockWindow ( ) ; parentFrame . parent = ancestorFrame ; try { await verifier . verify ( false ) ; done . fail ( 'Promise should not resolve' ) ; } catch ( err ) { expect ( err ) . toEqual ( OpenYoloInternalError . parentIsNotRoot ( ) ) ; done ( ) ; } } ) ; } ) ; describe ( 'multiple ancestors allowed' , ( ) => { let ancestorFrame ; beforeEach ( ( ) => { ancestorFrame = new MockWindow ( ) ; parentFrame . parent = ancestorFrame ; } ) ; it ( 'should resolve the promise if all ancestors valid' , async function ( done ) { spyOn ( verifier , 'verifyAncestorOrigin' ) . and . callFake ( ( ancestor ) => { if ( ancestor === parentFrame ) { return Promise . resolve ( permittedOrigins [ 1 ] ) ; } else if ( ancestor === ancestorFrame ) { return Promise . resolve ( permittedOrigins [ 0 ] ) ; } done . fail ( 'unknown frame' ) ; } ) ; try { let result = await verifier . verify ( true ) ; expect ( result ) . toEqual ( [ permittedOrigins [ 1 ] , permittedOrigins [ 0 ] ] ) ; done ( ) ; } catch ( err ) { done . fail ( 'Promise rejected' ) ; } } ) ; it ( 'should reject the promise if any ancestor is invalid' , async function ( done ) { let expectedError = OpenYoloInternalError . untrustedOrigin ( 'https://www.3vil.com' ) ; spyOn ( verifier , 'verifyAncestorOrigin' ) . and . callFake ( ( ancestor ) => { if ( ancestor === parentFrame ) { return Promise . resolve ( permittedOrigins [ 0 ] ) ; } else if ( ancestor === ancestorFrame ) { return Promise . reject ( expectedError ) ; } done . fail ( 'unknown frame' ) ; } ) ; try { await verifier . verify ( true ) ; done . fail ( 'Promise should not resolve' ) ; } catch ( err ) { expect ( err ) . toEqual ( expectedError ) ; done ( ) ; } } ) ; } ) ; } ) ; } ) ;