diff --git a/client/src/app/core/core-services/app-load.service.ts b/client/src/app/core/core-services/app-load.service.ts index 9688e7731..a05b6b074 100644 --- a/client/src/app/core/core-services/app-load.service.ts +++ b/client/src/app/core/core-services/app-load.service.ts @@ -11,11 +11,11 @@ import { CollectionStringMapperService } from './collection-string-mapper.servic import { CommonAppConfig } from '../../site/common/common.config'; import { ConfigAppConfig } from '../../site/config/config.config'; import { ServicesToLoadOnAppsLoaded } from '../core.module'; +import { FallbackRoutesService } from './fallback-routes.service'; import { MainMenuService } from './main-menu.service'; import { MediafileAppConfig } from '../../site/mediafiles/mediafile.config'; import { MotionsAppConfig } from '../../site/motions/motions.config'; import { OnAfterAppsLoaded } from '../definitions/on-after-apps-loaded'; -import { plugins } from '../../../plugins'; import { SearchService } from '../ui-services/search.service'; import { isSearchable } from '../../site/base/searchable'; import { TagAppConfig } from '../../site/tags/tag.config'; @@ -56,17 +56,11 @@ export class AppLoadService { private modelMapper: CollectionStringMapperService, private mainMenuService: MainMenuService, private searchService: SearchService, - private injector: Injector + private injector: Injector, + private fallbackRoutesService: FallbackRoutesService ) {} public async loadApps(): Promise { - if (plugins.length) { - console.log('plugins: ', plugins); - } - /*for (const pluginName of plugins) { - const plugin = await import('../../../../../plugins/' + pluginName + '/' + pluginName); - plugin.main(); - }*/ const repositories: OnAfterAppsLoaded[] = []; appConfigs.forEach((config: AppConfig) => { if (config.models) { @@ -92,6 +86,7 @@ export class AppLoadService { } if (config.mainMenuEntries) { this.mainMenuService.registerEntries(config.mainMenuEntries); + this.fallbackRoutesService.registerFallbackEntries(config.mainMenuEntries); } }); diff --git a/client/src/app/core/core-services/auth-guard.service.ts b/client/src/app/core/core-services/auth-guard.service.ts index 3487b4cab..770c4c43d 100644 --- a/client/src/app/core/core-services/auth-guard.service.ts +++ b/client/src/app/core/core-services/auth-guard.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, Router } from '@angular/router'; +import { FallbackRoutesService } from './fallback-routes.service'; import { OpenSlidesService } from './openslides.service'; import { OperatorService } from './operator.service'; @@ -21,7 +22,8 @@ export class AuthGuard implements CanActivate, CanActivateChild { public constructor( private router: Router, private operator: OperatorService, - private openSlidesService: OpenSlidesService + private openSlidesService: OpenSlidesService, + private fallbackRoutesService: FallbackRoutesService ) {} /** @@ -56,13 +58,34 @@ export class AuthGuard implements CanActivate, CanActivateChild { if (this.canActivate(route)) { return true; } else { - this.openSlidesService.redirectUrl = location.pathname; - this.router.navigate(['/error'], { - queryParams: { - error: 'Authentication Error', - msg: route.data.basePerm - } - }); + this.handleForbiddenRoute(route); } } + + /** + * Handles a forbidden route. If the route is "/" (start page), It is tried to + * use a fallback route provided by AuthGuardFallbackRoutes. If this won't work + * or it wasn't the start page in the first place, the operator will be redirected + * to an error page. + */ + private handleForbiddenRoute(route: ActivatedRouteSnapshot): void { + if (route.url.length === 0) { + // start page + const fallbackRoute = this.fallbackRoutesService.getFallbackRoute(); + if (fallbackRoute) { + this.router.navigate([fallbackRoute]); + return; + } + } + // Fall-through: If the url is the start page, but no other fallback was found, + // navigate to the error page. + + this.openSlidesService.redirectUrl = location.pathname; + this.router.navigate(['/error'], { + queryParams: { + error: 'Authentication Error', + msg: route.data.basePerm + } + }); + } } diff --git a/client/src/app/core/core-services/fallback-routes.service.ts b/client/src/app/core/core-services/fallback-routes.service.ts new file mode 100644 index 000000000..93189e701 --- /dev/null +++ b/client/src/app/core/core-services/fallback-routes.service.ts @@ -0,0 +1,43 @@ +import { Injectable } from '@angular/core'; + +import { OperatorService } from './operator.service'; + +export interface AuthGuardFallbackEntry { + route: string; + weight: number; + permission: string; +} + +/** + * Classical Auth-Guard. Checks if the user has to correct permissions to enter a page, and forwards to login if not. + */ +@Injectable({ + providedIn: 'root' +}) +export class FallbackRoutesService { + private fallbackEntries: AuthGuardFallbackEntry[] = []; + + /** + * Constructor + * + * @param operator Asking for the required permission + */ + public constructor(private operator: OperatorService) {} + + /** + * Adds fallback navigation entries for the start page. + * @param entries The entries to add + */ + public registerFallbackEntries(entries: AuthGuardFallbackEntry[]): void { + this.fallbackEntries.push(...entries); + this.fallbackEntries = this.fallbackEntries.sort((a, b) => a.weight - b.weight); + } + + public getFallbackRoute(): string | null { + for (const entry of this.fallbackEntries) { + if (this.operator.hasPerms(entry.permission)) { + return entry.route; + } + } + } +} diff --git a/client/src/app/core/core-services/offline.service.ts b/client/src/app/core/core-services/offline.service.ts index 89b7b9f83..b8d6a238f 100644 --- a/client/src/app/core/core-services/offline.service.ts +++ b/client/src/app/core/core-services/offline.service.ts @@ -30,16 +30,20 @@ export class OfflineService { * Sets the offline flag. Restores the DataStoreService to the last known configuration. */ public goOfflineBecauseFailedWhoAmI(): void { + if (!this.offline.getValue()) { + console.log('offline because whoami failed.'); + } this.offline.next(true); - console.log('offline because whoami failed.'); } /** * Sets the offline flag, because there is no connection to the server. */ public goOfflineBecauseConnectionLost(): void { + if (!this.offline.getValue()) { + console.log('offline because connection lost.'); + } this.offline.next(true); - console.log('offline because connection lost.'); } /** diff --git a/client/src/app/core/core-services/openslides.service.ts b/client/src/app/core/core-services/openslides.service.ts index 093998101..8dbfdf351 100644 --- a/client/src/app/core/core-services/openslides.service.ts +++ b/client/src/app/core/core-services/openslides.service.ts @@ -63,16 +63,23 @@ export class OpenSlidesService { */ public async bootup(): Promise { // start autoupdate if the user is logged in: - const response = await this.operator.whoAmIFromStorage(); + let response = await this.operator.whoAmIFromStorage(); + const needToCheckOperator = !!response; + + if (!response) { + response = await this.operator.whoAmI(); + } + if (!response.user && !response.guest_enabled) { if (!location.pathname.includes('error')) { this.redirectUrl = location.pathname; } this.redirectToLoginIfNotSubpage(); - this.checkOperator(false); } else { await this.afterLoginBootup(response.user_id); + } + if (needToCheckOperator) { // Check for the operator via a async whoami (so no await here) // to validate, that the cache was correct. this.checkOperator(false); diff --git a/client/src/app/core/core-services/operator.service.ts b/client/src/app/core/core-services/operator.service.ts index ef91f90a2..3da24aa6f 100644 --- a/client/src/app/core/core-services/operator.service.ts +++ b/client/src/app/core/core-services/operator.service.ts @@ -116,10 +116,30 @@ export class OperatorService implements OnAfterAppsLoaded { */ private userRepository: UserRepositoryService | null; + private _currentWhoAmI: WhoAmI | null = null; + private _defaultWhoAMI: WhoAmI = { + user_id: null, + guest_enabled: false, + user: null, + permissions: [] + }; + /** * The current WhoAmI response to extract the user (the operator) from. */ - private currentWhoAmI: WhoAmI | null; + private get currentWhoAmI(): WhoAmI { + return this._currentWhoAmI || this._defaultWhoAMI; + } + + private set currentWhoAmI(value: WhoAmI | null) { + this._currentWhoAmI = value; + + // Resetting the default whoami, when the current whoami isn't there. This + // is for a fresh restart and do not have (old) changed values in this.defaultWhoAmI + if (!value) { + this._defaultWhoAMI = this.getDefaultWhoAmIResponse(); + } + } private readonly _loaded: Deferred = new Deferred(); @@ -169,7 +189,7 @@ export class OperatorService implements OnAfterAppsLoaded { ), auditTime(10) ) - .subscribe(_ => this.updatePermissions()); + .subscribe(() => this.updatePermissions()); // Watches the user observable to update the viewUser for the operator. this.getUserObservable().subscribe(user => { @@ -198,36 +218,6 @@ export class OperatorService implements OnAfterAppsLoaded { }); } - /** - * Returns a default WhoAmI response - */ - private getDefaultWhoAmIResponse(): WhoAmI { - return { - user_id: null, - guest_enabled: false, - user: null, - permissions: [] - }; - } - - /** - * Gets the current WhoAmI response from the storage. - */ - public async whoAmIFromStorage(): Promise { - let response: WhoAmI; - try { - response = await this.storageService.get(WHOAMI_STORAGE_KEY); - if (!response) { - response = this.getDefaultWhoAmIResponse(); - } - } catch (e) { - response = this.getDefaultWhoAmIResponse(); - } - await this.updateCurrentWhoAmI(response); - this._loaded.resolve(); - return this.currentWhoAmI; - } - /** * Load the repo to get a view user. */ @@ -239,6 +229,24 @@ export class OperatorService implements OnAfterAppsLoaded { this.viewOperatorSubject.next(this._viewUser); } + /** + * Gets the current WhoAmI response from the storage. + */ + public async whoAmIFromStorage(): Promise { + let response: WhoAmI | null = null; + try { + response = await this.storageService.get(WHOAMI_STORAGE_KEY); + if (!isWhoAmI(response)) { + response = null; + } + } catch (e) {} + + if (response) { + await this.updateCurrentWhoAmI(response); + } + return response; + } + /** * Sets the operator user. Will be saved to storage * @param user The new operator. @@ -274,9 +282,6 @@ export class OperatorService implements OnAfterAppsLoaded { * WhoAMI response. */ private async updateUserInCurrentWhoAmI(): Promise { - if (!this.currentWhoAmI) { - this.currentWhoAmI = this.getDefaultWhoAmIResponse(); - } if (this.isAnonymous) { this.currentWhoAmI.user_id = null; this.currentWhoAmI.user = null; @@ -300,6 +305,7 @@ export class OperatorService implements OnAfterAppsLoaded { this._user = whoami ? whoami.user : null; await this.updatePermissions(); + this._loaded.resolve(); } /** @@ -394,9 +400,6 @@ export class OperatorService implements OnAfterAppsLoaded { } // Save perms to current WhoAmI - if (!this.currentWhoAmI) { - this.currentWhoAmI = this.getDefaultWhoAmIResponse(); - } this.currentWhoAmI.permissions = this.permissions; if (!this.OSStatus.isInHistoryMode) { @@ -406,4 +409,16 @@ export class OperatorService implements OnAfterAppsLoaded { // publish changes in the operator. this.operatorSubject.next(this.user); } + + /** + * Returns a default WhoAmI response + */ + private getDefaultWhoAmIResponse(): WhoAmI { + return { + user_id: null, + guest_enabled: false, + user: null, + permissions: [] + }; + } } diff --git a/client/src/app/site/site.component.html b/client/src/app/site/site.component.html index 3dde67a0f..95bffbf61 100644 --- a/client/src/app/site/site.component.html +++ b/client/src/app/site/site.component.html @@ -76,6 +76,14 @@ + + + exit_to_app + Login + + + + { - if (user) { - this.username = user.short_name; - } else if (!user && configService.instant('general_system_enable_anonymous')) { - this.username = translate.instant('Guest'); - } + this.username = user ? user.short_name : translate.instant('Guest'); this.isLoggedIn = !!user; });