diff --git a/src/core/classes/sites/unauthenticated-site.ts b/src/core/classes/sites/unauthenticated-site.ts index 68ef3f975..3d4a5e3fa 100644 --- a/src/core/classes/sites/unauthenticated-site.ts +++ b/src/core/classes/sites/unauthenticated-site.ts @@ -464,6 +464,7 @@ export type CoreSitePublicConfigResponse = { tool_mobile_setuplink?: string; // App download page. tool_mobile_qrcodetype?: CoreSiteQRCodeType; // eslint-disable-line @typescript-eslint/naming-convention warnings?: CoreWSExternalWarning[]; + showloginform?: number; // @since 4.5. Display default login form. }; /** diff --git a/src/core/features/login/components/login-methods/login-methods.html b/src/core/features/login/components/login-methods/login-methods.html index 9a405ae15..be93e7972 100644 --- a/src/core/features/login/components/login-methods/login-methods.html +++ b/src/core/features/login/components/login-methods/login-methods.html @@ -1,4 +1,5 @@ -
+
{{ 'core.login.or' | translate }}
diff --git a/src/core/features/login/components/login-methods/login-methods.ts b/src/core/features/login/components/login-methods/login-methods.ts index 88ac5a23e..f4c24f255 100644 --- a/src/core/features/login/components/login-methods/login-methods.ts +++ b/src/core/features/login/components/login-methods/login-methods.ts @@ -32,6 +32,7 @@ export class CoreLoginMethodsComponent implements OnInit { @Input() siteUrl = ''; @Input() siteConfig?: CoreSitePublicConfigResponse; @Input() redirectData?: CoreRedirectPayload; + @Input() showLoginForm = true; isBrowserSSO = false; showScanQR = false; diff --git a/src/core/features/login/constants.ts b/src/core/features/login/constants.ts index b83fbe9d6..2cb7ac285 100644 --- a/src/core/features/login/constants.ts +++ b/src/core/features/login/constants.ts @@ -21,6 +21,8 @@ export const EMAIL_SIGNUP_FEATURE_NAME = 'CoreLoginEmailSignup'; export const FORGOTTEN_PASSWORD_FEATURE_NAME = 'NoDelegate_ForgottenPassword'; export const IDENTITY_PROVIDERS_FEATURE_NAME = 'NoDelegate_IdentityProviders'; export const IDENTITY_PROVIDER_FEATURE_NAME_PREFIX = 'NoDelegate_IdentityProvider_'; +export const ALWAYS_SHOW_LOGIN_FORM = 'always_show_login_form'; +export const ALWAYS_SHOW_LOGIN_FORM_CHANGED = 'always_show_login_form_changed'; // Event indicating that a user left the app because it wasn't supported by a site. export const APP_UNSUPPORTED_CHURN = 'app_unsupported_churn'; diff --git a/src/core/features/login/pages/credentials/credentials.html b/src/core/features/login/pages/credentials/credentials.html index caacc02d9..c6d71047b 100644 --- a/src/core/features/login/pages/credentials/credentials.html +++ b/src/core/features/login/pages/credentials/credentials.html @@ -43,7 +43,8 @@
-
+ - +
diff --git a/src/core/features/login/pages/credentials/credentials.ts b/src/core/features/login/pages/credentials/credentials.ts index aad90fa57..8c8ccaa14 100644 --- a/src/core/features/login/pages/credentials/credentials.ts +++ b/src/core/features/login/pages/credentials/credentials.ts @@ -24,7 +24,7 @@ import { CoreDomUtils } from '@services/utils/dom'; import { CoreLoginHelper } from '@features/login/services/login-helper'; import { Translate } from '@singletons'; import { CoreSitePublicConfigResponse, CoreUnauthenticatedSite } from '@classes/sites/unauthenticated-site'; -import { CoreEvents } from '@singletons/events'; +import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreNavigator } from '@services/navigator'; import { CoreForms } from '@singletons/form'; import { CoreUserSupport } from '@features/user/services/support'; @@ -33,7 +33,11 @@ import { CoreUserGuestSupportConfig } from '@features/user/classes/support/guest import { SafeHtml } from '@angular/platform-browser'; import { CorePlatform } from '@services/platform'; import { CoreSitesFactory } from '@services/sites-factory'; -import { EMAIL_SIGNUP_FEATURE_NAME, FORGOTTEN_PASSWORD_FEATURE_NAME } from '@features/login/constants'; +import { + ALWAYS_SHOW_LOGIN_FORM_CHANGED, + EMAIL_SIGNUP_FEATURE_NAME, + FORGOTTEN_PASSWORD_FEATURE_NAME, +} from '@features/login/constants'; import { CoreCustomURLSchemes } from '@services/urlschemes'; import { CoreSiteError } from '@classes/errors/siteerror'; import { CoreKeyboard } from '@singletons/keyboard'; @@ -66,6 +70,7 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy { siteConfig?: CoreSitePublicConfigResponse; siteCheckError = ''; displaySiteUrl = false; + showLoginForm = true; protected siteCheck?: CoreSiteCheckResponse; protected eventThrown = false; @@ -73,6 +78,7 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy { protected siteId?: string; protected urlToOpen?: string; protected valueChangeSubscription?: Subscription; + protected alwaysShowLoginFormObserver?: CoreEventObserver; constructor( protected fb: FormBuilder, @@ -137,6 +143,10 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy { } }); } + + this.alwaysShowLoginFormObserver = CoreEvents.on(ALWAYS_SHOW_LOGIN_FORM_CHANGED, async () => { + this.showLoginForm = await CoreLoginHelper.shouldShowLoginForm(this.siteConfig); + }); } /** @@ -191,6 +201,8 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy { * Treat the site configuration (if it exists). */ protected async treatSiteConfig(): Promise { + this.showLoginForm = await CoreLoginHelper.shouldShowLoginForm(this.siteConfig); + if (!this.siteConfig) { this.authInstructions = undefined; this.canSignup = false; @@ -365,6 +377,7 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy { this.siteId, ); this.valueChangeSubscription?.unsubscribe(); + this.alwaysShowLoginFormObserver?.off(); } } diff --git a/src/core/features/login/pages/reconnect/reconnect.html b/src/core/features/login/pages/reconnect/reconnect.html index 3c529966e..2a5c92b3a 100644 --- a/src/core/features/login/pages/reconnect/reconnect.html +++ b/src/core/features/login/pages/reconnect/reconnect.html @@ -59,7 +59,8 @@
- + + [redirectData]="redirectData" [showLoginForm]="showLoginForm" />
diff --git a/src/core/features/login/pages/reconnect/reconnect.ts b/src/core/features/login/pages/reconnect/reconnect.ts index 642831241..181ef2f13 100644 --- a/src/core/features/login/pages/reconnect/reconnect.ts +++ b/src/core/features/login/pages/reconnect/reconnect.ts @@ -21,7 +21,7 @@ import { CoreDomUtils } from '@services/utils/dom'; import { CoreUtils } from '@services/utils/utils'; import { CoreLoginHelper } from '@features/login/services/login-helper'; import { CoreSite } from '@classes/sites/site'; -import { CoreEvents } from '@singletons/events'; +import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreError } from '@classes/errors/error'; import { CoreNavigator, CoreRedirectPayload } from '@services/navigator'; import { CoreForms } from '@singletons/form'; @@ -31,7 +31,7 @@ import { CoreUserAuthenticatedSupportConfig } from '@features/user/classes/suppo import { Translate } from '@singletons'; import { SafeHtml } from '@angular/platform-browser'; import { CoreSitePublicConfigResponse } from '@classes/sites/unauthenticated-site'; -import { FORGOTTEN_PASSWORD_FEATURE_NAME } from '@features/login/constants'; +import { ALWAYS_SHOW_LOGIN_FORM_CHANGED, FORGOTTEN_PASSWORD_FEATURE_NAME } from '@features/login/constants'; import { CoreKeyboard } from '@singletons/keyboard'; /** @@ -63,11 +63,13 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy { exceededAttemptsHTML?: SafeHtml | string | null; siteConfig?: CoreSitePublicConfigResponse; redirectData?: CoreRedirectPayload; + showLoginForm = true; protected viewLeft = false; protected eventThrown = false; protected loginSuccessful = false; protected username = ''; + protected alwaysShowLoginFormObserver?: CoreEventObserver; constructor( protected fb: FormBuilder, @@ -126,6 +128,10 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy { await this.checkSiteConfig(); + this.alwaysShowLoginFormObserver = CoreEvents.on(ALWAYS_SHOW_LOGIN_FORM_CHANGED, async () => { + this.showLoginForm = await CoreLoginHelper.shouldShowLoginForm(this.siteConfig); + }); + this.showLoading = false; } catch (error) { CoreDomUtils.showErrorModal(error); @@ -147,6 +153,7 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy { }, this.siteId, ); + this.alwaysShowLoginFormObserver?.off(); } /** @@ -168,6 +175,8 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy { readingStrategy: CoreSitesReadingStrategy.PREFER_NETWORK, })); + this.showLoginForm = await CoreLoginHelper.shouldShowLoginForm(this.siteConfig); + if (!this.siteConfig) { return; } diff --git a/src/core/features/login/services/login-helper.ts b/src/core/features/login/services/login-helper.ts index 856d729ca..8a92e8108 100644 --- a/src/core/features/login/services/login-helper.ts +++ b/src/core/features/login/services/login-helper.ts @@ -47,6 +47,8 @@ import { TypeOfLogin, } from '@classes/sites/unauthenticated-site'; import { + ALWAYS_SHOW_LOGIN_FORM, + ALWAYS_SHOW_LOGIN_FORM_CHANGED, APP_UNSUPPORTED_CHURN, EMAIL_SIGNUP_FEATURE_NAME, FAQ_QRCODE_IMAGE_HTML, @@ -924,6 +926,21 @@ export class CoreLoginHelperProvider { } } + /** + * Check if the default login form should be displayed. + * + * @param config Site public config. + * @returns True if the login form should be displayed. + */ + async shouldShowLoginForm(config?: CoreSitePublicConfigResponse): Promise { + // Only hide the form if the setting exists and is set to 0. + if (config?.showloginform === 0) { + return Boolean(await CoreConfig.get(ALWAYS_SHOW_LOGIN_FORM, 0)); + } + + return true; + } + /** * Check if a confirm should be shown to open a SSO authentication. * @@ -1692,6 +1709,7 @@ declare module '@singletons/events' { * @see https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation */ export interface CoreEventsData { + [ALWAYS_SHOW_LOGIN_FORM_CHANGED]: { value: number }; [APP_UNSUPPORTED_CHURN]: { siteUrl: string; debug?: CoreSiteErrorDebug }; } diff --git a/src/core/features/login/tests/behat/showloginform_setting.feature b/src/core/features/login/tests/behat/showloginform_setting.feature new file mode 100644 index 000000000..56a02a978 --- /dev/null +++ b/src/core/features/login/tests/behat/showloginform_setting.feature @@ -0,0 +1,60 @@ +@core_login @app @javascript @lms_from4.5 +Feature: Test showloginform setting in the app + + Background: + Given the Moodle site is compatible with this feature + And the following config values are set as admin: + | showloginform | 0 | + And the following "users" exist: + | username | firstname | lastname | + | student | david | student | + + Scenario: Login + When I launch the app + And I set the field "Your site" to "$WWWROOT" in the app + And I press "Connect to your site" in the app + Then the header should be "Log in" in the app + And I should not find "Log in" "ion-button" in the app + And I replace "/.*/" within ".core-siteurl" with "https://campus.example.edu" + And the UI should match the snapshot + + Scenario: Reconnect + When I entered the app as "student" + And I log out in the app + And I press "david student" in the app + Then the header should be "Reconnect" in the app + And I should not find "Log in" "ion-button" in the app + And I replace "/.*/" within ".core-siteurl" with "https://campus.example.edu" + And the UI should match the snapshot + + Scenario: Login with forced developer option + When I launch the app + And I set the field "Your site" to "$WWWROOT" in the app + And I press "Connect to your site" in the app + And I press "App settings" in the app + And I press "About" in the app + And I press "Moodle Mobile" in the app + And I press "Developer options" in the app + And I press "Always show login form" in the app + And I press the back button in the app + And I press the back button in the app + And I press the back button in the app + And I press the back button in the app + Then the header should be "Log in" in the app + And I should find "Log in" "ion-button" in the app + + Scenario: Reconnect with forced developer option + When I entered the app as "student" + And I log out in the app + And I press "App settings" in the app + And I press "About" in the app + And I press "Moodle Mobile" in the app + And I press "Developer options" in the app + And I press "Always show login form" in the app + And I press the back button in the app + And I press the back button in the app + And I press the back button in the app + And I press the back button in the app + And I press "david student" in the app + Then the header should be "Reconnect" in the app + And I should find "Log in" "ion-button" in the app diff --git a/src/core/features/login/tests/behat/snapshots/test-showloginform-setting-in-the-app-login_10.png b/src/core/features/login/tests/behat/snapshots/test-showloginform-setting-in-the-app-login_10.png new file mode 100644 index 000000000..c31717c64 Binary files /dev/null and b/src/core/features/login/tests/behat/snapshots/test-showloginform-setting-in-the-app-login_10.png differ diff --git a/src/core/features/login/tests/behat/snapshots/test-showloginform-setting-in-the-app-reconnect_10.png b/src/core/features/login/tests/behat/snapshots/test-showloginform-setting-in-the-app-reconnect_10.png new file mode 100644 index 000000000..24599dab7 Binary files /dev/null and b/src/core/features/login/tests/behat/snapshots/test-showloginform-setting-in-the-app-reconnect_10.png differ diff --git a/src/core/features/settings/pages/dev/dev.html b/src/core/features/settings/pages/dev/dev.html index bc6f5a9b7..4a82ee107 100644 --- a/src/core/features/settings/pages/dev/dev.html +++ b/src/core/features/settings/pages/dev/dev.html @@ -33,6 +33,11 @@

Enable staging sites {{stagingSitesCount}}

+ + +

Always show login form

+
+
diff --git a/src/core/features/settings/pages/dev/dev.ts b/src/core/features/settings/pages/dev/dev.ts index 8b2888399..7c9464ffd 100644 --- a/src/core/features/settings/pages/dev/dev.ts +++ b/src/core/features/settings/pages/dev/dev.ts @@ -14,12 +14,18 @@ import { CoreConstants } from '@/core/constants'; import { Component, OnInit } from '@angular/core'; -import { FAQ_QRCODE_INFO_DONE, ONBOARDING_DONE } from '@features/login/constants'; +import { + ALWAYS_SHOW_LOGIN_FORM, + ALWAYS_SHOW_LOGIN_FORM_CHANGED, + FAQ_QRCODE_INFO_DONE, + ONBOARDING_DONE, +} from '@features/login/constants'; import { CoreSettingsHelper } from '@features/settings/services/settings-helper'; import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins'; import { CoreUserTours } from '@features/usertours/services/user-tours'; import { CoreCacheManager } from '@services/cache-manager'; import { CoreConfig } from '@services/config'; +import { CoreEvents } from '@singletons/events'; import { CoreFile } from '@services/file'; import { CoreNavigator } from '@services/navigator'; import { CorePlatform } from '@services/platform'; @@ -40,6 +46,7 @@ export class CoreSettingsDevPage implements OnInit { rtl = false; forceSafeAreaMargins = false; direction = 'ltr'; + alwaysShowLoginForm = false; remoteStyles = true; remoteStylesCount = 0; @@ -70,6 +77,7 @@ export class CoreSettingsDevPage implements OnInit { this.enableStagingSites = await CoreSettingsHelper.hasEnabledStagingSites(); this.previousEnableStagingSites = this.enableStagingSites; } + this.alwaysShowLoginForm = Boolean(await CoreConfig.get(ALWAYS_SHOW_LOGIN_FORM, 0)); if (!this.siteId) { return; @@ -125,6 +133,15 @@ export class CoreSettingsDevPage implements OnInit { document.documentElement.classList.toggle('force-safe-area-margins', this.forceSafeAreaMargins); } + /** + * Called when always show login form is enabled or disabled. + */ + async alwaysShowLoginFormChanged(): Promise { + const value = Number(this.alwaysShowLoginForm); + await CoreConfig.set(ALWAYS_SHOW_LOGIN_FORM, value); + CoreEvents.trigger(ALWAYS_SHOW_LOGIN_FORM_CHANGED, { value }); + } + /** * Called when remote styles is enabled or disabled. */