From 495ae729935de88acb9bad73bc5c29e127a70bf1 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 21 Nov 2017 16:35:41 +0100 Subject: [PATCH] MOBILE-2253 login: Implement init view and helper --- src/app/app.component.ts | 34 +- src/app/app.scss | 39 ++ src/assets/img/logo_white.png | Bin 0 -> 18080 bytes src/core/constants.ts | 1 + src/core/emulator/emulator.module.ts | 6 +- src/core/emulator/providers/helper.ts | 2 +- src/core/login/login.module.ts | 27 + src/core/login/pages/init/init.html | 24 +- src/core/login/pages/init/init.module.ts | 32 +- src/core/login/pages/init/init.scss | 2 +- src/core/login/pages/init/init.ts | 90 ++- src/core/login/providers/helper.ts | 700 +++++++++++++++++++++++ src/providers/app.ts | 23 +- src/providers/config.ts | 4 +- src/theme/variables.scss | 43 ++ 15 files changed, 963 insertions(+), 64 deletions(-) create mode 100644 src/assets/img/logo_white.png create mode 100644 src/core/login/login.module.ts create mode 100644 src/core/login/providers/helper.ts diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 10cfaa120..9d2b521c1 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,21 +1,35 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + import { Component } from '@angular/core'; import { Platform } from 'ionic-angular'; import { StatusBar } from '@ionic-native/status-bar'; import { SplashScreen } from '@ionic-native/splash-screen'; @Component({ - templateUrl: 'app.html' + templateUrl: 'app.html' }) export class MyApp { - rootPage:any = 'InitPage'; + rootPage:any = 'CoreLoginInitPage'; - constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen) { - platform.ready().then(() => { - // Okay, so the platform is ready and our plugins are available. - // Here you can do any higher level native things you might need. - statusBar.styleDefault(); - splashScreen.hide(); - }); - } + constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen) { + platform.ready().then(() => { + // Okay, so the platform is ready and our plugins are available. + // Here you can do any higher level native things you might need. + statusBar.styleDefault(); + splashScreen.hide(); + }); + } } diff --git a/src/app/app.scss b/src/app/app.scss index 1392a6e29..fe1b0ae15 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -14,3 +14,42 @@ // To declare rules for a specific mode, create a child rule // for the .md, .ios, or .wp mode classes. The mode class is // automatically applied to the element in the app. + + +/** + * App Branding + */ +.mm-bglogo { + background-color: $mm-color-init-screen; /* Change this to add a bg image or change color */ + background: -webkit-radial-gradient($mm-color-init-screen-alt, $mm-color-init-screen); + background: radial-gradient($mm-color-init-screen-alt, $mm-color-init-screen); + background-repeat: no-repeat; + background-position: center center; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + height: 100%; + width: 100%; + display: table; + + .mm-logo { + display: table-cell; + text-align: center; + vertical-align: middle; + } + + img { + width: $mm-init-screen-logo-width; + max-width: $mm-init-screen-logo-max-width; + display: block; + margin: 0 auto; + margin-bottom: 30px; + } + + .spinner circle { + stroke: $mm-init-screen-spinner-color; + } +} + diff --git a/src/assets/img/logo_white.png b/src/assets/img/logo_white.png new file mode 100644 index 0000000000000000000000000000000000000000..280aa87f0e2c2540998139de37315ea7fb7c9da1 GIT binary patch literal 18080 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 // Convert the "window" to "any" type to be able to use non-standard properties. // Emulate Custom URL Scheme plugin in desktop apps. diff --git a/src/core/emulator/providers/helper.ts b/src/core/emulator/providers/helper.ts index 987835a22..3c333b25d 100644 --- a/src/core/emulator/providers/helper.ts +++ b/src/core/emulator/providers/helper.ts @@ -25,7 +25,7 @@ import { FileTransferErrorMock } from './file-transfer'; * Emulates the Cordova Zip plugin in desktop apps and in browser. */ @Injectable() -export class CoreEmulatorHelper implements CoreInitHandler { +export class CoreEmulatorHelperProvider implements CoreInitHandler { name = 'CoreEmulator'; priority = CoreInitDelegate.MAX_RECOMMENDED_PRIORITY + 500; blocking = true; diff --git a/src/core/login/login.module.ts b/src/core/login/login.module.ts new file mode 100644 index 000000000..e13f72e96 --- /dev/null +++ b/src/core/login/login.module.ts @@ -0,0 +1,27 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { NgModule } from '@angular/core'; +import { CoreLoginHelperProvider } from './providers/helper'; + +@NgModule({ + declarations: [ + ], + imports: [ + ], + providers: [ + CoreLoginHelperProvider, + ] +}) +export class CoreLoginModule {} diff --git a/src/core/login/pages/init/init.html b/src/core/login/pages/init/init.html index 38b1e2347..8961c1b4e 100644 --- a/src/core/login/pages/init/init.html +++ b/src/core/login/pages/init/init.html @@ -1,18 +1,8 @@ - - - - - Init - - - - - - - + + diff --git a/src/core/login/pages/init/init.module.ts b/src/core/login/pages/init/init.module.ts index 38e4ac377..ee877d5ab 100644 --- a/src/core/login/pages/init/init.module.ts +++ b/src/core/login/pages/init/init.module.ts @@ -1,13 +1,29 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { NgModule } from '@angular/core'; import { IonicPageModule } from 'ionic-angular'; -import { InitPage } from './init'; +import { CoreLoginInitPage } from './init'; +import { CoreLoginModule } from '../../login.module'; @NgModule({ - declarations: [ - InitPage, - ], - imports: [ - IonicPageModule.forChild(InitPage), - ], + declarations: [ + CoreLoginInitPage, + ], + imports: [ + CoreLoginModule, + IonicPageModule.forChild(CoreLoginInitPage), + ], }) -export class InitPageModule {} +export class CoreLoginInitPageModule {} diff --git a/src/core/login/pages/init/init.scss b/src/core/login/pages/init/init.scss index c3cc06952..f9a6bc26e 100644 --- a/src/core/login/pages/init/init.scss +++ b/src/core/login/pages/init/init.scss @@ -1,3 +1,3 @@ -page-init { +page-core-login-init { } diff --git a/src/core/login/pages/init/init.ts b/src/core/login/pages/init/init.ts index be5ed2e27..3f12be846 100644 --- a/src/core/login/pages/init/init.ts +++ b/src/core/login/pages/init/init.ts @@ -1,25 +1,87 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + import { Component } from '@angular/core'; -import { IonicPage, NavController, NavParams } from 'ionic-angular'; +import { IonicPage, NavController } from 'ionic-angular'; +import { CoreAppProvider } from '../../../../providers/app'; +import { CoreInitDelegate } from '../../../../providers/init'; +import { CoreSitesProvider } from '../../../../providers/sites'; +import { CoreConstants } from '../../../constants'; +import { CoreLoginHelperProvider } from '../../providers/helper'; /** - * Generated class for the InitPage page. - * - * See https://ionicframework.com/docs/components/#navigation for more info on - * Ionic pages and navigation. + * Page that displays a "splash screen" while the app is being initialized. */ - @IonicPage() @Component({ - selector: 'page-init', - templateUrl: 'init.html', + selector: 'page-core-login-init', + templateUrl: 'init.html', }) -export class InitPage { +export class CoreLoginInitPage { - constructor(public navCtrl: NavController, public navParams: NavParams) { - } + constructor(private navCtrl: NavController, private appProvider: CoreAppProvider, private initDelegate: CoreInitDelegate, + private sitesProvider: CoreSitesProvider, private loginHelper: CoreLoginHelperProvider) {} - ionViewDidLoad() { - console.log('ionViewDidLoad InitPage'); - } + /** + * View loaded. + */ + ionViewDidLoad() { + // Wait for the app to be ready. + this.initDelegate.ready().then(() => { + // Check if there was a pending redirect. + const redirectData = this.appProvider.getRedirect(); + if (redirectData.siteId && redirectData.page) { + // Unset redirect data. + this.appProvider.storeRedirect('', '', ''); + // Only accept the redirect if it was stored less than 20 seconds ago. + if (Date.now() - redirectData.timemodified < 20000) { + if (redirectData.siteId != CoreConstants.noSiteId) { + // The redirect is pointing to a site, load it. + return this.sitesProvider.loadSite(redirectData.siteId).then(() => { + if (!this.loginHelper.isSiteLoggedOut(redirectData.page, redirectData.params)) { + this.navCtrl.setRoot(redirectData.page, redirectData.params, {animate: false}); + } + }).catch(() => { + // Site doesn't exist. + this.loadPage(); + }); + } else { + // No site to load, just open the state. + return this.navCtrl.setRoot(redirectData.page, redirectData.params, {animate: false}); + } + } + } + + this.loadPage(); + }); + } + + /** + * Load the right page. + */ + protected loadPage() : void { + if (this.sitesProvider.isLoggedIn()) { + if (!this.loginHelper.isSiteLoggedOut()) { + this.loginHelper.goToSiteInitialPage(this.navCtrl, true); + } + } else { + this.sitesProvider.hasSites().then(() => { + this.navCtrl.setRoot('CoreLoginSitesPage'); + }, () => { + this.loginHelper.goToAddSite(this.navCtrl, true); + }); + } + } } diff --git a/src/core/login/providers/helper.ts b/src/core/login/providers/helper.ts new file mode 100644 index 000000000..7f5aecf20 --- /dev/null +++ b/src/core/login/providers/helper.ts @@ -0,0 +1,700 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { NavController } from 'ionic-angular'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreAppProvider } from '../../../providers/app'; +import { CoreConfigProvider } from '../../../providers/config'; +import { CoreEventsProvider } from '../../../providers/events'; +import { CoreLoggerProvider } from '../../../providers/logger'; +import { CoreSitesProvider } from '../../../providers/sites'; +import { CoreWSProvider } from '../../../providers/ws'; +import { CoreDomUtilsProvider } from '../../../providers/utils/dom'; +import { CoreTextUtilsProvider } from '../../../providers/utils/text'; +import { CoreUrlUtilsProvider } from '../../../providers/utils/url'; +import { CoreUtilsProvider } from '../../../providers/utils/utils'; +import { CoreConfigConstants } from '../../../configconstants'; +import { CoreConstants } from '../../constants'; +import { CoreEmulatorHelperProvider } from '../../emulator/providers/helper'; +import { Md5 } from 'ts-md5/dist/md5'; + +export interface CoreLoginSSOData { + siteUrl?: string; + token?: string; + privateToken?: string; + pageName?: string; + pageParams?: any +}; + +/** + * Emulates the Cordova Zip plugin in desktop apps and in browser. + */ +@Injectable() +export class CoreLoginHelperProvider { + protected logger; + + constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider, + private wsProvider: CoreWSProvider, private translate: TranslateService, private textUtils: CoreTextUtilsProvider, + private eventsProvider: CoreEventsProvider, private appProvider: CoreAppProvider, private utils: CoreUtilsProvider, + private urlUtils: CoreUrlUtilsProvider,private configProvider: CoreConfigProvider, + private emulatorHelper: CoreEmulatorHelperProvider) { + this.logger = logger.getInstance('CoreLoginHelper'); + } + + /** + * Accept site policy. + * + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved if success, rejected if failure. + */ + acceptSitePolicy(siteId?: string) : Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return site.write('core_user_agree_site_policy', {}).then((result) => { + if (!result.status) { + // Error. + if (result.warnings && result.warnings.length) { + return Promise.reject(result.warnings[0].message); + } else { + return Promise.reject(null); + } + } + }); + }); + } + + /** + * Check if a site allows requesting a password reset through the app. + * + * @param {string} siteUrl URL of the site. + * @return {Promise} Promise resolved with boolean: whether can be done through the app. + */ + canRequestPasswordReset(siteUrl: string) : Promise { + return this.requestPasswordReset(siteUrl).then(() => { + return true; + }).catch((error) => { + return error.available == 1 || error.errorcode != 'invalidrecord'; + }); + } + + /** + * Show a confirm modal if needed and open a browser to perform SSO login. + * + * @param {string} siteurl URL of the site where the SSO login will be performed. + * @param {number} typeOfLogin CoreConstants.loginSSOCode or CoreConstants.loginSSOInAppCode. + * @param {string} [service] The service to use. If not defined, external service will be used. + * @param {string} [launchUrl] The URL to open for SSO. If not defined, local_mobile launch URL will be used. + * @return {Void} + */ + confirmAndOpenBrowserForSSOLogin(siteUrl: string, typeOfLogin: number, service?: string, launchUrl?: string) : void { + // Show confirm only if it's needed. Treat "false" (string) as false to prevent typing errors. + let showConfirmation = this.shouldShowSSOConfirm(typeOfLogin), + promise; + + if (showConfirmation) { + promise = this.domUtils.showConfirm(this.translate.instant('mm.login.logininsiterequired')); + } else { + promise = Promise.resolve(); + } + + promise.then(() => { + this.openBrowserForSSOLogin(siteUrl, typeOfLogin, service, launchUrl); + }); + } + + /** + * Format profile fields, filtering the ones that shouldn't be shown on signup and classifying them in categories. + * + * @param {any[]} profileFields Profile fields to format. + * @return {any} Categories with the fields to show in each one. + */ + formatProfileFieldsForSignup(profileFields: any[]) : any { + let categories = {}; + + profileFields.forEach((field) => { + if (!field.signup) { + // 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 categories; + } + + /** + * Builds an object with error messages for some common errors. + * Please notice that this function doesn't support all possible error types. + * + * @param {string} [requiredMsg] Code of the string for required error. + * @param {string} [emailMsg] Code of the string for invalid email error. + * @param {string} [patternMsg] Code of the string for pattern not match error. + * @param {string} [urlMsg] Code of the string for invalid url error. + * @param {string} [minlengthMsg] Code of the string for "too short" error. + * @param {string} [maxlengthMsg] Code of the string for "too long" error. + * @param {string} [minMsg] Code of the string for min value error. + * @param {string} [maxMsg] Code of the string for max value error. + * @return {any} Object with the errors. + */ + getErrorMessages(requiredMsg?: string, emailMsg?: string, patternMsg?: string, urlMsg?: string, minlengthMsg?: string, + maxlengthMsg?: string, minMsg?: string, maxMsg?: string) : any { + var errors: any = {}; + + if (requiredMsg) { + errors.required = this.translate.instant(requiredMsg); + } + if (emailMsg) { + errors.email = this.translate.instant(emailMsg); + } + if (patternMsg) { + errors.pattern = this.translate.instant(patternMsg); + } + if (urlMsg) { + errors.url = this.translate.instant(urlMsg); + } + if (minlengthMsg) { + errors.minlength = this.translate.instant(minlengthMsg); + } + if (maxlengthMsg) { + errors.maxlength = this.translate.instant(maxlengthMsg); + } + if (minMsg) { + errors.min = this.translate.instant(minMsg); + } + if (maxMsg) { + errors.max = this.translate.instant(maxMsg); + } + + return errors; + } + + /** + * Get the site policy. + * + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with the site policy. + */ + getSitePolicy(siteId?: string) : Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + // Check if it's stored in the site config. + let sitePolicy = site.getStoredConfig('sitepolicy'); + if (typeof sitePolicy != 'undefined') { + return sitePolicy ? sitePolicy : Promise.reject(null); + } + + // Not in the config, try to get it using auth_email_get_signup_settings. + return this.wsProvider.callAjax('auth_email_get_signup_settings', {}, {siteUrl: site.getURL()}).then((settings) => { + return settings.sitepolicy ? settings.sitepolicy : Promise.reject(null); + }); + }); + } + + /** + * Get fixed site or sites. + * + * @return {string|any[]} Fixed site or list of fixed sites. + */ + getFixedSites() : string|any[] { + return CoreConfigConstants.siteurl; + } + + /** + * Get the valid identity providers from a site config. + * + * @param {any} siteConfig Site's public config. + * @return {any[]} Valid identity providers. + */ + getValidIdentityProviders(siteConfig: any) : any[] { + let validProviders = [], + httpUrl = this.textUtils.concatenatePaths(siteConfig.wwwroot, 'auth/oauth2/'), + httpsUrl = this.textUtils.concatenatePaths(siteConfig.httpswwwroot, 'auth/oauth2/'); + + if (siteConfig.identityproviders && siteConfig.identityproviders.length) { + siteConfig.identityproviders.forEach((provider) => { + if (provider.url && (provider.url.indexOf(httpsUrl) != -1 || provider.url.indexOf(httpUrl) != -1)) { + 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 {NavController} navCtrl The NavController instance to use. + * @param {boolean} [setRoot] True to set the new page as root, false to add it to the stack. + * @return {Promise} Promise resolved when done. + */ + goToAddSite(navCtrl: NavController, setRoot?: boolean) : Promise { + let pageName, + params; + + if (this.isFixedUrlSet()) { + // Fixed URL is set, go to credentials page. + let url = typeof CoreConfigConstants.siteurl == 'string' ? + CoreConfigConstants.siteurl : CoreConfigConstants.siteurl[0].url; + + pageName = 'CoreLoginCredentialsPage'; + params = {siteUrl: url}; + } else { + pageName = 'CoreLoginSitePage'; + } + + if (setRoot) { + return navCtrl.setRoot(pageName, params, {animate: false}); + } else { + return navCtrl.push(pageName, params); + } + } + + /** + * Go to the initial page of a site depending on 'userhomepage' setting. + * + * @param {NavController} navCtrl The NavController instance to use. + * @param {boolean} [setRoot] True to set the new page as root, false to add it to the stack. + * @return {Promise} Promise resolved when done. + */ + goToSiteInitialPage(navCtrl: NavController, setRoot?: boolean) : Promise { + return this.isMyOverviewEnabled().then((myOverview) => { + let myCourses = !myOverview && this.isMyCoursesEnabled(), + site = this.sitesProvider.getCurrentSite(), + promise; + + if (!site) { + return Promise.reject(null); + } + + // Check if frontpage is needed to be shown. (If configured or if any of the other avalaible). + if ((site.getInfo() && site.getInfo().userhomepage === 0) || (!myCourses && !myOverview)) { + promise = this.isFrontpageEnabled(); + } else { + promise = Promise.resolve(false); + } + + return promise.then((frontpage) => { + // Check avalaibility in priority order. + let pageName, + params; + + // @todo Use real pages names when they are implemented. + if (frontpage) { + pageName = 'Frontpage'; + } else if (myOverview) { + pageName = 'MyOverview'; + } else if (myCourses) { + pageName = 'MyCourses'; + } else { + // Anything else available, go to the user profile. + pageName = 'User'; + params = { + userId: site.getUserId() + }; + } + + if (setRoot) { + return navCtrl.setRoot(pageName, params, {animate: false}); + } else { + return navCtrl.push(pageName, params); + } + }); + }); + } + + /** + * 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 {string} siteUrl Site's URL. + * @param {string} token User's token. + * @param {string} [privateToken] User's private token. + * @return {Promise} Promise resolved when the user is authenticated with the token. + */ + handleSSOLoginAuthentication(siteUrl: string, token: string, privateToken?: string) : Promise { + if (this.sitesProvider.isLoggedIn()) { + // User logged in, he is reconnecting. Retrieve username. + var info = this.sitesProvider.getCurrentSite().getInfo(); + if (typeof info != 'undefined' && typeof info.username != 'undefined') { + return this.sitesProvider.updateSiteToken(info.siteurl, info.username, token, privateToken).then(() => { + this.sitesProvider.updateSiteInfoByUrl(info.siteurl, info.username); + }).catch(() => { + // Error updating token, return proper error message. + return Promise.reject(this.translate.instant('mm.login.errorupdatesite')); + }); + } + return Promise.reject(this.translate.instant('mm.login.errorupdatesite')); + } else { + return this.sitesProvider.newSite(siteUrl, token, privateToken); + } + } + + /** + * Check if the app is configured to use several fixed URLs. + * + * @return {boolean} Whether there are several fixed URLs. + */ + hasSeveralFixedSites() : boolean { + return CoreConfigConstants.siteurl && Array.isArray(CoreConfigConstants.siteurl) && + CoreConfigConstants.siteurl.length > 1; + } + + /** + * Given a site public config, check if email signup is disabled. + * + * @param {any} config Site public config. + * @return {boolean} Whether email signup is disabled. + */ + isEmailSignupDisabled(config: any) : boolean { + let disabledFeatures = config && config.tool_mobile_disabledfeatures; + if (!disabledFeatures) { + return false; + } + + let regEx = new RegExp('(,|^)\\$mmLoginEmailSignup(,|$)', 'g'); + return !!disabledFeatures.match(regEx); + } + + /** + * Check if the app is configured to use a fixed URL (only 1). + * + * @return {boolean} Whether there is 1 fixed URL. + */ + isFixedUrlSet() : boolean { + if (Array.isArray(CoreConfigConstants.siteurl)) { + return CoreConfigConstants.siteurl.length == 1; + } + return !!CoreConfigConstants.siteurl; + } + + /** + * Check if the app is configured to use a fixed URL (only 1). + * + * @return {Promise} Promise resolved with boolean: whether there is 1 fixed URL. + */ + protected isFrontpageEnabled() : Promise { + // var $mmaFrontpage = $mmAddonManager.get('$mmaFrontpage'); + // if ($mmaFrontpage && !$mmaFrontpage.isDisabledInSite()) { + // return $mmaFrontpage.isFrontpageAvailable().then(() => { + // return true; + // }).catch(() => { + // return false; + // }); + // } + // @todo: Implement it when front page is implemented. + return Promise.resolve(false); + } + + /** + * Check if My Courses is enabled. + * + * @return {boolean} Whether My Courses is enabled. + */ + protected isMyCoursesEnabled() : boolean { + // @todo: Implement it when My Courses is implemented. + return false; + // return !$mmCourses.isMyCoursesDisabledInSite(); + } + + /** + * Check if My Overview is enabled. + * + * @return {Promise} Promise resolved with boolean: whether My Overview is enabled. + */ + protected isMyOverviewEnabled() : Promise { + // @todo: Implement it when My Overview is implemented. + return Promise.resolve(false); + } + + /** + * Check if current site is logged out, triggering mmCoreEventSessionExpired if it is. + * + * @param {string} [pageName] Name of the page to go once authenticated if logged out. If not defined, site initial page. + * @param {any} [params] Params of the page to go once authenticated if logged out. + * @return {boolean} True if user is logged out, false otherwise. + */ + isSiteLoggedOut(pageName?: string, params?: any) : boolean { + let site = this.sitesProvider.getCurrentSite(); + if (!site) { + return false; + } + + if (site.isLoggedOut()) { + this.eventsProvider.trigger(CoreEventsProvider.SESSION_EXPIRED, { + siteId: site.getId(), + pageName: pageName, + params: params + }); + return true; + } + return false; + } + + /** + * Check if SSO login should use an embedded browser. + * + * @param {number} code Code to check. + * @return {boolean} True if embedded browser, false othwerise. + */ + isSSOEmbeddedBrowser(code: number) : boolean { + if (this.appProvider.isDesktop() && this.emulatorHelper.isLinux()) { + // In Linux desktop apps, always use embedded browser. + return true; + } + + return code == CoreConstants.loginSSOInAppCode; + } + + /** + * Check if SSO login is needed based on code returned by the WS. + * + * @param {number} code Code to check. + * @return {boolean} True if SSO login is needed, false othwerise. + */ + isSSOLoginNeeded(code: number) : boolean { + return code == CoreConstants.loginSSOCode || code == CoreConstants.loginSSOInAppCode; + } + + /** + * Open a browser to perform OAuth login (Google, Facebook, Microsoft). + * + * @param {string} siteUrl URL of the site where the login will be performed. + * @param {any} provider The identity provider. + * @param {string} [launchUrl] The URL to open for SSO. If not defined, tool/mobile launch URL will be used. + * @param {string} [pageName] Name of the page to go once authenticated. If not defined, site initial page. + * @param {any} [pageParams] Params of the state to go once authenticated. + * @return {boolean} True if success, false if error. + */ + openBrowserForOAuthLogin(siteUrl: string, provider: any, launchUrl?: string, pageName?: string, pageParams?: any) : boolean { + launchUrl = launchUrl || siteUrl + '/admin/tool/mobile/launch.php'; + if (!provider || !provider.url) { + return false; + } + + let service = this.sitesProvider.determineService(siteUrl), + loginUrl = this.prepareForSSOLogin(siteUrl, service, launchUrl, pageName, pageParams), + params = this.urlUtils.extractUrlParams(provider.url); + + if (!params.id) { + return false; + } + + loginUrl += '&oauthsso=' + params.id; + + if (this.appProvider.isDesktop() && this.emulatorHelper.isLinux()) { + // In Linux desktop apps, always use embedded browser. + this.utils.openInApp(loginUrl); + } else { + // Always open it in browser because the user might have the session stored in there. + this.utils.openInBrowser(loginUrl); + if ((navigator).app) { + (navigator).app.exitApp(); + } + } + + return true; + } + + /** + * Open a browser to perform SSO login. + * + * @param {string} siteurl URL of the site where the SSO login will be performed. + * @param {number} typeOfLogin CoreConstants.loginSSOCode or CoreConstants.loginSSOInAppCode. + * @param {string} [service] The service to use. If not defined, external service will be used. + * @param {string} [launchUrl] The URL to open for SSO. If not defined, local_mobile launch URL will be used. + * @param {string} [pageName] Name of the page to go once authenticated. If not defined, site initial page. + * @param {any} [pageParams] Params of the state to go once authenticated. + */ + openBrowserForSSOLogin(siteUrl: string, typeOfLogin: number, service?: string, launchUrl?: string, pageName?: string, + pageParams?: any) : void { + let loginUrl = this.prepareForSSOLogin(siteUrl, service, launchUrl, pageName, pageParams); + + if (this.isSSOEmbeddedBrowser(typeOfLogin)) { + let options = { + clearsessioncache: 'yes', // Clear the session cache to allow for multiple logins. + closebuttoncaption: this.translate.instant('mm.login.cancel'), + } + this.utils.openInApp(loginUrl, options); + } else { + this.utils.openInBrowser(loginUrl); + if ((navigator).app) { + (navigator).app.exitApp(); + } + } + } + + /** + * Convenient helper to open change password page. + * + * @param {string} siteUrl Site URL to construct change password URL. + * @param {string} error Error message. + */ + openChangePassword(siteUrl: string, error: string) : void { + let alert = this.domUtils.showAlert(this.translate.instant('mm.core.notice'), error, null, 3000); + alert.onDidDismiss(() => { + this.utils.openInApp(siteUrl + '/login/change_password.php'); + }); + } + + /** + * Open forgotten password in inappbrowser. + * + * @param {string} siteUrl URL of the site. + */ + openForgottenPassword(siteUrl: string) : void { + this.utils.openInApp(siteUrl + '/login/forgot_password.php'); + } + + /** + * Prepare the app to perform SSO login. + * + * @param {string} siteUrl URL of the site where the SSO login will be performed. + * @param {string} [service] The service to use. If not defined, external service will be used. + * @param {string} [launchUrl] The URL to open for SSO. If not defined, local_mobile launch URL will be used. + * @param {string} [pageName] Name of the page to go once authenticated. If not defined, site initial page. + * @param {any} [pageParams] Params of the state to go once authenticated. + */ + prepareForSSOLogin(siteUrl: string, service?: string, launchUrl?: string, pageName?: string, pageParams?: any) : string { + service = service || CoreConfigConstants.wsextservice; + launchUrl = launchUrl || siteUrl + '/local/mobile/launch.php'; + + let passport = Math.random() * 1000, + loginUrl = launchUrl + '?service=' + service; + + loginUrl += "&passport=" + passport; + loginUrl += "&urlscheme=" + CoreConfigConstants.customurlscheme; + + // Store the siteurl and passport in $mmConfig for persistence. We are "configuring" + // the app to wait for an SSO. $mmConfig shouldn't be used as a temporary storage. + this.configProvider.set(CoreConstants.loginLaunchData, JSON.stringify({ + siteUrl: siteUrl, + passport: passport, + pageName: pageName || '', + pageParams: pageParams || {} + })); + + return loginUrl; + } + + /** + * Request a password reset. + * + * @param {string} siteUrl URL of the site. + * @param {string} [username] Username to search. + * @param {string} [email] Email to search. + * @return {Promise} Promise resolved when done. + */ + requestPasswordReset(siteUrl: string, username?: string, email?: string) : Promise { + var params: any = {}; + + if (username) { + params.username = username; + } + + if (email) { + params.email = email; + } + + return this.wsProvider.callAjax('core_auth_request_password_reset', params, {siteUrl: siteUrl}); + } + + /** + * Check if a confirm should be shown to open a SSO authentication. + * + * @param {number} typeOfLogin CoreConstants.loginSSOCode or CoreConstants.loginSSOInAppCode. + * @return {boolean} True if confirm modal should be shown, false otherwise. + */ + shouldShowSSOConfirm(typeOfLogin: number) : boolean { + return !this.isSSOEmbeddedBrowser(typeOfLogin) && + (!CoreConfigConstants.skipssoconfirmation || String(CoreConfigConstants.skipssoconfirmation) === 'false'); + } + + /** + * Convenient helper to handle get User Token error. It redirects to change password page if forcepassword is set. + * + * @param {string} siteUrl Site URL to construct change password URL. + * @param {any} error Error object containing errorcode and error message. + */ + treatUserTokenError(siteUrl: string, error: any) : void { + if (typeof error == 'string') { + this.domUtils.showErrorModal(error); + } else if (error.errorcode == 'forcepasswordchangenotice') { + this.openChangePassword(siteUrl, error.error); + } else { + this.domUtils.showErrorModal(error.error); + } + } + + /** + * Convenient helper to validate a browser SSO login. + * + * @param {string} url URL received, to be validated. + * @return {Promise} Promise resolved on success. + */ + validateBrowserSSOLogin(url: string) : Promise { + // Split signature:::token + const params = url.split(":::"); + + return this.configProvider.get(CoreConstants.loginLaunchData).then((data): any => { + try { + data = JSON.parse(data); + } catch(ex) { + return Promise.reject(null); + } + + let launchSiteURL = data.siteurl, + passport = data.passport; + + // Reset temporary values. + this.configProvider.delete(CoreConstants.loginLaunchData); + + // Validate the signature. + // We need to check both http and https. + let signature = 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 = 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 + } + } else { + this.logger.debug('Invalid signature in the URL request yours: ' + params[0] + ' mine: ' + + signature + ' for passport ' + passport); + return Promise.reject(this.translate.instant('mm.core.unexpectederror')); + } + }); + } +} diff --git a/src/providers/app.ts b/src/providers/app.ts index 4ec2c3b39..5b49e96b1 100644 --- a/src/providers/app.ts +++ b/src/providers/app.ts @@ -21,6 +21,13 @@ import { CoreDbProvider } from './db'; import { CoreLoggerProvider } from './logger'; import { SQLiteDB } from '../classes/sqlitedb'; +export interface CoreRedirectData { + siteId?: string; + page?: string; // Name of the page to redirect. + params?: any; // Params to pass to the page. + timemodified?: number; +}; + /** * Factory to provide some global functionalities, like access to the global app database. * @description @@ -213,16 +220,16 @@ export class CoreAppProvider { /** * Retrieve redirect data. * - * @return {object} Object with siteid, state, params and timemodified. + * @return {CoreRedirectData} Object with siteid, state, params and timemodified. */ - getRedirect() : object { + getRedirect() : CoreRedirectData { if (localStorage && localStorage.getItem) { try { - let data = { - siteid: localStorage.getItem('mmCoreRedirectSiteId'), - state: localStorage.getItem('mmCoreRedirectState'), + let data: CoreRedirectData = { + siteId: localStorage.getItem('mmCoreRedirectSiteId'), + page: localStorage.getItem('mmCoreRedirectState'), params: localStorage.getItem('mmCoreRedirectParams'), - timemodified: localStorage.getItem('mmCoreRedirectTime') + timemodified: parseInt(localStorage.getItem('mmCoreRedirectTime'), 10) }; if (data.params) { @@ -243,9 +250,9 @@ export class CoreAppProvider { * * @param {string} siteId Site ID. * @param {string} page Page to go. - * @param {object} params Page params. + * @param {any} params Page params. */ - storeRedirect(siteId: string, page: string, params: object) : void { + storeRedirect(siteId: string, page: string, params: any) : void { if (localStorage && localStorage.setItem) { try { localStorage.setItem('mmCoreRedirectSiteId', siteId); diff --git a/src/providers/config.ts b/src/providers/config.ts index 0b4fb208c..88cf788c9 100644 --- a/src/providers/config.ts +++ b/src/providers/config.ts @@ -77,10 +77,10 @@ export class CoreConfigProvider { * Set an app setting. * * @param {string} name The config name. - * @param {any} value The config value. Can only store primitive values, not objects. + * @param {boolean|number|string} value The config value. Can only store primitive values, not objects. * @return {Promise} Promise resolved when done. */ - set(name: string, value: any) : Promise { + set(name: string, value: boolean|number|string) : Promise { return this.appDB.insertOrUpdateRecord(this.TABLE_NAME, {name: name, value: value}, {name: name}); } } diff --git a/src/theme/variables.scss b/src/theme/variables.scss index 18276a461..21fdcf593 100644 --- a/src/theme/variables.scss +++ b/src/theme/variables.scss @@ -86,3 +86,46 @@ $colors: ( @import "roboto"; @import "noto-sans"; + + +// Moodle Mobile variables +// -------------------------------------------------- + +// Color palette +$black: #3a3a3a; // Headings, standard text. +$gray-darker: #626262; // Text (emphasis-detail), placeholder, background. +$gray-dark: #9e9e9e; // Borders (never text). +$gray: #dddddd; +$gray-light: #eeeeee; // Background. +$gray-lighter: #f5f5f5; +$white: #ffffff; // Background, reversed text. + +$blue: #0064d2; // Link, background. +$blue-light: mix($blue, white, 20%); // Background. +$blue-dark: darken($blue, 10%); + +$turquoise: #007982; // Accent. +$turquoise-light: mix($turquoise, white, 20%); // Background. +$turquoise-dark: darken($turquoise, 10%); + +$green: #5e8100; // Accent. +$green-light: mix($green, white, 20%); +$green-dark: darken($green, 10%); + +$red: #cb3d4d; +$red-light: mix($red, white, 20%); +$red-dark: darken($red, 10%); + +$orange: #f98012; // Accent (never text). +$orange-light: lighten($orange, 10%); + +$yellow: #fbad1a; // Accent (never text). +$yellow-light: mix($yellow, white, 20%); +$yellow-dark: mix($yellow, black, 40%); + +// Init screen. +$mm-color-init-screen: #ff5c00; +$mm-color-init-screen-alt: #ff7600; +$mm-init-screen-spinner-color: $white; +$mm-init-screen-logo-width: 60%; +$mm-init-screen-logo-max-width: 300px;