Merge pull request #4971 from FinnStutzenstein/anonymousImprovements

Improves the sidebar for anonymous users and routing
This commit is contained in:
Emanuel Schütze 2019-09-05 15:40:19 +02:00 committed by GitHub
commit 975f8dc169
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 155 additions and 66 deletions

View File

@ -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<void> {
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);
}
});

View File

@ -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
}
});
}
}

View File

@ -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;
}
}
}
}

View File

@ -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.');
}
/**

View File

@ -63,16 +63,23 @@ export class OpenSlidesService {
*/
public async bootup(): Promise<void> {
// 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);

View File

@ -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<void> = 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<WhoAmI> {
let response: WhoAmI;
try {
response = await this.storageService.get<WhoAmI>(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<WhoAmI | null> {
let response: WhoAmI | null = null;
try {
response = await this.storageService.get<WhoAmI>(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<void> {
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: []
};
}
}

View File

@ -76,6 +76,14 @@
<!-- navigation -->
<mat-nav-list class="main-nav">
<ng-container *ngIf="!isLoggedIn">
<a routerLink="/login" mat-list-item>
<mat-icon>exit_to_app</mat-icon>
<span translate>Login</span>
</a>
<mat-divider></mat-divider>
</ng-container>
<span *ngFor="let entry of mainMenuService.entries">
<a
[@navItemAnim]

View File

@ -11,7 +11,6 @@ import { filter } from 'rxjs/operators';
import { navItemAnim, pageTransition } from '../shared/animations';
import { OfflineService } from 'app/core/core-services/offline.service';
import { ConfigService } from 'app/core/ui-services/config.service';
import { OverlayService } from 'app/core/ui-services/overlay.service';
import { UpdateService } from 'app/core/ui-services/update.service';
import { langToLocale } from 'app/shared/utils/lang-to-locale';
@ -90,7 +89,6 @@ export class SiteComponent extends BaseComponent implements OnInit {
public constructor(
title: Title,
protected translate: TranslateService,
configService: ConfigService,
offlineService: OfflineService,
private updateService: UpdateService,
private authService: AuthService,
@ -108,11 +106,7 @@ export class SiteComponent extends BaseComponent implements OnInit {
overlayService.setSpinner(true, translate.instant('Loading data. Please wait...'));
this.operator.getViewUserObservable().subscribe(user => {
if (user) {
this.username = user.short_name;
} else if (!user && configService.instant<boolean>('general_system_enable_anonymous')) {
this.username = translate.instant('Guest');
}
this.username = user ? user.short_name : translate.instant('Guest');
this.isLoggedIn = !!user;
});