diff --git a/extensions/amp-bind/0.1/amp-state.js b/extensions/amp-bind/0.1/amp-state.js index 5b5144274bcc..dafa5a078ede 100644 --- a/extensions/amp-bind/0.1/amp-state.js +++ b/extensions/amp-bind/0.1/amp-state.js @@ -152,7 +152,7 @@ export class AmpState extends AMP.BaseElement { policy = UrlReplacementPolicy.ALL; } - return getViewerAuthTokenIfAvailable(ampdoc.win, element).then(token => + return getViewerAuthTokenIfAvailable(element).then(token => this.fetch_(ampdoc, element, policy, opt_refresh, token) ); } diff --git a/extensions/amp-form/0.1/amp-form.js b/extensions/amp-form/0.1/amp-form.js index a63045da2228..f494aa64a66d 100644 --- a/extensions/amp-form/0.1/amp-form.js +++ b/extensions/amp-form/0.1/amp-form.js @@ -288,7 +288,7 @@ export class AmpForm { }), }; - return getViewerAuthTokenIfAvailable(this.win_, this.form_).then(token => { + return getViewerAuthTokenIfAvailable(this.form_).then(token => { if (token) { userAssert(request.fetchOpt['method'] == 'POST', 'Cannot attach auth token with GET request.'); diff --git a/extensions/amp-list/0.1/amp-list.js b/extensions/amp-list/0.1/amp-list.js index 4295dce7f787..0d9ae9c0f5e1 100644 --- a/extensions/amp-list/0.1/amp-list.js +++ b/extensions/amp-list/0.1/amp-list.js @@ -1002,7 +1002,7 @@ export class AmpList extends AMP.BaseElement { * @private */ prepareAndSendFetch_(opt_refresh = false) { - return getViewerAuthTokenIfAvailable(this.win, this.element).then(token => + return getViewerAuthTokenIfAvailable(this.element).then(token => this.fetch_(opt_refresh, token) ); } diff --git a/src/batched-json.js b/src/batched-json.js index 852e6506da9a..e94d1400887d 100644 --- a/src/batched-json.js +++ b/src/batched-json.js @@ -56,7 +56,7 @@ export function batchFetchJsonFor( const xhr = Services.batchedXhrFor(ampdoc.win); return requestForBatchFetch(element, opt_urlReplacement, opt_refresh) .then(data => { - if (opt_token) { + if (opt_token !== undefined) { data.fetchOpt['method'] = 'POST'; data.fetchOpt['headers'] = { 'Content-Type': 'application/x-www-form-urlencoded', diff --git a/src/utils/xhr-utils.js b/src/utils/xhr-utils.js index de71b3198d82..ed6e347165af 100644 --- a/src/utils/xhr-utils.js +++ b/src/utils/xhr-utils.js @@ -414,22 +414,24 @@ export function assertSuccess(response) { * Returns a promise resolving to a string identity token if the element * contains the 'crossorigin' attribute and the amp-viewer-assistance extension * is present. Resolves to undefined otherwise. - * @param {!Window} win * @param {!Element} element - * @return {!Promise} + * @return {!Promise} */ -export function getViewerAuthTokenIfAvailable(win, element) { +export function getViewerAuthTokenIfAvailable(element) { const crossOriginAttr = element.getAttribute('cross-origin') || element.getAttribute('crossorigin'); - if (crossOriginAttr && - crossOriginAttr.trim() === 'amp-viewer-auth-token-via-post') { + if (crossOriginAttr + && crossOriginAttr.trim() === 'amp-viewer-auth-token-via-post') { return Services.viewerAssistanceForDocOrNull(element) - .then(viewerAssistance => { - userAssert(viewerAssistance, - 'crossorigin="amp-viewer-auth-token-post" ' + - 'requires amp-viewer-assistance extension.'); - return viewerAssistance.getIdTokenPromise(); - }); + .then(va => { + userAssert(va, 'crossorigin="amp-viewer-auth-token-post" ' + + 'requires amp-viewer-assistance extension.'); + return va.getIdTokenPromise(); + }) + // If crossorigin attr is present, resolve with token or empty string. + .then(token => token || '') + .catch(() => ''); } - return Promise.resolve(); + // If crossorigin attribute is missing, always resolve with undefined. + return Promise.resolve(undefined); } diff --git a/test/unit/test-batched-json.js b/test/unit/test-batched-json.js index 4d1bd3deedde..4b077bf1b2f2 100644 --- a/test/unit/test-batched-json.js +++ b/test/unit/test-batched-json.js @@ -108,7 +108,7 @@ describe('batchFetchJsonFor', () => { }); describe('POST based identity', () => { - it('should send POST request with auth token if present', () => { + it('should send POST request with auth token is present', () => { const el = element('https://data.com'); const all = UrlReplacementPolicy.ALL; @@ -131,7 +131,42 @@ describe('batchFetchJsonFor', () => { 'https://data.com', expectedRequest); }); }); - }); + it('should send POST request with empty, defined identity token', () => { + const el = element('https://data.com'); + const all = UrlReplacementPolicy.ALL; + + urlReplacements.expandUrlAsync + .withArgs('https://data.com') + .returns(Promise.resolve('https://data.com')); + + const token = ''; + return batchFetchJsonFor(ampdoc, el, null, all, false, token) + .then(() => { + const fetchOpt = fetchJson.firstCall.args[1]; + expect(fetchOpt.body.ampViewerAuthToken).to.equal(''); + expect(fetchOpt.method).to.equal('POST'); + }); + }); + + it('should not transform the request with an undefined token', () => { + const el = element('https://data.com'); + const all = UrlReplacementPolicy.ALL; + + urlReplacements.expandUrlAsync + .withArgs('https://data.com') + .returns(Promise.resolve('https://data.com')); + + const expectedRequest = { + 'requireAmpResponseSourceOrigin': false, + }; + + return batchFetchJsonFor(ampdoc, el, null, all, false) + .then(() => { + expect(fetchJson).to.be.calledWithExactly( + 'https://data.com', expectedRequest); + }); + }); + }); // TODO(choumx): Add tests for normal fetch functionality. }); diff --git a/test/unit/utils/test-xhr-utils.js b/test/unit/utils/test-xhr-utils.js index 02adc6ca1dd2..147d2e381de9 100644 --- a/test/unit/utils/test-xhr-utils.js +++ b/test/unit/utils/test-xhr-utils.js @@ -16,6 +16,7 @@ import { ALLOW_SOURCE_ORIGIN_HEADER, + getViewerAuthTokenIfAvailable, getViewerInterceptResponse, setupAMPCors, setupInit, @@ -202,4 +203,76 @@ describes.sandboxed('utils/xhr-utils', {}, env => { }); }); + describe('getViewerAuthTokenIfAvailable', () => { + it('should return undefined if crossorigin attr is not present', () => { + const el = document.createElement('html'); + return getViewerAuthTokenIfAvailable(el) + .then(token => { + expect(token).to.equal(undefined); + }); + }); + + it('should return undefined if crossorigin attr does not contain ' + + 'exactly "amp-viewer-auth-token-post"', () => { + const el = document.createElement('html'); + el.setAttribute('crossorigin', ''); + return getViewerAuthTokenIfAvailable(el) + .then(token => { + expect(token).to.be.undefined; + }); + }); + + it('should return an auth token if one is present', () => { + sandbox.stub(Services, 'viewerAssistanceForDocOrNull').returns( + Promise.resolve({ + getIdTokenPromise: (() => Promise.resolve('idToken')), + })); + const el = document.createElement('html'); + el.setAttribute('crossorigin', 'amp-viewer-auth-token-via-post'); + return getViewerAuthTokenIfAvailable(el) + .then(token => { + expect(token).to.equal('idToken'); + }); + }); + + it('should return an empty auth token if there is not one present', () => { + sandbox.stub(Services, 'viewerAssistanceForDocOrNull').returns( + Promise.resolve({ + getIdTokenPromise: (() => Promise.resolve(undefined)), + })); + const el = document.createElement('html'); + el.setAttribute('crossorigin', 'amp-viewer-auth-token-via-post'); + return getViewerAuthTokenIfAvailable(el) + .then(token => { + expect(token).to.equal(''); + }); + }); + + it('should return an empty auth token if there is an issue retrieving ' + + 'the identity token', () => { + sandbox.stub(Services, 'viewerAssistanceForDocOrNull').returns( + Promise.reject({ + getIdTokenPromise: (() => Promise.reject()), + })); + const el = document.createElement('html'); + el.setAttribute('crossorigin', 'amp-viewer-auth-token-via-post'); + return getViewerAuthTokenIfAvailable(el) + .then(token => { + expect(token).to.equal(''); + }); + }); + + it('should assert that amp-viewer-assistance extension is present', () => { + sandbox.stub(Services, 'viewerAssistanceForDocOrNull').returns( + Promise.resolve()); + const el = document.createElement('html'); + el.setAttribute('crossorigin', 'amp-viewer-auth-token-via-post'); + expectAsyncConsoleError( + 'crossorigin="amp-viewer-auth-token-post" ' + + 'requires amp-viewer-assistance extension.', 1); + return getViewerAuthTokenIfAvailable(el) + .then(undefined, + e => expect(e).to.not.be.undefined); + }); + }); });