From e59d1809714a222985fa147610adf92043988caa Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 18 May 2021 12:47:37 +0200 Subject: [PATCH] MOBILE-3691 recaptcha: Fix recaptcha in Ionic 5 --- src/assets/js/iframe-recaptcha.js | 42 ------ src/core/components/components.module.ts | 3 - .../recaptcha/core-recaptcha-modal.html | 14 -- .../components/recaptcha/recaptcha-modal.ts | 126 ------------------ src/core/components/recaptcha/recaptcha.ts | 53 +++++--- src/core/services/utils/iframe.ts | 6 - 6 files changed, 37 insertions(+), 207 deletions(-) delete mode 100644 src/assets/js/iframe-recaptcha.js delete mode 100644 src/core/components/recaptcha/core-recaptcha-modal.html delete mode 100644 src/core/components/recaptcha/recaptcha-modal.ts diff --git a/src/assets/js/iframe-recaptcha.js b/src/assets/js/iframe-recaptcha.js deleted file mode 100644 index f1ff5e843..000000000 --- a/src/assets/js/iframe-recaptcha.js +++ /dev/null @@ -1,42 +0,0 @@ -// (C) Copyright 2015 Moodle Pty Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -(function () { - var url = location.href; - - if (!url.match(/^https?:\/\//i) || !url.match(/\/webservice\/recaptcha\.php/i)) { - // Not the recaptcha script, stop. - return; - } - - // Define recaptcha callbacks. - window.recaptchacallback = function(value) { - window.parent.postMessage({ - environment: 'moodleapp', - context: 'recaptcha', - action: 'callback', - frameUrl: location.href, - value: value, - }, '*'); - }; - - window.recaptchaexpiredcallback = function() { - window.parent.postMessage({ - environment: 'moodleapp', - context: 'recaptcha', - action: 'expired', - frameUrl: location.href, - }, '*'); - }; -})(); \ No newline at end of file diff --git a/src/core/components/components.module.ts b/src/core/components/components.module.ts index f1a876dc6..93c356a3c 100644 --- a/src/core/components/components.module.ts +++ b/src/core/components/components.module.ts @@ -44,7 +44,6 @@ import { CoreNavBarButtonsComponent } from './navbar-buttons/navbar-buttons'; import { CoreNavigationBarComponent } from './navigation-bar/navigation-bar'; import { CoreProgressBarComponent } from './progress-bar/progress-bar'; import { CoreRecaptchaComponent } from './recaptcha/recaptcha'; -import { CoreRecaptchaModalComponent } from './recaptcha/recaptcha-modal'; import { CoreSendMessageFormComponent } from './send-message-form/send-message-form'; import { CoreShowPasswordComponent } from './show-password/show-password'; import { CoreSitePickerComponent } from './site-picker/site-picker'; @@ -84,7 +83,6 @@ import { CoreHorizontalScrollControlsComponent } from './horizontal-scroll-contr CoreNavigationBarComponent, CoreProgressBarComponent, CoreRecaptchaComponent, - CoreRecaptchaModalComponent, CoreSendMessageFormComponent, CoreShowPasswordComponent, CoreSitePickerComponent, @@ -131,7 +129,6 @@ import { CoreHorizontalScrollControlsComponent } from './horizontal-scroll-contr CoreNavigationBarComponent, CoreProgressBarComponent, CoreRecaptchaComponent, - CoreRecaptchaModalComponent, CoreSendMessageFormComponent, CoreShowPasswordComponent, CoreSitePickerComponent, diff --git a/src/core/components/recaptcha/core-recaptcha-modal.html b/src/core/components/recaptcha/core-recaptcha-modal.html deleted file mode 100644 index 9d8f70aa6..000000000 --- a/src/core/components/recaptcha/core-recaptcha-modal.html +++ /dev/null @@ -1,14 +0,0 @@ - - - {{ 'core.login.security_question' | translate }} - - - - - - - - - - - diff --git a/src/core/components/recaptcha/recaptcha-modal.ts b/src/core/components/recaptcha/recaptcha-modal.ts deleted file mode 100644 index 0cb9215cf..000000000 --- a/src/core/components/recaptcha/recaptcha-modal.ts +++ /dev/null @@ -1,126 +0,0 @@ -// (C) Copyright 2015 Moodle Pty Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { Component, Input, OnDestroy } from '@angular/core'; - -import { ModalController } from '@singletons'; - -/** - * Component to display a the recaptcha in a modal. - */ -@Component({ - selector: 'core-recaptcha-modal', - templateUrl: 'core-recaptcha-modal.html', -}) -export class CoreRecaptchaModalComponent implements OnDestroy { - - @Input() recaptchaUrl?: string; - - expired = false; - value = ''; - - protected messageListenerFunction: (event: MessageEvent) => Promise; - - constructor() { - // Listen for messages from the iframe. - this.messageListenerFunction = this.onIframeMessage.bind(this); - window.addEventListener('message', this.messageListenerFunction); - } - - /** - * Close modal. - */ - closeModal(): void { - ModalController.dismiss({ - expired: this.expired, - value: this.value, - }); - } - - /** - * The iframe with the recaptcha was loaded. - * - * @param iframe Iframe element. - */ - loaded(iframe: HTMLIFrameElement): void { - // Search the iframe content. - const contentWindow = iframe?.contentWindow; - - if (contentWindow) { - try { - // Set the callbacks we're interested in. - contentWindow['recaptchacallback'] = this.onRecaptchaCallback.bind(this); - contentWindow['recaptchaexpiredcallback'] = this.onRecaptchaExpiredCallback.bind(this); - } catch (error) { - // Cannot access the window. - } - } - } - - /** - * Treat an iframe message event. - * - * @param event Event. - * @return Promise resolved when done. - */ - protected async onIframeMessage(event: MessageEvent): Promise { - if (!event.data || event.data.environment != 'moodleapp' || event.data.context != 'recaptcha') { - return; - } - - switch (event.data.action) { - case 'callback': - this.onRecaptchaCallback(event.data.value); - break; - case 'expired': - this.onRecaptchaExpiredCallback(); - break; - - default: - break; - } - } - - /** - * Recapcha callback called. - * - * @param value Value received. - */ - protected onRecaptchaCallback(value: string): void { - this.expired = false; - this.value = value; - this.closeModal(); - } - - /** - * Recapcha expired callback called. - */ - protected onRecaptchaExpiredCallback(): void { - this.expired = true; - this.value = ''; - } - - /** - * Component destroyed. - */ - ngOnDestroy(): void { - window.removeEventListener('message', this.messageListenerFunction); - } - -} - -export type CoreRecaptchaModalReturn = { - expired: boolean; - value: string; -}; diff --git a/src/core/components/recaptcha/recaptcha.ts b/src/core/components/recaptcha/recaptcha.ts index 7d58839d4..987acf1b0 100644 --- a/src/core/components/recaptcha/recaptcha.ts +++ b/src/core/components/recaptcha/recaptcha.ts @@ -16,9 +16,8 @@ import { Component, Input, OnInit } from '@angular/core'; import { CoreLang } from '@services/lang'; import { CoreSites } from '@services/sites'; -import { CoreDomUtils } from '@services/utils/dom'; import { CoreTextUtils } from '@services/utils/text'; -import { CoreRecaptchaModalComponent, CoreRecaptchaModalReturn } from './recaptcha-modal'; +import { CoreUtils } from '@services/utils/utils'; /** * Component that allows answering a recaptcha. @@ -57,27 +56,49 @@ export class CoreRecaptchaComponent implements OnInit { } /** - * Open the recaptcha modal. + * Let the user answer the recaptcha. */ async answerRecaptcha(): Promise { - // Set the iframe src. We use an iframe because reCaptcha V2 doesn't work with file:// protocol. + // Open the recaptcha challenge in an InAppBrowser. + // The app used to use an iframe for this, but the app can no longer access the iframe to create the required callbacks. + // The app cannot render the recaptcha directly because it has problems with the local protocols and domains. const src = CoreTextUtils.concatenatePaths(this.siteUrl!, 'webservice/recaptcha.php?lang=' + this.lang); - // Modal to answer the recaptcha. - // This is because the size of the recaptcha is dynamic, so it could cause problems if it was displayed inline. + const inAppBrowserWindow = CoreUtils.openInApp(src); + if (!inAppBrowserWindow) { + return; + } - const modalData = await CoreDomUtils.openModal({ - component: CoreRecaptchaModalComponent, - cssClass: 'core-modal-fullscreen', - componentProps: { - recaptchaUrl: src, - }, + // Set the callbacks once the page is loaded. + const loadStopSubscription = inAppBrowserWindow.on('loadstop').subscribe(() => { + inAppBrowserWindow.executeScript({ + code: + 'window.recaptchacallback = (value) => webkit.messageHandlers.cordova_iab.postMessage(' + + 'JSON.stringify({ action: "callback", value }));' + + 'window.recaptchaexpiredcallback = () => webkit.messageHandlers.cordova_iab.postMessage(' + + 'JSON.stringify({ action: "expired" }));', + }); }); - if (modalData) { - this.expired = modalData.expired; - this.model![this.modelValueName] = modalData.value; - } + // Listen for events. + const messageSubscription = inAppBrowserWindow.on('message').subscribe((event) => { + if (!event.data) { + return; + } + + if (event.data.action == 'expired') { + this.expired = true; + this.model![this.modelValueName] = ''; + } else if (event.data.action == 'callback') { + this.expired = false; + this.model![this.modelValueName] = event.data.value; + + // Close the InAppBrowser now. + inAppBrowserWindow.close(); + messageSubscription.unsubscribe(); + loadStopSubscription.unsubscribe(); + } + }); } } diff --git a/src/core/services/utils/iframe.ts b/src/core/services/utils/iframe.ts index 1aa40105a..800883601 100644 --- a/src/core/services/utils/iframe.ts +++ b/src/core/services/utils/iframe.ts @@ -516,14 +516,8 @@ export class CoreIframeUtilsProvider { private injectiOSScripts(userScriptWindow: WKUserScriptWindow) { const wwwPath = CoreFile.getWWWAbsolutePath(); const linksPath = CoreTextUtils.concatenatePaths(wwwPath, 'assets/js/iframe-treat-links.js'); - const recaptchaPath = CoreTextUtils.concatenatePaths(wwwPath, 'assets/js/iframe-recaptcha.js'); userScriptWindow.WKUserScript?.addScript({ id: 'CoreIframeUtilsLinksScript', file: linksPath }); - userScriptWindow.WKUserScript?.addScript({ - id: 'CoreIframeUtilsRecaptchaScript', - file: recaptchaPath, - injectionTime: userScriptWindow.WKUserScript?.InjectionTime.END, - }); // Handle post messages received by iframes. window.addEventListener('message', this.handleIframeMessage.bind(this));