commit
cdba087c6c
|
@ -8154,6 +8154,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/cordova-plugin-network-information/-/cordova-plugin-network-information-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/cordova-plugin-network-information/-/cordova-plugin-network-information-2.0.2.tgz",
|
||||||
"integrity": "sha512-NwO3qDBNL/vJxUxBTPNOA1HvkDf9eTeGH8JSZiwy1jq2W2mJKQEDBwqWkaEQS19Yd/MQTiw0cykxg5D7u4J6cQ=="
|
"integrity": "sha512-NwO3qDBNL/vJxUxBTPNOA1HvkDf9eTeGH8JSZiwy1jq2W2mJKQEDBwqWkaEQS19Yd/MQTiw0cykxg5D7u4J6cQ=="
|
||||||
},
|
},
|
||||||
|
"cordova-plugin-prevent-override": {
|
||||||
|
"version": "git+https://github.com/moodlemobile/cordova-plugin-prevent-override.git#49507eda3c929e488e58b8402cfc7e1521ebc400",
|
||||||
|
"from": "git+https://github.com/moodlemobile/cordova-plugin-prevent-override.git",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"cordova-plugin-qrscanner": {
|
"cordova-plugin-qrscanner": {
|
||||||
"version": "git+https://github.com/moodlemobile/cordova-plugin-qrscanner.git#857efee3a7a49104faabd108ff1f00a57d3aca94",
|
"version": "git+https://github.com/moodlemobile/cordova-plugin-qrscanner.git#857efee3a7a49104faabd108ff1f00a57d3aca94",
|
||||||
"from": "git+https://github.com/moodlemobile/cordova-plugin-qrscanner.git#dist",
|
"from": "git+https://github.com/moodlemobile/cordova-plugin-qrscanner.git#dist",
|
||||||
|
|
|
@ -145,6 +145,7 @@
|
||||||
"@typescript-eslint/eslint-plugin": "^4.22.0",
|
"@typescript-eslint/eslint-plugin": "^4.22.0",
|
||||||
"@typescript-eslint/parser": "^4.22.0",
|
"@typescript-eslint/parser": "^4.22.0",
|
||||||
"check-es-compat": "^1.1.1",
|
"check-es-compat": "^1.1.1",
|
||||||
|
"cordova-plugin-prevent-override": "git+https://github.com/moodlemobile/cordova-plugin-prevent-override.git",
|
||||||
"eslint": "^7.25.0",
|
"eslint": "^7.25.0",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-plugin-header": "^3.1.1",
|
"eslint-plugin-header": "^3.1.1",
|
||||||
|
@ -231,7 +232,8 @@
|
||||||
"ANDROID_SUPPORT_VERSION": "28.+"
|
"ANDROID_SUPPORT_VERSION": "28.+"
|
||||||
},
|
},
|
||||||
"cordova-plugin-globalization": {},
|
"cordova-plugin-globalization": {},
|
||||||
"cordova-plugin-file-transfer": {}
|
"cordova-plugin-file-transfer": {},
|
||||||
|
"cordova-plugin-prevent-override": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
|
|
|
@ -167,6 +167,11 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||||
CoreWindow.open(url, name);
|
CoreWindow.open(url, name);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Treat URLs that try to override the app.
|
||||||
|
win.onOverrideUrlLoading = (url: string) => {
|
||||||
|
CoreWindow.open(url);
|
||||||
|
};
|
||||||
|
|
||||||
CoreEvents.on(CoreEvents.LOGIN, async (data) => {
|
CoreEvents.on(CoreEvents.LOGIN, async (data) => {
|
||||||
if (data.siteId) {
|
if (data.siteId) {
|
||||||
const site = await CoreSites.getSite(data.siteId);
|
const site = await CoreSites.getSite(data.siteId);
|
||||||
|
|
|
@ -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,
|
|
||||||
}, '*');
|
|
||||||
};
|
|
||||||
})();
|
|
|
@ -44,7 +44,6 @@ import { CoreNavBarButtonsComponent } from './navbar-buttons/navbar-buttons';
|
||||||
import { CoreNavigationBarComponent } from './navigation-bar/navigation-bar';
|
import { CoreNavigationBarComponent } from './navigation-bar/navigation-bar';
|
||||||
import { CoreProgressBarComponent } from './progress-bar/progress-bar';
|
import { CoreProgressBarComponent } from './progress-bar/progress-bar';
|
||||||
import { CoreRecaptchaComponent } from './recaptcha/recaptcha';
|
import { CoreRecaptchaComponent } from './recaptcha/recaptcha';
|
||||||
import { CoreRecaptchaModalComponent } from './recaptcha/recaptcha-modal';
|
|
||||||
import { CoreSendMessageFormComponent } from './send-message-form/send-message-form';
|
import { CoreSendMessageFormComponent } from './send-message-form/send-message-form';
|
||||||
import { CoreShowPasswordComponent } from './show-password/show-password';
|
import { CoreShowPasswordComponent } from './show-password/show-password';
|
||||||
import { CoreSitePickerComponent } from './site-picker/site-picker';
|
import { CoreSitePickerComponent } from './site-picker/site-picker';
|
||||||
|
@ -84,7 +83,6 @@ import { CoreHorizontalScrollControlsComponent } from './horizontal-scroll-contr
|
||||||
CoreNavigationBarComponent,
|
CoreNavigationBarComponent,
|
||||||
CoreProgressBarComponent,
|
CoreProgressBarComponent,
|
||||||
CoreRecaptchaComponent,
|
CoreRecaptchaComponent,
|
||||||
CoreRecaptchaModalComponent,
|
|
||||||
CoreSendMessageFormComponent,
|
CoreSendMessageFormComponent,
|
||||||
CoreShowPasswordComponent,
|
CoreShowPasswordComponent,
|
||||||
CoreSitePickerComponent,
|
CoreSitePickerComponent,
|
||||||
|
@ -131,7 +129,6 @@ import { CoreHorizontalScrollControlsComponent } from './horizontal-scroll-contr
|
||||||
CoreNavigationBarComponent,
|
CoreNavigationBarComponent,
|
||||||
CoreProgressBarComponent,
|
CoreProgressBarComponent,
|
||||||
CoreRecaptchaComponent,
|
CoreRecaptchaComponent,
|
||||||
CoreRecaptchaModalComponent,
|
|
||||||
CoreSendMessageFormComponent,
|
CoreSendMessageFormComponent,
|
||||||
CoreShowPasswordComponent,
|
CoreShowPasswordComponent,
|
||||||
CoreSitePickerComponent,
|
CoreSitePickerComponent,
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
<ion-header>
|
|
||||||
<ion-toolbar>
|
|
||||||
<ion-title>{{ 'core.login.security_question' | translate }}</ion-title>
|
|
||||||
|
|
||||||
<ion-buttons slot="end">
|
|
||||||
<ion-button fill="clear" (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
|
|
||||||
<ion-icon slot="icon-only" name="fas-times" aria-hidden="true"></ion-icon>
|
|
||||||
</ion-button>
|
|
||||||
</ion-buttons>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
|
||||||
<ion-content>
|
|
||||||
<core-iframe [src]="recaptchaUrl" (loaded)="loaded($event)"></core-iframe>
|
|
||||||
</ion-content>
|
|
|
@ -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<void>;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
// Listen for messages from the iframe.
|
|
||||||
this.messageListenerFunction = this.onIframeMessage.bind(this);
|
|
||||||
window.addEventListener('message', this.messageListenerFunction);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close modal.
|
|
||||||
*/
|
|
||||||
closeModal(): void {
|
|
||||||
ModalController.dismiss(<CoreRecaptchaModalReturn>{
|
|
||||||
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<void> {
|
|
||||||
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;
|
|
||||||
};
|
|
|
@ -16,9 +16,8 @@ import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
|
||||||
import { CoreLang } from '@services/lang';
|
import { CoreLang } from '@services/lang';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
|
||||||
import { CoreTextUtils } from '@services/utils/text';
|
import { CoreTextUtils } from '@services/utils/text';
|
||||||
import { CoreRecaptchaModalComponent, CoreRecaptchaModalReturn } from './recaptcha-modal';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that allows answering a recaptcha.
|
* 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<void> {
|
async answerRecaptcha(): Promise<void> {
|
||||||
// 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);
|
const src = CoreTextUtils.concatenatePaths(this.siteUrl!, 'webservice/recaptcha.php?lang=' + this.lang);
|
||||||
|
|
||||||
// Modal to answer the recaptcha.
|
const inAppBrowserWindow = CoreUtils.openInApp(src);
|
||||||
// This is because the size of the recaptcha is dynamic, so it could cause problems if it was displayed inline.
|
if (!inAppBrowserWindow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const modalData = await CoreDomUtils.openModal<CoreRecaptchaModalReturn>({
|
// Set the callbacks once the page is loaded.
|
||||||
component: CoreRecaptchaModalComponent,
|
const loadStopSubscription = inAppBrowserWindow.on('loadstop').subscribe(() => {
|
||||||
cssClass: 'core-modal-fullscreen',
|
inAppBrowserWindow.executeScript({
|
||||||
componentProps: {
|
code:
|
||||||
recaptchaUrl: src,
|
'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) {
|
// Listen for events.
|
||||||
this.expired = modalData.expired;
|
const messageSubscription = inAppBrowserWindow.on('message').subscribe((event) => {
|
||||||
this.model![this.modelValueName] = modalData.value;
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -516,14 +516,8 @@ export class CoreIframeUtilsProvider {
|
||||||
private injectiOSScripts(userScriptWindow: WKUserScriptWindow) {
|
private injectiOSScripts(userScriptWindow: WKUserScriptWindow) {
|
||||||
const wwwPath = CoreFile.getWWWAbsolutePath();
|
const wwwPath = CoreFile.getWWWAbsolutePath();
|
||||||
const linksPath = CoreTextUtils.concatenatePaths(wwwPath, 'assets/js/iframe-treat-links.js');
|
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: 'CoreIframeUtilsLinksScript', file: linksPath });
|
||||||
userScriptWindow.WKUserScript?.addScript({
|
|
||||||
id: 'CoreIframeUtilsRecaptchaScript',
|
|
||||||
file: recaptchaPath,
|
|
||||||
injectionTime: userScriptWindow.WKUserScript?.InjectionTime.END,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle post messages received by iframes.
|
// Handle post messages received by iframes.
|
||||||
window.addEventListener('message', this.handleIframeMessage.bind(this));
|
window.addEventListener('message', this.handleIframeMessage.bind(this));
|
||||||
|
|
|
@ -69,8 +69,7 @@ export class CoreWindow {
|
||||||
|
|
||||||
if (name != '_system') {
|
if (name != '_system') {
|
||||||
// Check if it can be opened in the app.
|
// Check if it can be opened in the app.
|
||||||
treated = false;
|
treated = await CoreContentLinksHelper.handleLink(url, undefined, true, true);
|
||||||
await CoreContentLinksHelper.handleLink(url, undefined, true, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!treated) {
|
if (!treated) {
|
||||||
|
|
Loading…
Reference in New Issue