From cbefbbfacb37cb41709963cac0f5cb32a49d7bfb Mon Sep 17 00:00:00 2001 From: Kevin Dwan <25626770+krdwan@users.noreply.github.com> Date: Fri, 3 Apr 2020 11:51:07 -0400 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20global=20support=20for=20amp-?= =?UTF-8?q?recaptcha-input=20component.=20(#27450)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add global support for amp-recaptcha-input component * Update integration test for amp-recaptcha * Update global attribute to data-global and update unit tests for amp-recaptcha * Update naming of global property to global attribute --- 3p/recaptcha.js | 14 +++++++- .../0.1/amp-recaptcha-input.js | 10 +++++- .../0.1/amp-recaptcha-service.js | 17 +++++++++- .../0.1/test/test-amp-recaptcha-input.js | 2 +- .../0.1/test/test-amp-recaptcha-service.js | 34 ++++++++++++++++--- .../amp-recaptcha-input.md | 4 +++ test/integration/test-amp-recaptcha-input.js | 1 + test/unit/3p/test-recaptcha.js | 8 +++++ 8 files changed, 81 insertions(+), 9 deletions(-) diff --git a/3p/recaptcha.js b/3p/recaptcha.js index f552e4e99888..f172280336fe 100644 --- a/3p/recaptcha.js +++ b/3p/recaptcha.js @@ -27,7 +27,7 @@ import { setReportError, user, } from '../src/log'; -import {dict} from '../src/utils/object'; +import {dict, hasOwn} from '../src/utils/object'; import {isProxyOrigin, parseUrlDeprecated} from '../src/url'; import {loadScript} from './3p'; import {parseJson} from '../src/json'; @@ -56,6 +56,10 @@ const TAG = 'RECAPTCHA'; /** @const {string} */ const RECAPTCHA_API_URL = 'https://www.google.com/recaptcha/api.js?render='; +/** @const {string} */ +const GLOBAL_RECAPTCHA_API_URL = + 'https://www.recaptcha.net/recaptcha/api.js?render='; + /** {?IframeMessaginClient} */ let iframeMessagingClient = null; @@ -99,6 +103,14 @@ export function initRecaptcha(recaptchaApiBaseUrl = RECAPTCHA_API_URL) { 'The sitekey is required for the iframe' ); sitekey = dataObject.sitekey; + // Determine the recaptcha api URL based on global property + devAssert( + hasOwn(dataObject, 'global'), + 'The global property is required for the iframe' + ); + if (dataObject.global) { + recaptchaApiBaseUrl = GLOBAL_RECAPTCHA_API_URL; + } const recaptchaApiUrl = recaptchaApiBaseUrl + sitekey; loadScript( diff --git a/extensions/amp-recaptcha-input/0.1/amp-recaptcha-input.js b/extensions/amp-recaptcha-input/0.1/amp-recaptcha-input.js index 49799eddec3a..5b21111b1273 100644 --- a/extensions/amp-recaptcha-input/0.1/amp-recaptcha-input.js +++ b/extensions/amp-recaptcha-input/0.1/amp-recaptcha-input.js @@ -53,6 +53,9 @@ export class AmpRecaptchaInput extends AMP.BaseElement { /** @private {?Promise} */ this.registerPromise_ = null; + + /** @private {boolean} */ + this.global_ = false; } /** @override */ @@ -76,6 +79,8 @@ export class AmpRecaptchaInput extends AMP.BaseElement { this.element ); + this.global_ = this.element.hasAttribute('data-global'); + return recaptchaServiceForDoc(this.element).then((service) => { this.recaptchaService_ = service; @@ -108,7 +113,10 @@ export class AmpRecaptchaInput extends AMP.BaseElement { /** @override */ layoutCallback() { if (!this.registerPromise_ && this.sitekey_) { - this.registerPromise_ = this.recaptchaService_.register(this.sitekey_); + this.registerPromise_ = this.recaptchaService_.register( + this.sitekey_, + this.global_ + ); } return /** @type {!Promise} */ (this.registerPromise_); diff --git a/extensions/amp-recaptcha-input/0.1/amp-recaptcha-service.js b/extensions/amp-recaptcha-input/0.1/amp-recaptcha-service.js index 9906ba2631da..e3fba980ed88 100644 --- a/extensions/amp-recaptcha-input/0.1/amp-recaptcha-service.js +++ b/extensions/amp-recaptcha-input/0.1/amp-recaptcha-service.js @@ -88,15 +88,19 @@ export class AmpRecaptchaService { /** @private {Object} */ this.executeMap_ = {}; + + /** @private {boolean} */ + this.global_; } /** * Function to register as a dependant of the AmpRecaptcha serivce. * Used to create/destroy recaptcha boostrap iframe. * @param {string} sitekey + * @param {boolean} global * @return {Promise} */ - register(sitekey) { + register(sitekey, global = false) { if (!this.sitekey_) { this.sitekey_ = sitekey; } else if (this.sitekey_ !== sitekey) { @@ -107,6 +111,16 @@ export class AmpRecaptchaService { ) ); } + if (this.global_ === undefined) { + this.global_ = global; + } else if (this.global_ !== global) { + return Promise.reject( + new Error( + 'You must supply the data-global attribute ' + + 'to all or none of the amp-recaptcha-input elements.' + ) + ); + } this.registeredElementCount_++; if (!this.iframeLoadPromise_) { @@ -230,6 +244,7 @@ export class AmpRecaptchaService { dict({ 'sitekey': this.sitekey_, 'sentinel': 'amp-recaptcha', + 'global': this.global_, }) ) ); diff --git a/extensions/amp-recaptcha-input/0.1/test/test-amp-recaptcha-input.js b/extensions/amp-recaptcha-input/0.1/test/test-amp-recaptcha-input.js index 66d1afe0cbac..4c41e73d92b5 100644 --- a/extensions/amp-recaptcha-input/0.1/test/test-amp-recaptcha-input.js +++ b/extensions/amp-recaptcha-input/0.1/test/test-amp-recaptcha-input.js @@ -106,7 +106,7 @@ describes.realWin( }); }); - it('Should apply styles aftyer build', () => { + it('Should apply styles after build', () => { return getRecaptchaInput().then((ampRecaptchaInput) => { expect(win.getComputedStyle(ampRecaptchaInput).position).to.equal( 'absolute' diff --git a/extensions/amp-recaptcha-input/0.1/test/test-amp-recaptcha-service.js b/extensions/amp-recaptcha-input/0.1/test/test-amp-recaptcha-service.js index 40d27626819e..e91728c75294 100644 --- a/extensions/amp-recaptcha-input/0.1/test/test-amp-recaptcha-service.js +++ b/extensions/amp-recaptcha-input/0.1/test/test-amp-recaptcha-service.js @@ -109,16 +109,40 @@ describes.realWin( ' if they pass a different sitekey', () => { expect(recaptchaService.registeredElementCount_).to.be.equal(0); - return recaptchaService.register(fakeSitekey).then(() => { - expect(recaptchaService.registeredElementCount_).to.be.equal(1); - expect(recaptchaService.iframe_).to.be.ok; + return recaptchaService + .register(fakeSitekey) + .then(() => { + expect(recaptchaService.registeredElementCount_).to.be.equal(1); + expect(recaptchaService.iframe_).to.be.ok; + return recaptchaService.register(anotherFakeSitekey); + }) + .catch((err) => { + expect(err).to.be.ok; + expect(err.message).to.have.string('sitekey'); + expect(recaptchaService.registeredElementCount_).to.be.equal(1); + expect(recaptchaService.iframe_).to.be.ok; + }); + } + ); - return recaptchaService.register(anotherFakeSitekey).catch((err) => { + it( + 'should not allow elements to register,' + + ' if they do not all or none pass in the global attribute', + () => { + expect(recaptchaService.registeredElementCount_).to.be.equal(0); + return recaptchaService + .register(fakeSitekey, true) + .then(() => { + expect(recaptchaService.registeredElementCount_).to.be.equal(1); + expect(recaptchaService.iframe_).to.be.ok; + return recaptchaService.register(fakeSitekey, false); + }) + .catch((err) => { expect(err).to.be.ok; + expect(err.message).to.have.string('data-global'); expect(recaptchaService.registeredElementCount_).to.be.equal(1); expect(recaptchaService.iframe_).to.be.ok; }); - }); } ); diff --git a/extensions/amp-recaptcha-input/amp-recaptcha-input.md b/extensions/amp-recaptcha-input/amp-recaptcha-input.md index 861267fe829b..2ece9847ed94 100644 --- a/extensions/amp-recaptcha-input/amp-recaptcha-input.md +++ b/extensions/amp-recaptcha-input/amp-recaptcha-input.md @@ -78,6 +78,10 @@ grecaptcha.execute('reCAPTCHA_site_key', {action: 'reCAPTCHA_example_action'}); data-action (required) reCAPTCHA v3 action to be executed on form submission. + + data-global [optional] + By default, the iframe loads the recaptcha api script using the www.google.com endpoint. There are some situations when this is not accessible. When the data-global attribute is included, the component will load the script from the www.recaptcha.net endpoint instead. More information can be found in the reCAPTCHA FAQ. + ## Validation diff --git a/test/integration/test-amp-recaptcha-input.js b/test/integration/test-amp-recaptcha-input.js index 48517099f1cb..8e58b8c65429 100644 --- a/test/integration/test-amp-recaptcha-input.js +++ b/test/integration/test-amp-recaptcha-input.js @@ -136,6 +136,7 @@ describe JSON.stringify({ 'sitekey': '6LebBGoUAAAAAHbj1oeZMBU_rze_CutlbyzpH8VE', 'sentinel': 'amp-recaptcha', + 'global': false, }) ); }); diff --git a/test/unit/3p/test-recaptcha.js b/test/unit/3p/test-recaptcha.js index 6d1b3afe6a61..4e6c4dd95672 100644 --- a/test/unit/3p/test-recaptcha.js +++ b/test/unit/3p/test-recaptcha.js @@ -35,6 +35,14 @@ describe('3p recaptcha.js', () => { }); }); + it('should require a global property in the window.name dataObject', () => { + allowConsoleError(() => { + window.name = '{"sitekey":"sitekey"}'; + expect(initRecaptcha).to.throw('global'); + window.name = undefined; + }); + }); + describe('doesOriginDomainMatchIframeSrc()', () => { const getMockIframeWindowWithLocation = (url) => { // NOTE: The thirdPartyUrl returned by the config