OpenSlides/client/src/app/core/core-services/operator.service.ts

367 lines
12 KiB
TypeScript
Raw Normal View History

import { Injectable } from '@angular/core';
import { Observable, BehaviorSubject } from 'rxjs';
import { Group } from 'app/shared/models/users/group';
import { User } from '../../shared/models/users/user';
import { environment } from 'environments/environment';
2018-09-13 14:40:04 +02:00
import { DataStoreService } from './data-store.service';
import { Deferred } from '../deferred';
2018-10-26 10:23:14 +02:00
import { OfflineService } from './offline.service';
import { OpenSlidesStatusService } from './openslides-status.service';
import { ViewUser } from 'app/site/users/models/view-user';
import { OnAfterAppsLoaded } from '../onAfterAppsLoaded';
import { UserRepositoryService } from '../repositories/users/user-repository.service';
2019-03-04 11:45:15 +01:00
import { CollectionStringMapperService } from './collectionStringMapper.service';
import { StorageService } from './storage.service';
import { HttpService } from './http.service';
2019-03-07 10:47:03 +01:00
import { filter, auditTime } from 'rxjs/operators';
/**
* Permissions on the client are just strings. This makes clear, that
* permissions instead of arbitrary strings should be given.
*/
export type Permission = string;
/**
2019-03-04 11:45:15 +01:00
* Response format of the WhoAmI request.
*/
2019-03-07 10:47:03 +01:00
export interface WhoAmI {
user_id: number;
guest_enabled: boolean;
user: User;
2019-03-07 10:47:03 +01:00
permissions: Permission[];
}
2019-03-07 10:47:03 +01:00
function isWhoAmI(obj: any): obj is WhoAmI {
2019-03-04 11:45:15 +01:00
if (!obj) {
return false;
}
2019-03-07 10:47:03 +01:00
const whoAmI = obj as WhoAmI;
return (
whoAmI.guest_enabled !== undefined &&
whoAmI.user !== undefined &&
whoAmI.user_id !== undefined &&
whoAmI.permissions !== undefined
);
2019-03-04 11:45:15 +01:00
}
const WHOAMI_STORAGE_KEY = 'whoami';
/**
* The operator represents the user who is using OpenSlides.
*
* Changes in operator can be observed, directives do so on order to show
* or hide certain information.
*/
@Injectable({
providedIn: 'root'
})
2019-02-08 17:24:32 +01:00
export class OperatorService implements OnAfterAppsLoaded {
/**
* The operator.
*/
private _user: User;
2019-03-04 11:45:15 +01:00
public get user(): User {
return this._user;
}
/**
* The operator as a view user. We need a separation here, because
* we need to acces the operators permissions, before we get data
* from the server to build the view user.
*/
private _viewUser: ViewUser;
/**
* Get the user that corresponds to operator.
*/
public get viewUser(): ViewUser {
return this._viewUser;
}
public get isAnonymous(): boolean {
return !this.user || this.user.id === 0;
}
/**
2019-03-04 11:45:15 +01:00
* Save, if guests are enabled.
*/
2019-03-04 11:45:15 +01:00
public get guestsEnabled(): boolean {
2019-03-07 10:47:03 +01:00
return this.currentWhoAmI ? this.currentWhoAmI.guest_enabled : false;
2019-03-04 11:45:15 +01:00
}
/**
* The permissions of the operator. Updated via {@method updatePermissions}.
*/
private permissions: Permission[] = [];
/**
* The subject that can be observed by other instances using observing functions.
*/
private operatorSubject: BehaviorSubject<User> = new BehaviorSubject<User>(null);
/**
* Subject for the operator as a view user.
*/
private viewOperatorSubject: BehaviorSubject<ViewUser> = new BehaviorSubject<ViewUser>(null);
/**
* Do not access the repo before it wasn't loaded. Will be true after `onAfterAppsLoaded`.
*/
2019-03-04 11:45:15 +01:00
private userRepository: UserRepositoryService | null;
/**
* The current WhoAmI response to extract the user (the operator) from.
*/
2019-03-07 10:47:03 +01:00
private currentWhoAmI: WhoAmI | null;
private readonly _loaded: Deferred<void> = new Deferred();
public get loaded(): Promise<void> {
return this._loaded.promise;
}
/**
2018-10-26 10:23:14 +02:00
* Sets up an observer for watching changes in the DS. If the operator user or groups are changed,
* the operator's permissions are updated.
*
2019-03-04 11:45:15 +01:00
* @param http
2018-10-26 10:23:14 +02:00
* @param DS
* @param offlineService
*/
public constructor(
2019-03-04 11:45:15 +01:00
private http: HttpService,
private DS: DataStoreService,
private offlineService: OfflineService,
2019-03-04 11:45:15 +01:00
private collectionStringMapper: CollectionStringMapperService,
private storageService: StorageService,
private OSStatus: OpenSlidesStatusService
) {
this.DS.changeObservable.subscribe(newModel => {
2019-03-07 10:47:03 +01:00
if (this._user && newModel instanceof User && this._user.id === newModel.id) {
this._user = newModel;
this.updateUserInCurrentWhoAmI();
}
});
2019-03-07 10:47:03 +01:00
this.DS.changeObservable
.pipe(
filter(
model =>
// Any group has changed if we have an operator or
// group 1 (default) for anonymous changed
model instanceof Group && (!!this._user || model.id === 1)
),
auditTime(10)
)
.subscribe(newModel => this.updatePermissions());
}
2019-03-04 11:45:15 +01:00
/**
2019-03-07 10:47:03 +01:00
* Returns a default WhoAmI response
2019-03-04 11:45:15 +01:00
*/
2019-03-07 10:47:03 +01:00
private getDefaultWhoAmIResponse(): WhoAmI {
return {
2019-03-04 11:45:15 +01:00
user_id: null,
guest_enabled: false,
2019-03-07 10:47:03 +01:00
user: null,
permissions: []
2019-03-04 11:45:15 +01:00
};
2019-03-07 10:47:03 +01:00
}
/**
* Gets the current WhoAmI response from the storage.
*/
public async whoAmIFromStorage(): Promise<WhoAmI> {
let response: WhoAmI;
2019-03-04 11:45:15 +01:00
try {
2019-03-07 10:47:03 +01:00
response = await this.storageService.get<WhoAmI>(WHOAMI_STORAGE_KEY);
if (!response) {
response = this.getDefaultWhoAmIResponse();
2019-03-04 11:45:15 +01:00
}
} catch (e) {
2019-03-07 10:47:03 +01:00
response = this.getDefaultWhoAmIResponse();
2019-03-04 11:45:15 +01:00
}
2019-03-07 10:47:03 +01:00
await this.updateCurrentWhoAmI(response);
this._loaded.resolve();
2019-03-07 10:47:03 +01:00
return this.currentWhoAmI;
2019-03-04 11:45:15 +01:00
}
/**
* Load the repo to get a view user.
*/
public onAfterAppsLoaded(): void {
2019-03-04 11:45:15 +01:00
this.userRepository = this.collectionStringMapper.getRepository(ViewUser) as UserRepositoryService;
if (this.user) {
this._viewUser = this.userRepository.getViewModel(this.user.id);
}
}
2019-03-04 11:45:15 +01:00
/**
* Sets the operator user. Will be saved to storage
* @param user The new operator.
*/
2019-03-07 10:47:03 +01:00
public async setWhoAmI(whoami: WhoAmI | null): Promise<void> {
if (whoami === null) {
whoami = this.getDefaultWhoAmIResponse();
}
2019-03-07 10:47:03 +01:00
await this.updateCurrentWhoAmI(whoami);
}
/**
* Calls `/apps/users/whoami` to find out the real operator.
2019-03-04 11:45:15 +01:00
*
2018-10-26 10:23:14 +02:00
* @returns The response of the WhoAmI request.
*/
2019-03-07 10:47:03 +01:00
public async whoAmI(): Promise<WhoAmI> {
2018-10-26 10:23:14 +02:00
try {
2019-03-04 11:45:15 +01:00
const response = await this.http.get(environment.urlPrefix + '/users/whoami/');
2019-03-07 10:47:03 +01:00
if (isWhoAmI(response)) {
await this.updateCurrentWhoAmI(response);
2019-03-04 11:45:15 +01:00
} else {
this.offlineService.goOfflineBecauseFailedWhoAmI();
2018-10-26 10:23:14 +02:00
}
} catch (e) {
this.offlineService.goOfflineBecauseFailedWhoAmI();
}
2019-03-07 10:47:03 +01:00
return this.currentWhoAmI;
2019-03-04 11:45:15 +01:00
}
/**
* Saves the user to storage by wrapping it into a (maybe existing)
* WhoAMI response.
*/
2019-03-07 10:47:03 +01:00
private async updateUserInCurrentWhoAmI(): Promise<void> {
if (!this.currentWhoAmI) {
this.currentWhoAmI = this.getDefaultWhoAmIResponse();
2019-03-04 11:45:15 +01:00
}
2019-03-07 10:47:03 +01:00
if (this.isAnonymous) {
this.currentWhoAmI.user_id = null;
this.currentWhoAmI.user = null;
2019-03-04 11:45:15 +01:00
} else {
2019-03-07 10:47:03 +01:00
this.currentWhoAmI.user_id = this.user.id;
this.currentWhoAmI.user = this.user;
2019-03-04 11:45:15 +01:00
}
2019-03-07 10:47:03 +01:00
this.currentWhoAmI.permissions = this.permissions;
await this.updateCurrentWhoAmI();
}
/**
2019-03-07 10:47:03 +01:00
* Updates the user and update the permissions.
2019-03-04 11:45:15 +01:00
*/
2019-03-07 10:47:03 +01:00
private async updateCurrentWhoAmI(whoami?: WhoAmI): Promise<void> {
if (whoami) {
this.currentWhoAmI = whoami;
} else {
whoami = this.currentWhoAmI;
}
this._user = whoami ? whoami.user : null;
if (this._user && this.userRepository) {
this._viewUser = this.userRepository.getViewModel(this._user.id);
} else {
this._viewUser = null;
}
await this.updatePermissions();
2019-03-04 11:45:15 +01:00
}
/**
* @returns an observable for the operator as a user.
*/
public getUserObservable(): Observable<User> {
return this.operatorSubject.asObservable();
}
2019-03-04 11:45:15 +01:00
/**
* @returns an observable for the operator as a viewUser. Note, that
* the viewUser might not be there, so for reliable (and not display) information,
* use the `getUserObservable`.
*/
public getViewUserObservable(): Observable<ViewUser> {
return this.viewOperatorSubject.asObservable();
}
/**
* Checks, if the operator has at least one of the given permissions.
2018-09-18 18:27:14 +02:00
* @param checkPerms The permissions to check, if at least one matches.
*/
2018-09-18 18:27:14 +02:00
public hasPerms(...checkPerms: Permission[]): boolean {
2018-10-09 13:44:38 +02:00
if (this._user && this._user.groups_id.includes(2)) {
return true;
}
2018-09-18 18:27:14 +02:00
return checkPerms.some(permission => {
return this.permissions.includes(permission);
});
}
/**
* Returns true, if the operator is in at least one group or he is in the admin group.
* @param groups The groups to check
*/
public isInGroup(...groups: Group[]): boolean {
return this.isInGroupIds(...groups.map(group => group.id));
}
/**
* Returns true, if the operator is in at least one group or he is in the admin group.
* @param groups The group ids to check
*/
public isInGroupIds(...groupIds: number[]): boolean {
if (!this.user) {
return groupIds.includes(1); // any anonymous is in the default group.
}
if (this.user.groups_id.includes(2)) {
// An admin has all perms and is technically in every group.
return true;
}
return groupIds.some(id => this.user.groups_id.includes(id));
}
/**
* Update the operators permissions and publish the operator afterwards.
2019-03-07 10:47:03 +01:00
* Saves the current WhoAmI to storage with the updated permissions
*/
2019-03-07 10:47:03 +01:00
private async updatePermissions(): Promise<void> {
this.permissions = [];
2019-03-07 10:47:03 +01:00
// If we do not have any groups, take the permissions from the
// latest WhoAmI response.
if (this.DS.getAll(Group).length === 0) {
if (this.currentWhoAmI) {
this.permissions = this.currentWhoAmI.permissions;
}
} else {
2019-03-07 10:47:03 +01:00
// Anonymous or users in the default group.
if (!this.user || this.user.groups_id.length === 0) {
const defaultGroup = this.DS.get<Group>('users/group', 1);
if (defaultGroup && defaultGroup.permissions instanceof Array) {
this.permissions = defaultGroup.permissions;
}
} else {
const permissionSet = new Set();
this.DS.getMany(Group, this.user.groups_id).forEach(group => {
group.permissions.forEach(permission => {
permissionSet.add(permission);
});
});
2019-03-07 10:47:03 +01:00
this.permissions = Array.from(permissionSet.values());
}
}
// Save perms to current WhoAmI
if (!this.currentWhoAmI) {
this.currentWhoAmI = this.getDefaultWhoAmIResponse();
}
2019-03-07 10:47:03 +01:00
this.currentWhoAmI.permissions = this.permissions;
if (!this.OSStatus.isInHistoryMode) {
await this.storageService.set(WHOAMI_STORAGE_KEY, this.currentWhoAmI);
}
2019-03-07 10:47:03 +01:00
// publish changes in the operator.
this.operatorSubject.next(this.user);
this.viewOperatorSubject.next(this.viewUser);
}
}