forked from EVOgeek/Vmeda.Online
		
	
		
			
				
	
	
		
			1386 lines
		
	
	
		
			51 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			1386 lines
		
	
	
		
			51 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| // (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 { Injectable } from '@angular/core';
 | |
| import { Location } from '@angular/common';
 | |
| import { Params } from '@angular/router';
 | |
| import { NavController } from '@ionic/angular';
 | |
| import { Md5 } from 'ts-md5/dist/md5';
 | |
| 
 | |
| import { CoreApp, CoreStoreConfig } from '@services/app';
 | |
| import { CoreConfig } from '@services/config';
 | |
| import { CoreEvents, CoreEventSessionExpiredData } from '@singletons/events';
 | |
| import { CoreSites, CoreLoginSiteInfo } from '@services/sites';
 | |
| import { CoreWS, CoreWSExternalWarning } from '@services/ws';
 | |
| import { CoreDomUtils } from '@services/utils/dom';
 | |
| import { CoreTextUtils } from '@services/utils/text';
 | |
| import { CoreUrlParams, CoreUrlUtils } from '@services/utils/url';
 | |
| import { CoreUtils } from '@services/utils/utils';
 | |
| import { CoreConstants } from '@core/constants';
 | |
| import { CoreSite, CoreSiteIdentityProvider, CoreSitePublicConfigResponse } from '@classes/site';
 | |
| import { CoreError } from '@classes/errors/error';
 | |
| import { CoreWSError } from '@classes/errors/wserror';
 | |
| import { makeSingleton, Translate } from '@singletons/core.singletons';
 | |
| import { CoreLogger } from '@singletons/logger';
 | |
| import { CoreUrl } from '@singletons/url';
 | |
| import { NavigationOptions } from '@ionic/angular/providers/nav-controller';
 | |
| 
 | |
| /**
 | |
|  * Helper provider that provides some common features regarding authentication.
 | |
|  */
 | |
| @Injectable({
 | |
|     providedIn: 'root',
 | |
| })
 | |
| export class CoreLoginHelperProvider {
 | |
| 
 | |
|     static readonly OPEN_COURSE = 'open_course';
 | |
|     static readonly ONBOARDING_DONE = 'onboarding_done';
 | |
|     static readonly FAQ_URL_IMAGE_HTML = '<img src="assets/img/login/faq_url.png" role="presentation">';
 | |
|     static readonly FAQ_QRCODE_IMAGE_HTML = '<img src="assets/img/login/faq_qrcode.png" role="presentation">';
 | |
| 
 | |
|     protected logger: CoreLogger;
 | |
|     protected isSSOConfirmShown = false;
 | |
|     protected isOpenEditAlertShown = false;
 | |
|     protected pageToLoad?: {page: string; params: Params; time: number}; // Page to load once main menu is opened.
 | |
|     protected isOpeningReconnect = false;
 | |
|     waitingForBrowser = false;
 | |
| 
 | |
|     constructor(
 | |
|         protected location: Location,
 | |
|         protected navCtrl: NavController,
 | |
|     ) {
 | |
|         this.logger = CoreLogger.getInstance('CoreLoginHelper');
 | |
| 
 | |
|         CoreEvents.on(CoreEvents.MAIN_MENU_OPEN, () => {
 | |
|             /* If there is any page pending to be opened, do it now. Don't open pages stored more than 5 seconds ago, probably
 | |
|                the function to open the page was called when it shouldn't. */
 | |
|             if (this.pageToLoad && Date.now() - this.pageToLoad.time < 5000) {
 | |
|                 this.loadPageInMainMenu(this.pageToLoad.page, this.pageToLoad.params);
 | |
|                 delete this.pageToLoad;
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Accept site policy.
 | |
|      *
 | |
|      * @param siteId Site ID. If not defined, current site.
 | |
|      * @return Promise resolved if success, rejected if failure.
 | |
|      */
 | |
|     async acceptSitePolicy(siteId?: string): Promise<void> {
 | |
|         const site = await CoreSites.instance.getSite(siteId);
 | |
| 
 | |
|         const result = await site.write<AgreeSitePolicyResult>('core_user_agree_site_policy', {});
 | |
| 
 | |
|         if (!result.status) {
 | |
|             // Error.
 | |
|             if (result.warnings && result.warnings.length) {
 | |
|                 // Check if there is a warning 'alreadyagreed'.
 | |
|                 for (const i in result.warnings) {
 | |
|                     const warning = result.warnings[i];
 | |
|                     if (warning.warningcode == 'alreadyagreed') {
 | |
|                         // Policy already agreed, treat it as a success.
 | |
|                         return;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 // Another warning, reject.
 | |
|                 throw new CoreWSError(result.warnings[0]);
 | |
|             } else {
 | |
|                 throw new CoreError('Cannot agree site policy');
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Check if a site allows requesting a password reset through the app.
 | |
|      *
 | |
|      * @param siteUrl URL of the site.
 | |
|      * @return Promise resolved with boolean: whether can be done through the app.
 | |
|      */
 | |
|     async canRequestPasswordReset(siteUrl: string): Promise<boolean> {
 | |
|         try {
 | |
|             await this.requestPasswordReset(siteUrl);
 | |
| 
 | |
|             return true;
 | |
|         } catch (error) {
 | |
|             return error.available == 1 || (error.errorcode && error.errorcode != 'invalidrecord');
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Function called when an SSO InAppBrowser is closed or the app is resumed. Check if user needs to be logged out.
 | |
|      */
 | |
|     checkLogout(): void {
 | |
|         // @todo
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Show a confirm modal if needed and open a browser to perform SSO login.
 | |
|      *
 | |
|      * @param siteurl URL of the site where the SSO login will be performed.
 | |
|      * @param typeOfLogin CoreConstants.LOGIN_SSO_CODE or CoreConstants.LOGIN_SSO_INAPP_CODE.
 | |
|      * @param service The service to use. If not defined, external service will be used.
 | |
|      * @param launchUrl The URL to open for SSO. If not defined, local_mobile launch URL will be used.
 | |
|      * @return Promise resolved when done or if user cancelled.
 | |
|      */
 | |
|     async confirmAndOpenBrowserForSSOLogin(
 | |
|         siteUrl: string,
 | |
|         typeOfLogin: number,
 | |
|         service?: string,
 | |
|         launchUrl?: string,
 | |
|     ): Promise<void> {
 | |
|         // Show confirm only if it's needed. Treat "false" (string) as false to prevent typing errors.
 | |
|         const showConfirmation = this.shouldShowSSOConfirm(typeOfLogin);
 | |
| 
 | |
|         if (showConfirmation) {
 | |
|             try {
 | |
|                 await CoreDomUtils.instance.showConfirm(Translate.instance.instant('core.login.logininsiterequired'));
 | |
|             } catch (error) {
 | |
|                 // User canceled, stop.
 | |
|                 return;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         this.openBrowserForSSOLogin(siteUrl, typeOfLogin, service, launchUrl);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Helper function to act when the forgotten password is clicked.
 | |
|      *
 | |
|      * @param siteUrl Site URL.
 | |
|      * @param username Username.
 | |
|      * @param siteConfig Site config.
 | |
|      */
 | |
|     async forgottenPasswordClicked(
 | |
|         navCtrl: NavController,
 | |
|         siteUrl: string,
 | |
|         username: string,
 | |
|         siteConfig?: CoreSitePublicConfigResponse,
 | |
|     ): Promise<void> {
 | |
|         if (siteConfig && siteConfig.forgottenpasswordurl) {
 | |
|             // URL set, open it.
 | |
|             CoreUtils.instance.openInApp(siteConfig.forgottenpasswordurl);
 | |
| 
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         // Check if password reset can be done through the app.
 | |
|         const modal = await CoreDomUtils.instance.showModalLoading();
 | |
| 
 | |
|         try {
 | |
|             const canReset = await this.canRequestPasswordReset(siteUrl);
 | |
| 
 | |
|             if (canReset) {
 | |
|                 await navCtrl.navigateForward(['/login/forgottenpassword'], {
 | |
|                     queryParams: {
 | |
|                         siteUrl,
 | |
|                         username,
 | |
|                     },
 | |
|                 });
 | |
|             } else {
 | |
|                 this.openForgottenPassword(siteUrl);
 | |
|             }
 | |
|         } finally {
 | |
|             modal.dismiss();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Format profile fields, filtering the ones that shouldn't be shown on signup and classifying them in categories.
 | |
|      *
 | |
|      * @param profileFields Profile fields to format.
 | |
|      * @return Categories with the fields to show in each one.
 | |
|      */
 | |
|     formatProfileFieldsForSignup(profileFields: AuthEmailSignupProfileField[]): AuthEmailSignupProfileFieldsCategory[] {
 | |
|         if (!profileFields) {
 | |
|             return [];
 | |
|         }
 | |
| 
 | |
|         const categories: Record<number, AuthEmailSignupProfileFieldsCategory> = {};
 | |
| 
 | |
|         profileFields.forEach((field) => {
 | |
|             if (!field.signup || !field.categoryid) {
 | |
|                 // Not a signup field, ignore it.
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             if (!categories[field.categoryid]) {
 | |
|                 categories[field.categoryid] = {
 | |
|                     id: field.categoryid,
 | |
|                     name: field.categoryname || '',
 | |
|                     fields: [],
 | |
|                 };
 | |
|             }
 | |
| 
 | |
|             categories[field.categoryid].fields.push(field);
 | |
|         });
 | |
| 
 | |
|         return Object.keys(categories).map((index) => categories[Number(index)]);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get disabled features from a site public config.
 | |
|      *
 | |
|      * @param config Site public config.
 | |
|      * @return Disabled features.
 | |
|      */
 | |
|     getDisabledFeatures(config?: CoreSitePublicConfigResponse): string {
 | |
|         const disabledFeatures = config?.tool_mobile_disabledfeatures;
 | |
|         if (!disabledFeatures) {
 | |
|             return '';
 | |
|         }
 | |
| 
 | |
|         return CoreTextUtils.instance.treatDisabledFeatures(disabledFeatures);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Builds an object with error messages for some common errors.
 | |
|      * Please notice that this function doesn't support all possible error types.
 | |
|      *
 | |
|      * @param requiredMsg Code of the string for required error.
 | |
|      * @param emailMsg Code of the string for invalid email error.
 | |
|      * @param patternMsg Code of the string for pattern not match error.
 | |
|      * @param urlMsg Code of the string for invalid url error.
 | |
|      * @param minlengthMsg Code of the string for "too short" error.
 | |
|      * @param maxlengthMsg Code of the string for "too long" error.
 | |
|      * @param minMsg Code of the string for min value error.
 | |
|      * @param maxMsg Code of the string for max value error.
 | |
|      * @return Object with the errors.
 | |
|      */
 | |
|     getErrorMessages(
 | |
|         requiredMsg?: string,
 | |
|         emailMsg?: string,
 | |
|         patternMsg?: string,
 | |
|         urlMsg?: string,
 | |
|         minlengthMsg?: string,
 | |
|         maxlengthMsg?: string,
 | |
|         minMsg?: string,
 | |
|         maxMsg?: string,
 | |
|     ): any {
 | |
|         const errors: any = {};
 | |
| 
 | |
|         if (requiredMsg) {
 | |
|             errors.required = errors.requiredTrue = Translate.instance.instant(requiredMsg);
 | |
|         }
 | |
|         if (emailMsg) {
 | |
|             errors.email = Translate.instance.instant(emailMsg);
 | |
|         }
 | |
|         if (patternMsg) {
 | |
|             errors.pattern = Translate.instance.instant(patternMsg);
 | |
|         }
 | |
|         if (urlMsg) {
 | |
|             errors.url = Translate.instance.instant(urlMsg);
 | |
|         }
 | |
|         if (minlengthMsg) {
 | |
|             errors.minlength = Translate.instance.instant(minlengthMsg);
 | |
|         }
 | |
|         if (maxlengthMsg) {
 | |
|             errors.maxlength = Translate.instance.instant(maxlengthMsg);
 | |
|         }
 | |
|         if (minMsg) {
 | |
|             errors.min = Translate.instance.instant(minMsg);
 | |
|         }
 | |
|         if (maxMsg) {
 | |
|             errors.max = Translate.instance.instant(maxMsg);
 | |
|         }
 | |
| 
 | |
|         return errors;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get logo URL from a site public config.
 | |
|      *
 | |
|      * @param config Site public config.
 | |
|      * @return Logo URL.
 | |
|      */
 | |
|     getLogoUrl(config: CoreSitePublicConfigResponse): string | undefined {
 | |
|         return !CoreConstants.CONFIG.forceLoginLogo && config ? (config.logourl || config.compactlogourl) : undefined;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns the logout label of a site.
 | |
|      *
 | |
|      * @param site Site. If not defined, use current site.
 | |
|      * @return The string key.
 | |
|      */
 | |
|     getLogoutLabel(site?: CoreSite): string {
 | |
|         site = site || CoreSites.instance.getCurrentSite();
 | |
|         const config = site?.getStoredConfig();
 | |
| 
 | |
|         return 'core.mainmenu.' + (config && config.tool_mobile_forcelogout == '1' ? 'logout' : 'changesite');
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get the OAuth ID of some URL params (if it has an OAuth ID).
 | |
|      *
 | |
|      * @param params Params.
 | |
|      * @return OAuth ID.
 | |
|      */
 | |
|     getOAuthIdFromParams(params: CoreUrlParams): number | undefined {
 | |
|         return params && typeof params.oauthsso != 'undefined' ? Number(params.oauthsso) : undefined;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get the site policy.
 | |
|      *
 | |
|      * @param siteId Site ID. If not defined, current site.
 | |
|      * @return Promise resolved with the site policy.
 | |
|      */
 | |
|     async getSitePolicy(siteId?: string): Promise<string> {
 | |
|         const site = await CoreSites.instance.getSite(siteId);
 | |
| 
 | |
|         let sitePolicy: string | undefined;
 | |
| 
 | |
|         try {
 | |
|             // Try to get the latest config, maybe the site policy was just added or has changed.
 | |
|             sitePolicy = await site.getConfig('sitepolicy', true);
 | |
|         } catch (error) {
 | |
|             // Cannot get config, try to get the site policy using auth_email_get_signup_settings.
 | |
|             const settings = <AuthEmailSignupSettings> await CoreWS.instance.callAjax(
 | |
|                 'auth_email_get_signup_settings',
 | |
|                 {},
 | |
|                 { siteUrl: site.getURL() },
 | |
|             );
 | |
| 
 | |
|             sitePolicy = settings.sitepolicy;
 | |
|         }
 | |
| 
 | |
|         if (!sitePolicy) {
 | |
|             throw new CoreError('Cannot retrieve site policy');
 | |
|         }
 | |
| 
 | |
|         return sitePolicy;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get fixed site or sites.
 | |
|      *
 | |
|      * @return Fixed site or list of fixed sites.
 | |
|      */
 | |
|     getFixedSites(): string | CoreLoginSiteInfo[] {
 | |
|         return CoreConstants.CONFIG.siteurl;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get the valid identity providers from a site config.
 | |
|      *
 | |
|      * @param siteConfig Site's public config.
 | |
|      * @param disabledFeatures List of disabled features already treated. If not provided it will be calculated.
 | |
|      * @return Valid identity providers.
 | |
|      */
 | |
|     getValidIdentityProviders(siteConfig?: CoreSitePublicConfigResponse, disabledFeatures?: string): CoreSiteIdentityProvider[] {
 | |
|         if (!siteConfig) {
 | |
|             return [];
 | |
|         }
 | |
|         if (this.isFeatureDisabled('NoDelegate_IdentityProviders', siteConfig, disabledFeatures)) {
 | |
|             // Identity providers are disabled, return an empty list.
 | |
|             return [];
 | |
|         }
 | |
| 
 | |
|         const validProviders: CoreSiteIdentityProvider[] = [];
 | |
|         const httpUrl = CoreTextUtils.instance.concatenatePaths(siteConfig.wwwroot, 'auth/oauth2/');
 | |
|         const httpsUrl = CoreTextUtils.instance.concatenatePaths(siteConfig.httpswwwroot, 'auth/oauth2/');
 | |
| 
 | |
|         if (siteConfig.identityproviders && siteConfig.identityproviders.length) {
 | |
|             siteConfig.identityproviders.forEach((provider) => {
 | |
|                 const urlParams = CoreUrlUtils.instance.extractUrlParams(provider.url);
 | |
| 
 | |
|                 if (provider.url && (provider.url.indexOf(httpsUrl) != -1 || provider.url.indexOf(httpUrl) != -1) &&
 | |
|                         !this.isFeatureDisabled('NoDelegate_IdentityProvider_' + urlParams.id, siteConfig, disabledFeatures)) {
 | |
|                     validProviders.push(provider);
 | |
|                 }
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         return validProviders;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Go to the page to add a new site.
 | |
|      * If a fixed URL is configured, go to credentials instead.
 | |
|      *
 | |
|      * @param setRoot True to set the new page as root, false to add it to the stack.
 | |
|      * @param showKeyboard Whether to show keyboard in the new page. Only if no fixed URL set.
 | |
|      * @return Promise resolved when done.
 | |
|      */
 | |
|     async goToAddSite(setRoot?: boolean, showKeyboard?: boolean): Promise<void> {
 | |
|         let pageRoute: string;
 | |
|         let params: Params;
 | |
| 
 | |
|         if (this.isFixedUrlSet()) {
 | |
|             // Fixed URL is set, go to credentials page.
 | |
|             const fixedSites = this.getFixedSites();
 | |
|             const url = typeof fixedSites == 'string' ? fixedSites : fixedSites[0].url;
 | |
| 
 | |
|             pageRoute = '/login/credentials';
 | |
|             params = { siteUrl: url };
 | |
|         } else {
 | |
|             pageRoute = '/login/site';
 | |
|             params = { showKeyboard: showKeyboard };
 | |
|         }
 | |
| 
 | |
|         if (setRoot) {
 | |
|             await this.navCtrl.navigateRoot(pageRoute, {
 | |
|                 queryParams: params,
 | |
|             });
 | |
|         } else {
 | |
|             await this.navCtrl.navigateForward(pageRoute, {
 | |
|                 queryParams: params,
 | |
|             });
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Open a page that doesn't belong to any site.
 | |
|      *
 | |
|      * @param navCtrl Nav Controller.
 | |
|      * @param page Page to open.
 | |
|      * @param params Params of the page.
 | |
|      * @return Promise resolved when done.
 | |
|      */
 | |
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | |
|     goToNoSitePage(page?: string, params?: Params): Promise<any> {
 | |
|         // @todo
 | |
|         return Promise.resolve();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Go to the initial page of a site depending on 'userhomepage' setting.
 | |
|      *
 | |
|      * @param options Options.
 | |
|      * @return Promise resolved when done.
 | |
|      */
 | |
|     goToSiteInitialPage(options?: OpenMainMenuOptions): Promise<void> {
 | |
|         return this.openMainMenu(options);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Convenient helper to handle authentication in the app using a token received by SSO login. If it's a new account,
 | |
|      * the site is stored and the user is authenticated. If the account already exists, update its token.
 | |
|      *
 | |
|      * @param siteUrl Site's URL.
 | |
|      * @param token User's token.
 | |
|      * @param privateToken User's private token.
 | |
|      * @param oauthId OAuth ID. Only if the authentication was using an OAuth method.
 | |
|      * @return Promise resolved when the user is authenticated with the token.
 | |
|      */
 | |
|     handleSSOLoginAuthentication(siteUrl: string, token: string, privateToken?: string, oauthId?: number): Promise<string> {
 | |
|         // Always create a new site to prevent overriding data if another user credentials were introduced.
 | |
|         return CoreSites.instance.newSite(siteUrl, token, privateToken, true, oauthId);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Check if the app is configured to use several fixed URLs.
 | |
|      *
 | |
|      * @return Whether there are several fixed URLs.
 | |
|      */
 | |
|     hasSeveralFixedSites(): boolean {
 | |
|         return !!(CoreConstants.CONFIG.siteurl && Array.isArray(CoreConstants.CONFIG.siteurl) &&
 | |
|             CoreConstants.CONFIG.siteurl.length > 1);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Given a site public config, check if email signup is disabled.
 | |
|      *
 | |
|      * @param config Site public config.
 | |
|      * @param disabledFeatures List of disabled features already treated. If not provided it will be calculated.
 | |
|      * @return Whether email signup is disabled.
 | |
|      */
 | |
|     isEmailSignupDisabled(config?: CoreSitePublicConfigResponse, disabledFeatures?: string): boolean {
 | |
|         return this.isFeatureDisabled('CoreLoginEmailSignup', config, disabledFeatures);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Given a site public config, check if a certian feature is disabled.
 | |
|      *
 | |
|      * @param feature Feature to check.
 | |
|      * @param config Site public config.
 | |
|      * @param disabledFeatures List of disabled features already treated. If not provided it will be calculated.
 | |
|      * @return Whether email signup is disabled.
 | |
|      */
 | |
|     isFeatureDisabled(feature: string, config?: CoreSitePublicConfigResponse, disabledFeatures?: string): boolean {
 | |
|         if (typeof disabledFeatures == 'undefined') {
 | |
|             disabledFeatures = this.getDisabledFeatures(config);
 | |
|         }
 | |
| 
 | |
|         const regEx = new RegExp('(,|^)' + feature + '(,|$)', 'g');
 | |
| 
 | |
|         return !!disabledFeatures.match(regEx);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Check if the app is configured to use a fixed URL (only 1).
 | |
|      *
 | |
|      * @return Whether there is 1 fixed URL.
 | |
|      */
 | |
|     isFixedUrlSet(): boolean {
 | |
|         if (Array.isArray(CoreConstants.CONFIG.siteurl)) {
 | |
|             return CoreConstants.CONFIG.siteurl.length == 1;
 | |
|         }
 | |
| 
 | |
|         return !!CoreConstants.CONFIG.siteurl;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Given a site public config, check if forgotten password is disabled.
 | |
|      *
 | |
|      * @param config Site public config.
 | |
|      * @param disabledFeatures List of disabled features already treated. If not provided it will be calculated.
 | |
|      * @return Whether it's disabled.
 | |
|      */
 | |
|     isForgottenPasswordDisabled(config?: CoreSitePublicConfigResponse, disabledFeatures?: string): boolean {
 | |
|         return this.isFeatureDisabled('NoDelegate_ForgottenPassword', config, disabledFeatures);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Check if current site is logged out, triggering mmCoreEventSessionExpired if it is.
 | |
|      *
 | |
|      * @param pageName Name of the page to go once authenticated if logged out. If not defined, site initial page.
 | |
|      * @param params Params of the page to go once authenticated if logged out.
 | |
|      * @return True if user is logged out, false otherwise.
 | |
|      */
 | |
|     isSiteLoggedOut(pageName?: string, params?: Params): boolean {
 | |
|         const site = CoreSites.instance.getCurrentSite();
 | |
|         if (!site) {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         if (site.isLoggedOut()) {
 | |
|             CoreEvents.trigger(CoreEvents.SESSION_EXPIRED, {
 | |
|                 pageName,
 | |
|                 params,
 | |
|             }, site.getId());
 | |
| 
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Check if a site URL is "allowed". In case the app has fixed sites, only those will be allowed to connect to.
 | |
|      *
 | |
|      * @param siteUrl Site URL to check.
 | |
|      * @return Promise resolved with boolean: whether is one of the fixed sites.
 | |
|      */
 | |
|     async isSiteUrlAllowed(siteUrl: string): Promise<boolean> {
 | |
|         if (this.isFixedUrlSet()) {
 | |
|             // Only 1 site allowed.
 | |
|             return CoreUrl.sameDomainAndPath(siteUrl, <string> this.getFixedSites());
 | |
|         } else if (this.hasSeveralFixedSites()) {
 | |
|             const sites = <CoreLoginSiteInfo[]> this.getFixedSites();
 | |
| 
 | |
|             return sites.some((site) => CoreUrl.sameDomainAndPath(siteUrl, site.url));
 | |
|         } else if (CoreConstants.CONFIG.multisitesdisplay == 'sitefinder' && CoreConstants.CONFIG.onlyallowlistedsites) {
 | |
|             // Call the sites finder to validate the site.
 | |
|             const result = await CoreSites.instance.findSites(siteUrl.replace(/^https?:\/\/|\.\w{2,3}\/?$/g, ''));
 | |
| 
 | |
|             return result && result.some((site) => CoreUrl.sameDomainAndPath(siteUrl, site.url));
 | |
|         } else {
 | |
|             // No fixed sites or it uses a non-restrictive sites finder. Allow connecting.
 | |
|             return true;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Check if SSO login should use an embedded browser.
 | |
|      *
 | |
|      * @param code Code to check.
 | |
|      * @return True if embedded browser, false othwerise.
 | |
|      */
 | |
|     isSSOEmbeddedBrowser(code: number): boolean {
 | |
|         return code == CoreConstants.LOGIN_SSO_INAPP_CODE;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Check if SSO login is needed based on code returned by the WS.
 | |
|      *
 | |
|      * @param code Code to check.
 | |
|      * @return True if SSO login is needed, false othwerise.
 | |
|      */
 | |
|     isSSOLoginNeeded(code: number): boolean {
 | |
|         return code == CoreConstants.LOGIN_SSO_CODE || code == CoreConstants.LOGIN_SSO_INAPP_CODE;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Load a site and load a certain page in that site.
 | |
|      *
 | |
|      * @param page Name of the page to load.
 | |
|      * @param params Params to pass to the page.
 | |
|      * @param siteId Site to load.
 | |
|      * @return Promise resolved when done.
 | |
|      */
 | |
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | |
|     protected loadSiteAndPage(page: string, params: Params, siteId: string): Promise<any> {
 | |
|         // @todo
 | |
|         return Promise.resolve();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Load a certain page in the main menu page.
 | |
|      *
 | |
|      * @param page Name of the page to load.
 | |
|      * @param params Params to pass to the page.
 | |
|      */
 | |
|     loadPageInMainMenu(page: string, params: Params): void {
 | |
|         if (!CoreApp.instance.isMainMenuOpen()) {
 | |
|             // Main menu not open. Store the page to be loaded later.
 | |
|             this.pageToLoad = {
 | |
|                 page: page,
 | |
|                 params: params,
 | |
|                 time: Date.now(),
 | |
|             };
 | |
| 
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if (page == CoreLoginHelperProvider.OPEN_COURSE) {
 | |
|             // @todo Use the openCourse function.
 | |
|         } else {
 | |
|             CoreEvents.trigger(CoreEvents.LOAD_PAGE_MAIN_MENU, { redirectPage: page, redirectParams: params });
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Open the main menu, loading a certain page.
 | |
|      *
 | |
|      * @param options Options.
 | |
|      * @return Promise resolved when done.
 | |
|      */
 | |
|     protected async openMainMenu(options?: OpenMainMenuOptions): Promise<void> {
 | |
| 
 | |
|         // Due to DeepLinker, we need to remove the path from the URL before going to main menu.
 | |
|         // IonTabs checks the URL to determine which path to load for deep linking, so we clear the URL.
 | |
|         // @todo this.location.replaceState('');
 | |
| 
 | |
|         if (options?.redirectPage == CoreLoginHelperProvider.OPEN_COURSE) {
 | |
|             // Load the main menu first, and then open the course.
 | |
|             try {
 | |
|                 await this.navCtrl.navigateRoot('/mainmenu');
 | |
|             } finally {
 | |
|                 // @todo: Open course.
 | |
|             }
 | |
|         } else {
 | |
|             // Open the main menu.
 | |
|             const queryParams: Params = Object.assign({}, options);
 | |
|             delete queryParams.navigationOptions;
 | |
| 
 | |
|             await this.navCtrl.navigateRoot('/mainmenu', {
 | |
|                 queryParams,
 | |
|                 ...options?.navigationOptions,
 | |
|             });
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Open a browser to perform OAuth login (Google, Facebook, Microsoft).
 | |
|      *
 | |
|      * @param siteUrl URL of the site where the login will be performed.
 | |
|      * @param provider The identity provider.
 | |
|      * @param launchUrl The URL to open for SSO. If not defined, tool/mobile launch URL will be used.
 | |
|      * @param pageName Name of the page to go once authenticated. If not defined, site initial page.
 | |
|      * @param pageParams Params of the state to go once authenticated.
 | |
|      * @return True if success, false if error.
 | |
|      */
 | |
|     openBrowserForOAuthLogin(
 | |
|         siteUrl: string,
 | |
|         provider: CoreSiteIdentityProvider,
 | |
|         launchUrl?: string,
 | |
|         pageName?: string,
 | |
|         pageParams?: Params,
 | |
|     ): boolean {
 | |
|         launchUrl = launchUrl || siteUrl + '/admin/tool/mobile/launch.php';
 | |
|         if (!provider || !provider.url) {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         const params = CoreUrlUtils.instance.extractUrlParams(provider.url);
 | |
| 
 | |
|         if (!params.id) {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         const service = CoreSites.instance.determineService(siteUrl);
 | |
|         const loginUrl = this.prepareForSSOLogin(siteUrl, service, launchUrl, pageName, pageParams, {
 | |
|             oauthsso: params.id,
 | |
|         });
 | |
| 
 | |
|         // Always open it in browser because the user might have the session stored in there.
 | |
|         CoreUtils.instance.openInBrowser(loginUrl);
 | |
| 
 | |
|         const nav = <any> window.navigator; // eslint-disable-line @typescript-eslint/no-explicit-any
 | |
|         nav.app?.exitApp();
 | |
| 
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Open a browser to perform SSO login.
 | |
|      *
 | |
|      * @param siteurl URL of the site where the SSO login will be performed.
 | |
|      * @param typeOfLogin CoreConstants.LOGIN_SSO_CODE or CoreConstants.LOGIN_SSO_INAPP_CODE.
 | |
|      * @param service The service to use. If not defined, external service will be used.
 | |
|      * @param launchUrl The URL to open for SSO. If not defined, local_mobile launch URL will be used.
 | |
|      * @param pageName Name of the page to go once authenticated. If not defined, site initial page.
 | |
|      * @param pageParams Params of the state to go once authenticated.
 | |
|      */
 | |
|     openBrowserForSSOLogin(
 | |
|         siteUrl: string,
 | |
|         typeOfLogin: number,
 | |
|         service?: string,
 | |
|         launchUrl?: string,
 | |
|         pageName?: string,
 | |
|         pageParams?: Params,
 | |
|     ): void {
 | |
|         const loginUrl = this.prepareForSSOLogin(siteUrl, service, launchUrl, pageName, pageParams);
 | |
| 
 | |
|         if (this.isSSOEmbeddedBrowser(typeOfLogin)) {
 | |
|             CoreUtils.instance.openInApp(loginUrl, {
 | |
|                 clearsessioncache: 'yes', // Clear the session cache to allow for multiple logins.
 | |
|                 closebuttoncaption: Translate.instance.instant('core.login.cancel'),
 | |
|             });
 | |
|         } else {
 | |
|             CoreUtils.instance.openInBrowser(loginUrl);
 | |
| 
 | |
|             const nav = <any> window.navigator; // eslint-disable-line @typescript-eslint/no-explicit-any
 | |
|             nav.app?.exitApp();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Convenient helper to open change password page.
 | |
|      *
 | |
|      * @param siteUrl Site URL to construct change password URL.
 | |
|      * @param error Error message.
 | |
|      * @return Promise resolved when done.
 | |
|      */
 | |
|     async openChangePassword(siteUrl: string, error: string): Promise<void> {
 | |
|         const alert = await CoreDomUtils.instance.showAlert(Translate.instance.instant('core.notice'), error, undefined, 3000);
 | |
| 
 | |
|         await alert.onDidDismiss();
 | |
| 
 | |
|         CoreUtils.instance.openInApp(siteUrl + '/login/change_password.php');
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Open forgotten password in inappbrowser.
 | |
|      *
 | |
|      * @param siteUrl URL of the site.
 | |
|      */
 | |
|     openForgottenPassword(siteUrl: string): void {
 | |
|         CoreUtils.instance.openInApp(siteUrl + '/login/forgot_password.php');
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Function to open in app browser to change password or complete user profile.
 | |
|      *
 | |
|      * @param siteId The site ID.
 | |
|      * @param path The relative path of the URL to open.
 | |
|      * @param alertMessage The key of the message to display before opening the in app browser.
 | |
|      * @param invalidateCache Whether to invalidate site's cache (e.g. when the user is forced to change password).
 | |
|      * @return Promise resolved when done.
 | |
|      */
 | |
|     async openInAppForEdit(siteId: string, path: string, alertMessage?: string, invalidateCache?: boolean): Promise<void> {
 | |
|         if (!siteId || siteId !== CoreSites.instance.getCurrentSiteId()) {
 | |
|             // Site that triggered the event is not current site, nothing to do.
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         const currentSite = CoreSites.instance.getCurrentSite();
 | |
|         const siteUrl = currentSite?.getURL();
 | |
| 
 | |
|         if (!currentSite || !siteUrl) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if (!this.isOpenEditAlertShown && !this.waitingForBrowser) {
 | |
|             this.isOpenEditAlertShown = true;
 | |
| 
 | |
|             if (invalidateCache) {
 | |
|                 currentSite.invalidateWsCache();
 | |
|             }
 | |
| 
 | |
|             // Open change password.
 | |
|             if (alertMessage) {
 | |
|                 alertMessage = Translate.instance.instant(alertMessage) + '<br>' +
 | |
|                     Translate.instance.instant('core.redirectingtosite');
 | |
|             }
 | |
| 
 | |
|             try {
 | |
|                 await currentSite.openInAppWithAutoLogin(siteUrl + path, undefined, alertMessage);
 | |
| 
 | |
|                 this.waitingForBrowser = true;
 | |
|             } finally {
 | |
|                 this.isOpenEditAlertShown = false;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Function that should be called when password change is forced. Reserved for core use.
 | |
|      *
 | |
|      * @param siteId The site ID.
 | |
|      */
 | |
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | |
|     passwordChangeForced(siteId: string): void {
 | |
|         // @todo
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Prepare the app to perform SSO login.
 | |
|      *
 | |
|      * @param siteUrl URL of the site where the SSO login will be performed.
 | |
|      * @param service The service to use. If not defined, external service will be used.
 | |
|      * @param launchUrl The URL to open for SSO. If not defined, local_mobile launch URL will be used.
 | |
|      * @param pageName Name of the page to go once authenticated. If not defined, site initial page.
 | |
|      * @param pageParams Params of the state to go once authenticated.
 | |
|      * @param urlParams Other params to add to the URL.
 | |
|      * @return Login Url.
 | |
|      */
 | |
|     prepareForSSOLogin(
 | |
|         siteUrl: string,
 | |
|         service?: string,
 | |
|         launchUrl?: string,
 | |
|         pageName?: string,
 | |
|         pageParams?: Params,
 | |
|         urlParams?: CoreUrlParams,
 | |
|     ): string {
 | |
| 
 | |
|         service = service || CoreConstants.CONFIG.wsextservice;
 | |
|         launchUrl = launchUrl || siteUrl + '/local/mobile/launch.php';
 | |
| 
 | |
|         const passport = Math.random() * 1000;
 | |
|         let loginUrl = launchUrl + '?service=' + service;
 | |
| 
 | |
|         loginUrl += '&passport=' + passport;
 | |
|         loginUrl += '&urlscheme=' + CoreConstants.CONFIG.customurlscheme;
 | |
| 
 | |
|         if (urlParams) {
 | |
|             loginUrl = CoreUrlUtils.instance.addParamsToUrl(loginUrl, urlParams);
 | |
|         }
 | |
| 
 | |
|         // Store the siteurl and passport in CoreConfigProvider for persistence.
 | |
|         // We are "configuring" the app to wait for an SSO. CoreConfigProvider shouldn't be used as a temporary storage.
 | |
|         CoreConfig.instance.set(CoreConstants.LOGIN_LAUNCH_DATA, JSON.stringify(<StoredLoginLaunchData> {
 | |
|             siteUrl: siteUrl,
 | |
|             passport: passport,
 | |
|             pageName: pageName || '',
 | |
|             pageParams: pageParams || {},
 | |
|             ssoUrlParams: urlParams || {},
 | |
|         }));
 | |
| 
 | |
|         return loginUrl;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Redirect to a new page, setting it as the root page and loading the right site if needed.
 | |
|      *
 | |
|      * @param page Name of the page to load. Special cases: OPEN_COURSE (to open course page).
 | |
|      * @param params Params to pass to the page.
 | |
|      * @param siteId Site to load. If not defined, current site.
 | |
|      * @return Promise resolved when done.
 | |
|      */
 | |
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | |
|     async redirect(page: string, params?: Params, siteId?: string): Promise<void> {
 | |
|         // @todo
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Request a password reset.
 | |
|      *
 | |
|      * @param siteUrl URL of the site.
 | |
|      * @param username Username to search.
 | |
|      * @param email Email to search.
 | |
|      * @return Promise resolved when done.
 | |
|      */
 | |
|     requestPasswordReset(siteUrl: string, username?: string, email?: string): Promise<CoreLoginRequestPasswordResetResult> {
 | |
|         const params: Record<string, string> = {};
 | |
| 
 | |
|         if (username) {
 | |
|             params.username = username;
 | |
|         }
 | |
| 
 | |
|         if (email) {
 | |
|             params.email = email;
 | |
|         }
 | |
| 
 | |
|         return CoreWS.instance.callAjax('core_auth_request_password_reset', params, { siteUrl });
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Function that should be called when the session expires. Reserved for core use.
 | |
|      *
 | |
|      * @param data Data received by the SESSION_EXPIRED event.
 | |
|      * @return Promise resolved when done.
 | |
|      */
 | |
|     async sessionExpired(data: CoreEventSessionExpiredData): Promise<void> {
 | |
|         const siteId = data?.siteId;
 | |
|         const currentSite = CoreSites.instance.getCurrentSite();
 | |
|         const siteUrl = currentSite?.getURL();
 | |
| 
 | |
|         if (!currentSite || !siteUrl) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if (siteId && siteId !== currentSite.getId()) {
 | |
|             return; // Site that triggered the event is not current site.
 | |
|         }
 | |
| 
 | |
|         try {
 | |
|             // Check authentication method.
 | |
|             const result = await CoreSites.instance.checkSite(siteUrl);
 | |
| 
 | |
|             if (result.warning) {
 | |
|                 CoreDomUtils.instance.showErrorModal(result.warning, true, 4000);
 | |
|             }
 | |
| 
 | |
|             if (this.isSSOLoginNeeded(result.code)) {
 | |
|                 // SSO. User needs to authenticate in a browser. Check if we need to display a message.
 | |
|                 if (!CoreApp.instance.isSSOAuthenticationOngoing() && !this.isSSOConfirmShown && !this.waitingForBrowser) {
 | |
|                     this.isSSOConfirmShown = true;
 | |
| 
 | |
|                     if (this.shouldShowSSOConfirm(result.code)) {
 | |
|                         await CoreDomUtils.instance.showConfirm(Translate.instance.instant('core.login.' +
 | |
|                             (currentSite.isLoggedOut() ? 'loggedoutssodescription' : 'reconnectssodescription')));
 | |
|                     }
 | |
| 
 | |
|                     try {
 | |
|                         this.waitingForBrowser = true;
 | |
| 
 | |
|                         this.openBrowserForSSOLogin(
 | |
|                             result.siteUrl,
 | |
|                             result.code,
 | |
|                             result.service,
 | |
|                             result.config?.launchurl,
 | |
|                             data.pageName,
 | |
|                             data.params,
 | |
|                         );
 | |
|                     } catch (error) {
 | |
|                         // User cancelled, logout him.
 | |
|                         CoreSites.instance.logout();
 | |
|                     } finally {
 | |
|                         this.isSSOConfirmShown = false;
 | |
|                     }
 | |
|                 }
 | |
|             } else {
 | |
|                 if (currentSite.isOAuth()) {
 | |
|                     // User authenticated using an OAuth method. Check if it's still valid.
 | |
|                     const identityProviders = this.getValidIdentityProviders(result.config);
 | |
|                     const providerToUse = identityProviders.find((provider) => {
 | |
|                         const params = CoreUrlUtils.instance.extractUrlParams(provider.url);
 | |
| 
 | |
|                         return Number(params.id) == currentSite.getOAuthId();
 | |
|                     });
 | |
| 
 | |
|                     if (providerToUse) {
 | |
|                         if (!CoreApp.instance.isSSOAuthenticationOngoing() && !this.isSSOConfirmShown && !this.waitingForBrowser) {
 | |
|                             // Open browser to perform the OAuth.
 | |
|                             this.isSSOConfirmShown = true;
 | |
| 
 | |
|                             const confirmMessage = Translate.instance.instant('core.login.' +
 | |
|                                     (currentSite.isLoggedOut() ? 'loggedoutssodescription' : 'reconnectssodescription'));
 | |
| 
 | |
|                             try {
 | |
|                                 await CoreDomUtils.instance.showConfirm(confirmMessage);
 | |
| 
 | |
|                                 this.waitingForBrowser = true;
 | |
|                                 CoreSites.instance.unsetCurrentSite(); // Unset current site to make authentication work fine.
 | |
| 
 | |
|                                 this.openBrowserForOAuthLogin(
 | |
|                                     siteUrl,
 | |
|                                     providerToUse,
 | |
|                                     result.config?.launchurl,
 | |
|                                     data.pageName,
 | |
|                                     data.params,
 | |
|                                 );
 | |
|                             } catch (error) {
 | |
|                                 // User cancelled, logout him.
 | |
|                                 CoreSites.instance.logout();
 | |
|                             } finally {
 | |
|                                 this.isSSOConfirmShown = false;
 | |
|                             }
 | |
|                         }
 | |
| 
 | |
|                         return;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 const info = currentSite.getInfo();
 | |
|                 if (typeof info != 'undefined' && typeof info.username != 'undefined' && !this.isOpeningReconnect) {
 | |
|                     // @todo
 | |
|                 }
 | |
|             }
 | |
|         } catch (error) {
 | |
|             // Error checking site.
 | |
|             if (currentSite.isLoggedOut()) {
 | |
|                 // Site is logged out, show error and logout the user.
 | |
|                 CoreDomUtils.instance.showErrorModalDefault(error, 'core.networkerrormsg', true);
 | |
|                 CoreSites.instance.logout();
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Check if a confirm should be shown to open a SSO authentication.
 | |
|      *
 | |
|      * @param typeOfLogin CoreConstants.LOGIN_SSO_CODE or CoreConstants.LOGIN_SSO_INAPP_CODE.
 | |
|      * @return True if confirm modal should be shown, false otherwise.
 | |
|      */
 | |
|     shouldShowSSOConfirm(typeOfLogin: number): boolean {
 | |
|         return !this.isSSOEmbeddedBrowser(typeOfLogin) &&
 | |
|             (!CoreConstants.CONFIG.skipssoconfirmation || String(CoreConstants.CONFIG.skipssoconfirmation) === 'false');
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Show a modal warning the user that he should use the Workplace app.
 | |
|      *
 | |
|      * @param message The warning message.
 | |
|      */
 | |
|     protected showWorkplaceNoticeModal(message: string): void {
 | |
|         const link = CoreApp.instance.getAppStoreUrl({ android: 'com.moodle.workplace', ios: 'id1470929705' });
 | |
| 
 | |
|         CoreDomUtils.instance.showDownloadAppNoticeModal(message, link);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Show a modal warning the user that he should use the current Moodle app.
 | |
|      *
 | |
|      * @param message The warning message.
 | |
|      */
 | |
|     protected showMoodleAppNoticeModal(message: string): void {
 | |
|         const storesConfig: CoreStoreConfig = CoreConstants.CONFIG.appstores;
 | |
|         storesConfig.mobile = 'https://download.moodle.org/mobile/';
 | |
|         storesConfig.default = 'https://download.moodle.org/mobile/';
 | |
| 
 | |
|         const link = CoreApp.instance.getAppStoreUrl(storesConfig);
 | |
| 
 | |
|         CoreDomUtils.instance.showDownloadAppNoticeModal(message, link);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Show a modal to inform the user that a confirmation email was sent, and a button to resend the email on 3.6+ sites.
 | |
|      *
 | |
|      * @param siteUrl Site URL.
 | |
|      * @param email Email of the user. If set displayed in the message.
 | |
|      * @param username Username. If not set the button to resend email will not be shown.
 | |
|      * @param password User password. If not set the button to resend email will not be shown.
 | |
|      */
 | |
|     protected async showNotConfirmedModal(siteUrl: string, email?: string, username?: string, password?: string): Promise<void> {
 | |
|         const title = Translate.instance.instant('core.login.mustconfirm');
 | |
|         let message: string;
 | |
|         let canResend = false;
 | |
|         if (email) {
 | |
|             message = Translate.instance.instant('core.login.emailconfirmsent', { $a: email });
 | |
|         } else {
 | |
|             message = Translate.instance.instant('core.login.emailconfirmsentnoemail');
 | |
|         }
 | |
| 
 | |
|         // Check whether we need to display the resend button or not.
 | |
|         if (username && password) {
 | |
|             canResend = await this.canResendEmail(siteUrl);
 | |
|         }
 | |
| 
 | |
|         if (!canResend) {
 | |
|             // Just display an informative alert.
 | |
|             await CoreDomUtils.instance.showAlert(title, message);
 | |
| 
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         const okText = Translate.instance.instant('core.login.resendemail');
 | |
|         const cancelText = Translate.instance.instant('core.close');
 | |
| 
 | |
|         try {
 | |
|             // Ask the user if he wants to resend the email.
 | |
|             await CoreDomUtils.instance.showConfirm(message, title, okText, cancelText);
 | |
| 
 | |
|             // Call the WS to resend the confirmation email.
 | |
|             const modal = await CoreDomUtils.instance.showModalLoading('core.sending', true);
 | |
|             const data = { username, password };
 | |
|             const preSets = { siteUrl };
 | |
| 
 | |
|             try {
 | |
|                 const result = <ResendConfirmationEmailResult> await CoreWS.instance.callAjax(
 | |
|                     'core_auth_resend_confirmation_email',
 | |
|                     data,
 | |
|                     preSets,
 | |
|                 );
 | |
| 
 | |
|                 if (!result.status) {
 | |
|                     throw new CoreWSError(result.warnings?.[0]);
 | |
|                 }
 | |
| 
 | |
|                 const message = Translate.instance.instant('core.login.emailconfirmsentsuccess');
 | |
|                 CoreDomUtils.instance.showAlert(Translate.instance.instant('core.success'), message);
 | |
|             } finally {
 | |
|                 modal.dismiss();
 | |
|             }
 | |
|         } catch (error) {
 | |
|             CoreDomUtils.instance.showErrorModal(error);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Check if confirmation email an be resent.
 | |
|      *
 | |
|      * @param siteUrl Site URL to check.
 | |
|      * @return Promise.
 | |
|      */
 | |
|     protected async canResendEmail(siteUrl: string): Promise<boolean> {
 | |
|         const modal = await CoreDomUtils.instance.showModalLoading();
 | |
| 
 | |
|         // We don't have site info before login, the only way to check if the WS is available is by calling it.
 | |
|         try {
 | |
|             // This call will always fail because we aren't sending parameters.
 | |
|             await CoreWS.instance.callAjax('core_auth_resend_confirmation_email', {}, { siteUrl });
 | |
| 
 | |
|             return true; // We should never reach here.
 | |
|         } catch (error) {
 | |
|             // If the WS responds with an invalid parameter error it means the WS is avaiable.
 | |
|             return error?.errorcode === 'invalidparameter';
 | |
|         } finally {
 | |
|             modal.dismiss();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Function called when site policy is not agreed. Reserved for core use.
 | |
|      *
 | |
|      * @param siteId Site ID. If not defined, current site.
 | |
|      */
 | |
|     sitePolicyNotAgreed(siteId?: string): void {
 | |
|         siteId = siteId || CoreSites.instance.getCurrentSiteId();
 | |
|         if (!siteId || siteId != CoreSites.instance.getCurrentSiteId()) {
 | |
|             // Only current site allowed.
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if (!CoreSites.instance.wsAvailableInCurrentSite('core_user_agree_site_policy')) {
 | |
|             // WS not available, stop.
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         // @todo Navigate to site policy page.
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Convenient helper to handle get User Token error. It redirects to change password page if forcepassword is set.
 | |
|      *
 | |
|      * @param siteUrl Site URL to construct change password URL.
 | |
|      * @param error Error object containing errorcode and error message.
 | |
|      * @param username Username.
 | |
|      * @param password User password.
 | |
|      */
 | |
|     treatUserTokenError(siteUrl: string, error: CoreWSError, username?: string, password?: string): void {
 | |
|         if (error.errorcode == 'forcepasswordchangenotice') {
 | |
|             this.openChangePassword(siteUrl, CoreTextUtils.instance.getErrorMessageFromError(error)!);
 | |
|         } else if (error.errorcode == 'usernotconfirmed') {
 | |
|             this.showNotConfirmedModal(siteUrl, undefined, username, password);
 | |
|         } else if (error.errorcode == 'connecttomoodleapp') {
 | |
|             this.showMoodleAppNoticeModal(CoreTextUtils.instance.getErrorMessageFromError(error)!);
 | |
|         } else if (error.errorcode == 'connecttoworkplaceapp') {
 | |
|             this.showWorkplaceNoticeModal(CoreTextUtils.instance.getErrorMessageFromError(error)!);
 | |
|         } else {
 | |
|             CoreDomUtils.instance.showErrorModal(error);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Convenient helper to validate a browser SSO login.
 | |
|      *
 | |
|      * @param url URL received, to be validated.
 | |
|      * @return Promise resolved on success.
 | |
|      */
 | |
|     async validateBrowserSSOLogin(url: string): Promise<CoreLoginSSOData> {
 | |
|         // Split signature:::token
 | |
|         const params = url.split(':::');
 | |
| 
 | |
|         const serializedData = await CoreConfig.instance.get<string>(CoreConstants.LOGIN_LAUNCH_DATA);
 | |
| 
 | |
|         const data = <StoredLoginLaunchData | null> CoreTextUtils.instance.parseJSON(serializedData, null);
 | |
|         if (data === null) {
 | |
|             throw new CoreError('No launch data stored.');
 | |
|         }
 | |
| 
 | |
|         const passport = data.passport;
 | |
|         let launchSiteURL = data.siteUrl;
 | |
| 
 | |
|         // Reset temporary values.
 | |
|         CoreConfig.instance.delete(CoreConstants.LOGIN_LAUNCH_DATA);
 | |
| 
 | |
|         // Validate the signature.
 | |
|         // We need to check both http and https.
 | |
|         let signature = <string> Md5.hashAsciiStr(launchSiteURL + passport);
 | |
|         if (signature != params[0]) {
 | |
|             if (launchSiteURL.indexOf('https://') != -1) {
 | |
|                 launchSiteURL = launchSiteURL.replace('https://', 'http://');
 | |
|             } else {
 | |
|                 launchSiteURL = launchSiteURL.replace('http://', 'https://');
 | |
|             }
 | |
|             signature = <string> Md5.hashAsciiStr(launchSiteURL + passport);
 | |
|         }
 | |
| 
 | |
|         if (signature == params[0]) {
 | |
|             this.logger.debug('Signature validated');
 | |
| 
 | |
|             return {
 | |
|                 siteUrl: launchSiteURL,
 | |
|                 token: params[1],
 | |
|                 privateToken: params[2],
 | |
|                 pageName: data.pageName,
 | |
|                 pageParams: data.pageParams,
 | |
|                 ssoUrlParams: data.ssoUrlParams,
 | |
|             };
 | |
|         } else {
 | |
|             this.logger.debug('Invalid signature in the URL request yours: ' + params[0] + ' mine: '
 | |
|                 + signature + ' for passport ' + passport);
 | |
| 
 | |
|             throw new CoreError(Translate.instance.instant('core.unexpectederror'));
 | |
|         }
 | |
|     }
 | |
| 
 | |
| }
 | |
| 
 | |
| export class CoreLoginHelper extends makeSingleton(CoreLoginHelperProvider) {}
 | |
| 
 | |
| /**
 | |
|  * Data related to a SSO authentication.
 | |
|  */
 | |
| export interface CoreLoginSSOData {
 | |
|     /**
 | |
|      * The site's URL.
 | |
|      */
 | |
|     siteUrl: string;
 | |
| 
 | |
|     /**
 | |
|      * User's token.
 | |
|      */
 | |
|     token?: string;
 | |
| 
 | |
|     /**
 | |
|      * User's private token.
 | |
|      */
 | |
|     privateToken?: string;
 | |
| 
 | |
|     /**
 | |
|      * Name of the page to go after authenticated.
 | |
|      */
 | |
|     pageName?: string;
 | |
| 
 | |
|     /**
 | |
|      * Params to page to the page.
 | |
|      */
 | |
|     pageParams?: Params;
 | |
| 
 | |
|     /**
 | |
|      * Other params added to the login url.
 | |
|      */
 | |
|     ssoUrlParams?: CoreUrlParams;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Result of WS core_user_agree_site_policy.
 | |
|  */
 | |
| type AgreeSitePolicyResult = {
 | |
|     status: boolean; // Status: true only if we set the policyagreed to 1 for the user.
 | |
|     warnings?: CoreWSExternalWarning[];
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Result of WS auth_email_get_signup_settings.
 | |
|  */
 | |
| export type AuthEmailSignupSettings = {
 | |
|     namefields: string[];
 | |
|     passwordpolicy?: string; // Password policy.
 | |
|     sitepolicy?: string; // Site policy.
 | |
|     sitepolicyhandler?: string; // Site policy handler.
 | |
|     defaultcity?: string; // Default city.
 | |
|     country?: string; // Default country.
 | |
|     profilefields?: AuthEmailSignupProfileField[]; // Required profile fields.
 | |
|     recaptchapublickey?: string; // Recaptcha public key.
 | |
|     recaptchachallengehash?: string; // Recaptcha challenge hash.
 | |
|     recaptchachallengeimage?: string; // Recaptcha challenge noscript image.
 | |
|     recaptchachallengejs?: string; // Recaptcha challenge js url.
 | |
|     warnings?: CoreWSExternalWarning[];
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Profile field for signup.
 | |
|  */
 | |
| export type AuthEmailSignupProfileField = {
 | |
|     id?: number; // Profile field id.
 | |
|     shortname?: string; // Profile field shortname.
 | |
|     name?: string; // Profield field name.
 | |
|     datatype?: string; // Profield field datatype.
 | |
|     description?: string; // Profield field description.
 | |
|     descriptionformat: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | |
|     categoryid?: number; // Profield field category id.
 | |
|     categoryname?: string; // Profield field category name.
 | |
|     sortorder?: number; // Profield field sort order.
 | |
|     required?: number; // Profield field required.
 | |
|     locked?: number; // Profield field locked.
 | |
|     visible?: number; // Profield field visible.
 | |
|     forceunique?: number; // Profield field unique.
 | |
|     signup?: number; // Profield field in signup form.
 | |
|     defaultdata?: string; // Profield field default data.
 | |
|     defaultdataformat: number; // Defaultdata format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | |
|     param1?: string; // Profield field settings.
 | |
|     param2?: string; // Profield field settings.
 | |
|     param3?: string; // Profield field settings.
 | |
|     param4?: string; // Profield field settings.
 | |
|     param5?: string; // Profield field settings.
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Category of profile fields for signup.
 | |
|  */
 | |
| export type AuthEmailSignupProfileFieldsCategory = {
 | |
|     id: number; // Category ID.
 | |
|     name: string; // Category name.
 | |
|     fields: AuthEmailSignupProfileField[]; // Field in the category.
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Result of WS core_auth_request_password_reset.
 | |
|  */
 | |
| export type CoreLoginRequestPasswordResetResult = {
 | |
|     status: string; // The returned status of the process
 | |
|     notice: string; // Important information for the user about the process.
 | |
|     warnings?: CoreWSExternalWarning[];
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Result of WS core_auth_resend_confirmation_email.
 | |
|  */
 | |
| type ResendConfirmationEmailResult = {
 | |
|     status: boolean; // True if the confirmation email was sent, false otherwise.
 | |
|     warnings?: CoreWSExternalWarning[];
 | |
| };
 | |
| 
 | |
| type StoredLoginLaunchData = {
 | |
|     siteUrl: string;
 | |
|     passport: number;
 | |
|     pageName: string;
 | |
|     pageParams: Params;
 | |
|     ssoUrlParams: CoreUrlParams;
 | |
| };
 | |
| 
 | |
| type OpenMainMenuOptions = {
 | |
|     redirectPage?: string; // Route of the page to open in main menu. If not defined, default tab will be selected.
 | |
|     redirectParams?: Params; // Params to pass to the selected tab if any.
 | |
|     urlToOpen?: string; // URL to open once the main menu is loaded.
 | |
|     navigationOptions?: NavigationOptions; // Navigation options.
 | |
| };
 |