MOBILE-2253 login: Restore session in init, handle SSO and other events
parent
7893d718a2
commit
26d5690c36
|
@ -12,24 +12,82 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component } from '@angular/core';
|
import { Component, ViewChild, AfterViewInit } from '@angular/core';
|
||||||
import { Platform } from 'ionic-angular';
|
import { Platform, Nav } from 'ionic-angular';
|
||||||
import { StatusBar } from '@ionic-native/status-bar';
|
import { StatusBar } from '@ionic-native/status-bar';
|
||||||
import { SplashScreen } from '@ionic-native/splash-screen';
|
import { SplashScreen } from '@ionic-native/splash-screen';
|
||||||
|
import { CoreEventsProvider } from '../providers/events';
|
||||||
|
import { CoreLoginHelperProvider } from '../core/login/providers/helper';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: 'app.html'
|
templateUrl: 'app.html'
|
||||||
})
|
})
|
||||||
export class MyApp {
|
export class MyApp implements AfterViewInit {
|
||||||
|
@ViewChild(Nav) navCtrl;
|
||||||
rootPage:any = 'CoreLoginInitPage';
|
rootPage:any = 'CoreLoginInitPage';
|
||||||
|
|
||||||
constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen) {
|
constructor(private platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen,
|
||||||
|
private eventsProvider: CoreEventsProvider, private loginHelper: CoreLoginHelperProvider) {
|
||||||
platform.ready().then(() => {
|
platform.ready().then(() => {
|
||||||
// Okay, so the platform is ready and our plugins are available.
|
// Okay, so the platform is ready and our plugins are available.
|
||||||
// Here you can do any higher level native things you might need.
|
// Here you can do any higher level native things you might need.
|
||||||
statusBar.styleDefault();
|
statusBar.styleDefault();
|
||||||
splashScreen.hide();
|
splashScreen.hide();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View has been initialized.
|
||||||
|
*/
|
||||||
|
ngAfterViewInit() {
|
||||||
|
this.loginHelper.setNavCtrl(this.navCtrl);
|
||||||
|
|
||||||
|
// Go to sites page when user is logged out.
|
||||||
|
this.eventsProvider.on(CoreEventsProvider.LOGOUT, () => {
|
||||||
|
this.navCtrl.setRoot('CoreLoginSitesPage');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for session expired events.
|
||||||
|
this.eventsProvider.on(CoreEventsProvider.SESSION_EXPIRED, (data) => {
|
||||||
|
this.loginHelper.sessionExpired(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for passwordchange and usernotfullysetup events to open InAppBrowser.
|
||||||
|
this.eventsProvider.on(CoreEventsProvider.PASSWORD_CHANGE_FORCED, (data) => {
|
||||||
|
this.loginHelper.openInAppForEdit(data.siteId, '/login/change_password.php', 'core.forcepasswordchangenotice', true);
|
||||||
|
});
|
||||||
|
this.eventsProvider.on(CoreEventsProvider.USER_NOT_FULLY_SETUP, (data) => {
|
||||||
|
this.loginHelper.openInAppForEdit(data.siteId, '/user/edit.php', 'core.usernotfullysetup');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for sitepolicynotagreed event to accept the site policy.
|
||||||
|
this.eventsProvider.on(CoreEventsProvider.SITE_POLICY_NOT_AGREED, (data) => {
|
||||||
|
this.loginHelper.sitePolicyNotAgreed(data.siteId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check URLs loaded in any InAppBrowser.
|
||||||
|
this.eventsProvider.on(CoreEventsProvider.IAB_LOAD_START, (event) => {
|
||||||
|
this.loginHelper.inAppBrowserLoadStart(event.url);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check InAppBrowser closed.
|
||||||
|
this.eventsProvider.on(CoreEventsProvider.IAB_EXIT, () => {
|
||||||
|
this.loginHelper.waitingForBrowser = false;
|
||||||
|
this.loginHelper.lastInAppUrl = '';
|
||||||
|
this.loginHelper.checkLogout();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.platform.resume.subscribe(() => {
|
||||||
|
// Wait a second before setting it to false since in iOS there could be some frozen WS calls.
|
||||||
|
setTimeout(() => {
|
||||||
|
this.loginHelper.waitingForBrowser = false;
|
||||||
|
this.loginHelper.checkLogout();
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
// @todo: Register observer to check if the app was launched via URL scheme.
|
||||||
|
// $mmURLDelegate.register('mmLoginSSO', appLaunchedByURL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,8 @@ import { CoreFilepoolProvider } from '../providers/filepool';
|
||||||
import { CoreUpdateManagerProvider } from '../providers/update-manager';
|
import { CoreUpdateManagerProvider } from '../providers/update-manager';
|
||||||
import { CorePluginFileDelegate } from '../providers/plugin-file-delegate';
|
import { CorePluginFileDelegate } from '../providers/plugin-file-delegate';
|
||||||
|
|
||||||
|
import { CoreLoginModule } from '../core/login/login.module';
|
||||||
|
|
||||||
// For translate loader. AoT requires an exported function for factories.
|
// For translate loader. AoT requires an exported function for factories.
|
||||||
export function createTranslateLoader(http: HttpClient) {
|
export function createTranslateLoader(http: HttpClient) {
|
||||||
return new TranslateHttpLoader(http, './assets/lang/', '.json');
|
return new TranslateHttpLoader(http, './assets/lang/', '.json');
|
||||||
|
@ -76,7 +78,8 @@ export function createTranslateLoader(http: HttpClient) {
|
||||||
deps: [HttpClient]
|
deps: [HttpClient]
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
CoreEmulatorModule
|
CoreEmulatorModule,
|
||||||
|
CoreLoginModule
|
||||||
],
|
],
|
||||||
bootstrap: [IonicApp],
|
bootstrap: [IonicApp],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
|
@ -119,19 +122,27 @@ export function createTranslateLoader(http: HttpClient) {
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class AppModule {
|
export class AppModule {
|
||||||
constructor(platform: Platform, initDelegate: CoreInitDelegate, updateManager: CoreUpdateManagerProvider) {
|
constructor(platform: Platform, initDelegate: CoreInitDelegate, updateManager: CoreUpdateManagerProvider,
|
||||||
// Create a handler for platform ready and register it in the init delegate.
|
sitesProvider: CoreSitesProvider) {
|
||||||
let handler = {
|
// Register a handler for platform ready.
|
||||||
|
initDelegate.registerProcess({
|
||||||
name: 'CorePlatformReady',
|
name: 'CorePlatformReady',
|
||||||
priority: CoreInitDelegate.MAX_RECOMMENDED_PRIORITY + 400,
|
priority: CoreInitDelegate.MAX_RECOMMENDED_PRIORITY + 400,
|
||||||
blocking: true,
|
blocking: true,
|
||||||
load: platform.ready
|
load: platform.ready
|
||||||
};
|
});
|
||||||
initDelegate.registerProcess(handler);
|
|
||||||
|
|
||||||
// Register the update manager as an init process.
|
// Register the update manager as an init process.
|
||||||
initDelegate.registerProcess(updateManager);
|
initDelegate.registerProcess(updateManager);
|
||||||
|
|
||||||
|
// Restore the user's session during the init process.
|
||||||
|
initDelegate.registerProcess({
|
||||||
|
name: 'CoreRestoreSession',
|
||||||
|
priority: CoreInitDelegate.MAX_RECOMMENDED_PRIORITY + 200,
|
||||||
|
blocking: false,
|
||||||
|
load: sitesProvider.restoreSession.bind(sitesProvider)
|
||||||
|
});
|
||||||
|
|
||||||
// Execute the init processes.
|
// Execute the init processes.
|
||||||
initDelegate.executeInitProcesses();
|
initDelegate.executeInitProcesses();
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ import { CoreUtilsProvider } from '../providers/utils/utils';
|
||||||
import { CoreConstants } from '../core/constants';
|
import { CoreConstants } from '../core/constants';
|
||||||
import { CoreConfigConstants } from '../configconstants';
|
import { CoreConfigConstants } from '../configconstants';
|
||||||
import { Md5 } from 'ts-md5/dist/md5';
|
import { Md5 } from 'ts-md5/dist/md5';
|
||||||
|
import { InAppBrowserObject } from '@ionic-native/in-app-browser';
|
||||||
|
|
||||||
export interface CoreSiteWSPreSets {
|
export interface CoreSiteWSPreSets {
|
||||||
getFromCache?: boolean; // Get the value from the cache if it's still valid.
|
getFromCache?: boolean; // Get the value from the cache if it's still valid.
|
||||||
|
@ -1017,9 +1018,9 @@ export class CoreSite {
|
||||||
* @param {string} url The URL to open.
|
* @param {string} url The URL to open.
|
||||||
* @param {any} [options] Override default options passed to InAppBrowser.
|
* @param {any} [options] Override default options passed to InAppBrowser.
|
||||||
* @param {string} [alertMessage] If defined, an alert will be shown before opening the inappbrowser.
|
* @param {string} [alertMessage] If defined, an alert will be shown before opening the inappbrowser.
|
||||||
* @return {Promise<any>} Promise resolved when done, rejected otherwise.
|
* @return {Promise<InAppBrowserObject>} Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
openInAppWithAutoLogin(url: string, options?: any, alertMessage?: string) : Promise<any> {
|
openInAppWithAutoLogin(url: string, options?: any, alertMessage?: string) : Promise<InAppBrowserObject> {
|
||||||
return this.openWithAutoLogin(true, url, options, alertMessage);
|
return this.openWithAutoLogin(true, url, options, alertMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1029,9 +1030,9 @@ export class CoreSite {
|
||||||
* @param {string} url The URL to open.
|
* @param {string} url The URL to open.
|
||||||
* @param {object} [options] Override default options passed to inappbrowser.
|
* @param {object} [options] Override default options passed to inappbrowser.
|
||||||
* @param {string} [alertMessage] If defined, an alert will be shown before opening the inappbrowser.
|
* @param {string} [alertMessage] If defined, an alert will be shown before opening the inappbrowser.
|
||||||
* @return {Promise<any>} Promise resolved when done, rejected otherwise.
|
* @return {Promise<InAppBrowserObject>} Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
openInAppWithAutoLoginIfSameSite(url: string, options?: any, alertMessage?: string) : Promise<any> {
|
openInAppWithAutoLoginIfSameSite(url: string, options?: any, alertMessage?: string) : Promise<InAppBrowserObject> {
|
||||||
return this.openWithAutoLoginIfSameSite(true, url, options, alertMessage);
|
return this.openWithAutoLoginIfSameSite(true, url, options, alertMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1042,39 +1043,40 @@ export class CoreSite {
|
||||||
* @param {string} url The URL to open.
|
* @param {string} url The URL to open.
|
||||||
* @param {object} [options] Override default options passed to $cordovaInAppBrowser#open.
|
* @param {object} [options] Override default options passed to $cordovaInAppBrowser#open.
|
||||||
* @param {string} [alertMessage] If defined, an alert will be shown before opening the browser/inappbrowser.
|
* @param {string} [alertMessage] If defined, an alert will be shown before opening the browser/inappbrowser.
|
||||||
* @return {Promise<any>} Promise resolved when done, rejected otherwise.
|
* @return {Promise<InAppBrowserObject>} Promise resolved when done. Resolve param is returned only if inApp=true.
|
||||||
*/
|
*/
|
||||||
openWithAutoLogin(inApp: boolean, url: string, options?: any, alertMessage?: string) : Promise<any> {
|
openWithAutoLogin(inApp: boolean, url: string, options?: any, alertMessage?: string) : Promise<InAppBrowserObject> {
|
||||||
// Convenience function to open the URL.
|
// Convenience function to open the URL.
|
||||||
let open = (url) => {
|
let open = (url) => {
|
||||||
if (modal) {
|
return new Promise<InAppBrowserObject>((resolve, reject) => {
|
||||||
modal.dismiss();
|
if (modal) {
|
||||||
}
|
modal.dismiss();
|
||||||
|
|
||||||
if (alertMessage) {
|
|
||||||
let alert = this.domUtils.showAlert('mm.core.notice', alertMessage, null, 3000);
|
|
||||||
alert.onDidDismiss(() => {
|
|
||||||
if (inApp) {
|
|
||||||
this.utils.openInApp(url, options);
|
|
||||||
} else {
|
|
||||||
this.utils.openInBrowser(url);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (inApp) {
|
|
||||||
this.utils.openInApp(url, options);
|
|
||||||
} else {
|
|
||||||
this.utils.openInBrowser(url);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if (alertMessage) {
|
||||||
|
let alert = this.domUtils.showAlert('mm.core.notice', alertMessage, null, 3000);
|
||||||
|
alert.onDidDismiss(() => {
|
||||||
|
if (inApp) {
|
||||||
|
resolve(this.utils.openInApp(url, options));
|
||||||
|
} else {
|
||||||
|
resolve(this.utils.openInBrowser(url));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (inApp) {
|
||||||
|
resolve(this.utils.openInApp(url, options));
|
||||||
|
} else {
|
||||||
|
resolve(this.utils.openInBrowser(url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!this.privateToken || !this.wsAvailable('tool_mobile_get_autologin_key') ||
|
if (!this.privateToken || !this.wsAvailable('tool_mobile_get_autologin_key') ||
|
||||||
(this.lastAutoLogin && this.timeUtils.timestamp() - this.lastAutoLogin < 6 * CoreConstants.secondsMinute)) {
|
(this.lastAutoLogin && this.timeUtils.timestamp() - this.lastAutoLogin < 6 * CoreConstants.secondsMinute)) {
|
||||||
// No private token, WS not available or last auto-login was less than 6 minutes ago.
|
// No private token, WS not available or last auto-login was less than 6 minutes ago.
|
||||||
// Open the final URL without auto-login.
|
// Open the final URL without auto-login.
|
||||||
open(url);
|
return Promise.resolve(open(url));
|
||||||
return Promise.resolve();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const userId = this.getUserId(),
|
const userId = this.getUserId(),
|
||||||
|
@ -1087,16 +1089,15 @@ export class CoreSite {
|
||||||
return this.write('tool_mobile_get_autologin_key', params).then((data) => {
|
return this.write('tool_mobile_get_autologin_key', params).then((data) => {
|
||||||
if (!data.autologinurl || !data.key) {
|
if (!data.autologinurl || !data.key) {
|
||||||
// Not valid data, open the final URL without auto-login.
|
// Not valid data, open the final URL without auto-login.
|
||||||
open(url);
|
return open(url);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lastAutoLogin = this.timeUtils.timestamp();
|
this.lastAutoLogin = this.timeUtils.timestamp();
|
||||||
|
|
||||||
open(data.autologinurl + '?userid=' + userId + '&key=' + data.key + '&urltogo=' + url);
|
return open(data.autologinurl + '?userid=' + userId + '&key=' + data.key + '&urltogo=' + url);
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
// Couldn't get autologin key, open the final URL without auto-login.
|
// Couldn't get autologin key, open the final URL without auto-login.
|
||||||
open(url);
|
return open(url);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1107,9 +1108,9 @@ export class CoreSite {
|
||||||
* @param {string} url The URL to open.
|
* @param {string} url The URL to open.
|
||||||
* @param {object} [options] Override default options passed to inappbrowser.
|
* @param {object} [options] Override default options passed to inappbrowser.
|
||||||
* @param {string} [alertMessage] If defined, an alert will be shown before opening the browser/inappbrowser.
|
* @param {string} [alertMessage] If defined, an alert will be shown before opening the browser/inappbrowser.
|
||||||
* @return {Promise<any>} Promise resolved when done, rejected otherwise.
|
* @return {Promise<InAppBrowserObject>} Promise resolved when done. Resolve param is returned only if inApp=true.
|
||||||
*/
|
*/
|
||||||
openWithAutoLoginIfSameSite(inApp: boolean, url: string, options?: any, alertMessage?: string) : Promise<any> {
|
openWithAutoLoginIfSameSite(inApp: boolean, url: string, options?: any, alertMessage?: string) : Promise<InAppBrowserObject> {
|
||||||
if (this.containsUrl(url)) {
|
if (this.containsUrl(url)) {
|
||||||
return this.openWithAutoLogin(inApp, url, options, alertMessage);
|
return this.openWithAutoLogin(inApp, url, options, alertMessage);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1118,7 +1119,7 @@ export class CoreSite {
|
||||||
} else {
|
} else {
|
||||||
this.utils.openInBrowser(url);
|
this.utils.openInBrowser(url);
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,11 +13,12 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { NavController } from 'ionic-angular';
|
import { NavController, Platform } from 'ionic-angular';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { CoreAppProvider } from '../../../providers/app';
|
import { CoreAppProvider } from '../../../providers/app';
|
||||||
import { CoreConfigProvider } from '../../../providers/config';
|
import { CoreConfigProvider } from '../../../providers/config';
|
||||||
import { CoreEventsProvider } from '../../../providers/events';
|
import { CoreEventsProvider } from '../../../providers/events';
|
||||||
|
import { CoreInitDelegate } from '../../../providers/init';
|
||||||
import { CoreLoggerProvider } from '../../../providers/logger';
|
import { CoreLoggerProvider } from '../../../providers/logger';
|
||||||
import { CoreSitesProvider } from '../../../providers/sites';
|
import { CoreSitesProvider } from '../../../providers/sites';
|
||||||
import { CoreWSProvider } from '../../../providers/ws';
|
import { CoreWSProvider } from '../../../providers/ws';
|
||||||
|
@ -44,12 +45,17 @@ export interface CoreLoginSSOData {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CoreLoginHelperProvider {
|
export class CoreLoginHelperProvider {
|
||||||
protected logger;
|
protected logger;
|
||||||
|
protected isSSOConfirmShown = false;
|
||||||
|
protected isOpenEditAlertShown = false;
|
||||||
|
protected navCtrl: NavController;
|
||||||
|
lastInAppUrl: string;
|
||||||
|
waitingForBrowser = false;
|
||||||
|
|
||||||
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider,
|
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider,
|
||||||
private wsProvider: CoreWSProvider, private translate: TranslateService, private textUtils: CoreTextUtilsProvider,
|
private wsProvider: CoreWSProvider, private translate: TranslateService, private textUtils: CoreTextUtilsProvider,
|
||||||
private eventsProvider: CoreEventsProvider, private appProvider: CoreAppProvider, private utils: CoreUtilsProvider,
|
private eventsProvider: CoreEventsProvider, private appProvider: CoreAppProvider, private utils: CoreUtilsProvider,
|
||||||
private urlUtils: CoreUrlUtilsProvider,private configProvider: CoreConfigProvider,
|
private urlUtils: CoreUrlUtilsProvider,private configProvider: CoreConfigProvider, private platform: Platform,
|
||||||
private emulatorHelper: CoreEmulatorHelperProvider) {
|
private emulatorHelper: CoreEmulatorHelperProvider, private initDelegate: CoreInitDelegate) {
|
||||||
this.logger = logger.getInstance('CoreLoginHelper');
|
this.logger = logger.getInstance('CoreLoginHelper');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,6 +90,75 @@ export class CoreLoginHelperProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to handle URL received by Custom URL Scheme. If it's a SSO login, perform authentication.
|
||||||
|
*
|
||||||
|
* @param {string} url URL received.
|
||||||
|
* @return {boolean} True if it's a SSO URL, false otherwise.
|
||||||
|
*/
|
||||||
|
appLaunchedByURL(url: string) : boolean {
|
||||||
|
let ssoScheme = CoreConfigConstants.customurlscheme + '://token=';
|
||||||
|
if (url.indexOf(ssoScheme) == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.appProvider.isSSOAuthenticationOngoing()) {
|
||||||
|
// Authentication ongoing, probably duplicated request.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (this.appProvider.isDesktop()) {
|
||||||
|
// In desktop, make sure InAppBrowser is closed.
|
||||||
|
this.utils.closeInAppBrowser(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// App opened using custom URL scheme. Probably an SSO authentication.
|
||||||
|
this.appProvider.startSSOAuthentication();
|
||||||
|
this.logger.debug('App launched by URL');
|
||||||
|
|
||||||
|
// Delete the sso scheme from the URL.
|
||||||
|
url = url.replace(ssoScheme, '');
|
||||||
|
|
||||||
|
// Some platforms like Windows add a slash at the end. Remove it.
|
||||||
|
// Some sites add a # at the end of the URL. If it's there, remove it.
|
||||||
|
url = url.replace(/\/?#?\/?$/, '');
|
||||||
|
|
||||||
|
// Decode from base64.
|
||||||
|
try {
|
||||||
|
url = atob(url);
|
||||||
|
} catch(err) {
|
||||||
|
// Error decoding the parameter.
|
||||||
|
this.logger.error('Error decoding parameter received for login SSO');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let modal = this.domUtils.showModalLoading('mm.login.authenticating', true),
|
||||||
|
siteData: CoreLoginSSOData;
|
||||||
|
|
||||||
|
// Wait for app to be ready.
|
||||||
|
this.initDelegate.ready().then(() => {
|
||||||
|
return this.validateBrowserSSOLogin(url);
|
||||||
|
}).then((data) => {
|
||||||
|
siteData = data;
|
||||||
|
return this.handleSSOLoginAuthentication(siteData.siteUrl, siteData.token, siteData.privateToken);
|
||||||
|
}).then(() => {
|
||||||
|
if (siteData.pageName) {
|
||||||
|
// State defined, go to that state instead of site initial page.
|
||||||
|
this.navCtrl.push(siteData.pageName, siteData.pageParams);
|
||||||
|
} else {
|
||||||
|
this.goToSiteInitialPage(this.navCtrl, true);
|
||||||
|
}
|
||||||
|
}).catch((errorMessage) => {
|
||||||
|
if (typeof errorMessage == 'string' && errorMessage != '') {
|
||||||
|
this.domUtils.showErrorModal(errorMessage);
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
modal.dismiss();
|
||||||
|
this.appProvider.finishSSOAuthentication();
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a site allows requesting a password reset through the app.
|
* Check if a site allows requesting a password reset through the app.
|
||||||
*
|
*
|
||||||
|
@ -98,6 +173,17 @@ export class CoreLoginHelperProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function called when an SSO InAppBrowser is closed or the app is resumed. Check if user needs to be logged out.
|
||||||
|
*/
|
||||||
|
checkLogout() {
|
||||||
|
if (!this.appProvider.isSSOAuthenticationOngoing() && this.sitesProvider.isLoggedIn() &&
|
||||||
|
this.sitesProvider.getCurrentSite().isLoggedOut() && this.navCtrl.getActive().name == 'CoreLoginReconnectPage') {
|
||||||
|
// User must reauthenticate but he closed the InAppBrowser without doing so, logout him.
|
||||||
|
this.sitesProvider.logout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show a confirm modal if needed and open a browser to perform SSO login.
|
* Show a confirm modal if needed and open a browser to perform SSO login.
|
||||||
*
|
*
|
||||||
|
@ -374,6 +460,39 @@ export class CoreLoginHelperProvider {
|
||||||
CoreConfigConstants.siteurl.length > 1;
|
CoreConfigConstants.siteurl.length > 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function called when a page starts loading in any InAppBrowser window.
|
||||||
|
*
|
||||||
|
* @param {string} url Loaded url.
|
||||||
|
*/
|
||||||
|
inAppBrowserLoadStart(url: string) : void {
|
||||||
|
// URLs with a custom scheme can be prefixed with "http://" or "https://", we need to remove this.
|
||||||
|
url = url.replace(/^https?:\/\//, '');
|
||||||
|
|
||||||
|
if (this.appLaunchedByURL(url)) {
|
||||||
|
// Close the browser if it's a valid SSO URL.
|
||||||
|
this.utils.closeInAppBrowser(false);
|
||||||
|
} else if (this.platform.is('android')) {
|
||||||
|
// Check if the URL has a custom URL scheme. In Android they need to be opened manually.
|
||||||
|
const urlScheme = this.urlUtils.getUrlProtocol(url);
|
||||||
|
if (urlScheme && urlScheme !== 'file' && urlScheme !== 'cdvfile') {
|
||||||
|
// Open in browser should launch the right app if found and do nothing if not found.
|
||||||
|
this.utils.openInBrowser(url);
|
||||||
|
|
||||||
|
// At this point the InAppBrowser is showing a "Webpage not available" error message.
|
||||||
|
// Try to navigate to last loaded URL so this error message isn't found.
|
||||||
|
if (this.lastInAppUrl) {
|
||||||
|
this.utils.openInApp(this.lastInAppUrl);
|
||||||
|
} else {
|
||||||
|
// No last URL loaded, close the InAppBrowser.
|
||||||
|
this.utils.closeInAppBrowser(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.lastInAppUrl = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a site public config, check if email signup is disabled.
|
* Given a site public config, check if email signup is disabled.
|
||||||
*
|
*
|
||||||
|
@ -580,6 +699,43 @@ export class CoreLoginHelperProvider {
|
||||||
this.utils.openInApp(siteUrl + '/login/forgot_password.php');
|
this.utils.openInApp(siteUrl + '/login/forgot_password.php');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Function to open in app browser to change password or complete user profile.
|
||||||
|
*
|
||||||
|
* @param {string} siteId The site ID.
|
||||||
|
* @param {string} path The relative path of the URL to open.
|
||||||
|
* @param {string} alertMessage The key of the message to display before opening the in app browser.
|
||||||
|
* @param {boolean} [invalidateCache] Whether to invalidate site's cache (e.g. when the user is forced to change password).
|
||||||
|
*/
|
||||||
|
openInAppForEdit(siteId: string, path: string, alertMessage: string, invalidateCache?: boolean) : void {
|
||||||
|
if (!siteId || siteId !== this.sitesProvider.getCurrentSiteId()) {
|
||||||
|
// Site that triggered the event is not current site, nothing to do.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentSite = this.sitesProvider.getCurrentSite(),
|
||||||
|
siteUrl = currentSite && currentSite.getURL();
|
||||||
|
if (!currentSite || !siteUrl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.isOpenEditAlertShown && !this.waitingForBrowser) {
|
||||||
|
this.isOpenEditAlertShown = true;
|
||||||
|
|
||||||
|
if (invalidateCache) {
|
||||||
|
currentSite.invalidateWsCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open change password.
|
||||||
|
alertMessage = this.translate.instant(alertMessage) + '<br>' + this.translate.instant('mm.core.redirectingtosite');
|
||||||
|
currentSite.openInAppWithAutoLogin(siteUrl + path, undefined, alertMessage).then(() => {
|
||||||
|
this.waitingForBrowser = true;
|
||||||
|
}).finally(() => {
|
||||||
|
this.isOpenEditAlertShown = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare the app to perform SSO login.
|
* Prepare the app to perform SSO login.
|
||||||
*
|
*
|
||||||
|
@ -633,6 +789,88 @@ export class CoreLoginHelperProvider {
|
||||||
return this.wsProvider.callAjax('core_auth_request_password_reset', params, {siteUrl: siteUrl});
|
return this.wsProvider.callAjax('core_auth_request_password_reset', params, {siteUrl: siteUrl});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function that should be called when the session expires. Reserved for core use.
|
||||||
|
*
|
||||||
|
* @param {any} data Data received by the SESSION_EXPIRED event.
|
||||||
|
*/
|
||||||
|
sessionExpired(data: any) : void {
|
||||||
|
let siteId = data && data.siteId,
|
||||||
|
currentSite = this.sitesProvider.getCurrentSite(),
|
||||||
|
siteUrl = currentSite && currentSite.getURL(),
|
||||||
|
promise;
|
||||||
|
|
||||||
|
if (!currentSite || !siteUrl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (siteId && siteId !== currentSite.getId()) {
|
||||||
|
return; // Site that triggered the event is not current site.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check authentication method.
|
||||||
|
this.sitesProvider.checkSite(siteUrl).then((result) => {
|
||||||
|
|
||||||
|
if (result.warning) {
|
||||||
|
this.domUtils.showErrorModal(result.warning, true, 4000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isSSOLoginNeeded(result.code)) {
|
||||||
|
// SSO. User needs to authenticate in a browser. Prevent showing the message several times
|
||||||
|
// or show it again if the user is already authenticating using SSO.
|
||||||
|
if (!this.appProvider.isSSOAuthenticationOngoing() && !this.isSSOConfirmShown && !this.waitingForBrowser) {
|
||||||
|
this.isSSOConfirmShown = true;
|
||||||
|
|
||||||
|
if (this.shouldShowSSOConfirm(result.code)) {
|
||||||
|
promise = this.domUtils.showConfirm(this.translate.instant('mm.login.' +
|
||||||
|
(currentSite.isLoggedOut() ? 'loggedoutssodescription' : 'reconnectssodescription')));
|
||||||
|
} else {
|
||||||
|
promise = Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.then(() => {
|
||||||
|
this.waitingForBrowser = true;
|
||||||
|
this.openBrowserForSSOLogin(result.siteUrl, result.code, result.service,
|
||||||
|
result.config && result.config.launchurl, data.pageName, data.params);
|
||||||
|
}).catch(() => {
|
||||||
|
// User cancelled, logout him.
|
||||||
|
this.sitesProvider.logout();
|
||||||
|
}).finally(() => {
|
||||||
|
this.isSSOConfirmShown = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let info = currentSite.getInfo();
|
||||||
|
if (typeof info != 'undefined' && typeof info.username != 'undefined') {
|
||||||
|
this.navCtrl.setRoot('CoreLoginReconnectPage', {
|
||||||
|
infoSiteUrl: info.siteurl,
|
||||||
|
siteUrl: result.siteUrl,
|
||||||
|
siteId: siteId,
|
||||||
|
pageName: data.pageName,
|
||||||
|
pageParams: data.params,
|
||||||
|
siteConfig: result.config
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
// Error checking site.
|
||||||
|
if (currentSite.isLoggedOut()) {
|
||||||
|
// Site is logged out, show error and logout the user.
|
||||||
|
this.domUtils.showErrorModalDefault(error, 'mm.core.networkerrormsg', true);
|
||||||
|
this.sitesProvider.logout();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a NavController to use.
|
||||||
|
*
|
||||||
|
* @param {NavController} navCtrl Nav controller.
|
||||||
|
*/
|
||||||
|
setNavCtrl(navCtrl: NavController) : void {
|
||||||
|
this.navCtrl = navCtrl;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a confirm should be shown to open a SSO authentication.
|
* Check if a confirm should be shown to open a SSO authentication.
|
||||||
*
|
*
|
||||||
|
@ -644,6 +882,26 @@ export class CoreLoginHelperProvider {
|
||||||
(!CoreConfigConstants.skipssoconfirmation || String(CoreConfigConstants.skipssoconfirmation) === 'false');
|
(!CoreConfigConstants.skipssoconfirmation || String(CoreConfigConstants.skipssoconfirmation) === 'false');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function called when site policy is not agreed. Reserved for core use.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
*/
|
||||||
|
sitePolicyNotAgreed(siteId?: string) : void {
|
||||||
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
if (!siteId || siteId != this.sitesProvider.getCurrentSiteId()) {
|
||||||
|
// Only current site allowed.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.sitesProvider.getCurrentSite().wsAvailable('core_user_agree_site_policy')) {
|
||||||
|
// WS not available, stop.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.navCtrl.setRoot('CoreLoginSitePolicyPage', {siteId: siteId});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenient helper to handle get User Token error. It redirects to change password page if forcepassword is set.
|
* Convenient helper to handle get User Token error. It redirects to change password page if forcepassword is set.
|
||||||
*
|
*
|
||||||
|
|
|
@ -43,6 +43,8 @@ export class CoreEventsProvider {
|
||||||
public static REMOTE_ADDONS_LOADED = 'remote_addons_loaded';
|
public static REMOTE_ADDONS_LOADED = 'remote_addons_loaded';
|
||||||
public static LOGIN_SITE_CHECKED = 'login_site_checked';
|
public static LOGIN_SITE_CHECKED = 'login_site_checked';
|
||||||
public static LOGIN_SITE_UNCHECKED = 'login_site_unchecked';
|
public static LOGIN_SITE_UNCHECKED = 'login_site_unchecked';
|
||||||
|
public static IAB_LOAD_START = 'inappbrowser_load_start';
|
||||||
|
public static IAB_EXIT = 'inappbrowser_exit';
|
||||||
|
|
||||||
logger;
|
logger;
|
||||||
observables = {};
|
observables = {};
|
||||||
|
|
|
@ -19,6 +19,7 @@ import { InAppBrowser, InAppBrowserObject } from '@ionic-native/in-app-browser';
|
||||||
import { Clipboard } from '@ionic-native/clipboard';
|
import { Clipboard } from '@ionic-native/clipboard';
|
||||||
import { CoreAppProvider } from '../app';
|
import { CoreAppProvider } from '../app';
|
||||||
import { CoreDomUtilsProvider } from './dom';
|
import { CoreDomUtilsProvider } from './dom';
|
||||||
|
import { CoreEventsProvider } from '../events';
|
||||||
import { CoreLoggerProvider } from '../logger';
|
import { CoreLoggerProvider } from '../logger';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { CoreLangProvider } from '../lang';
|
import { CoreLangProvider } from '../lang';
|
||||||
|
@ -39,7 +40,7 @@ export class CoreUtilsProvider {
|
||||||
|
|
||||||
constructor(private iab: InAppBrowser, private appProvider: CoreAppProvider, private clipboard: Clipboard,
|
constructor(private iab: InAppBrowser, private appProvider: CoreAppProvider, private clipboard: Clipboard,
|
||||||
private domUtils: CoreDomUtilsProvider, logger: CoreLoggerProvider, private translate: TranslateService,
|
private domUtils: CoreDomUtilsProvider, logger: CoreLoggerProvider, private translate: TranslateService,
|
||||||
private platform: Platform, private langProvider: CoreLangProvider) {
|
private platform: Platform, private langProvider: CoreLangProvider, private eventsProvider: CoreEventsProvider) {
|
||||||
this.logger = logger.getInstance('CoreUtilsProvider');
|
this.logger = logger.getInstance('CoreUtilsProvider');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,7 +250,7 @@ export class CoreUtilsProvider {
|
||||||
*
|
*
|
||||||
* @param {boolean} [closeAll] Desktop only. True to close all secondary windows, false to close only the "current" one.
|
* @param {boolean} [closeAll] Desktop only. True to close all secondary windows, false to close only the "current" one.
|
||||||
*/
|
*/
|
||||||
closeInAppBrowser(closeAll: boolean) : void {
|
closeInAppBrowser(closeAll?: boolean) : void {
|
||||||
if (this.iabInstance) {
|
if (this.iabInstance) {
|
||||||
this.iabInstance.close();
|
this.iabInstance.close();
|
||||||
if (closeAll && this.appProvider.isDesktop()) {
|
if (closeAll && this.appProvider.isDesktop()) {
|
||||||
|
@ -790,6 +791,17 @@ export class CoreUtilsProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.iabInstance = this.iab.create(url, '_blank', options);
|
this.iabInstance = this.iab.create(url, '_blank', options);
|
||||||
|
|
||||||
|
// Trigger global events when a url is loaded or the window is closed. This is to make it work like in Ionic 1.
|
||||||
|
let loadStartSubscription = this.iabInstance.on('loadstart').subscribe((event) => {
|
||||||
|
this.eventsProvider.trigger(CoreEventsProvider.IAB_LOAD_START, event);
|
||||||
|
});
|
||||||
|
let exitSubscription = this.iabInstance.on('exit').subscribe((event) => {
|
||||||
|
loadStartSubscription.unsubscribe();
|
||||||
|
exitSubscription.unsubscribe();
|
||||||
|
this.eventsProvider.trigger(CoreEventsProvider.IAB_EXIT, event);
|
||||||
|
});
|
||||||
|
|
||||||
return this.iabInstance;
|
return this.iabInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue