Merge pull request #4471 from FinnStutzenstein/permissionsInWhoAmI
rework login system (again)
This commit is contained in:
commit
3ac7788fe8
@ -28,6 +28,10 @@ export class AuthGuard implements CanActivate, CanActivateChild {
|
|||||||
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
|
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
|
||||||
const basePerm: string | string[] = route.data.basePerm;
|
const basePerm: string | string[] = route.data.basePerm;
|
||||||
|
|
||||||
|
console.log('Auth guard');
|
||||||
|
console.log('motions.can_see:', this.operator.hasPerms('motions.can_see'));
|
||||||
|
console.log('motions.can_manage:', this.operator.hasPerms('motions.can_manage'));
|
||||||
|
|
||||||
if (!basePerm) {
|
if (!basePerm) {
|
||||||
return true;
|
return true;
|
||||||
} else if (basePerm instanceof Array) {
|
} else if (basePerm instanceof Array) {
|
||||||
|
@ -1,21 +1,12 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
import { OperatorService, WhoAmI } from 'app/core/core-services/operator.service';
|
||||||
import { environment } from 'environments/environment';
|
import { environment } from 'environments/environment';
|
||||||
import { User } from '../../shared/models/users/user';
|
|
||||||
import { OpenSlidesService } from './openslides.service';
|
import { OpenSlidesService } from './openslides.service';
|
||||||
import { HttpService } from './http.service';
|
import { HttpService } from './http.service';
|
||||||
import { DataStoreService } from './data-store.service';
|
import { DataStoreService } from './data-store.service';
|
||||||
|
|
||||||
/**
|
|
||||||
* The data returned by a post request to the login route.
|
|
||||||
*/
|
|
||||||
interface LoginResponse {
|
|
||||||
user_id: number;
|
|
||||||
user: User;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authenticates an OpenSlides user with username and password
|
* Authenticates an OpenSlides user with username and password
|
||||||
*/
|
*/
|
||||||
@ -49,31 +40,57 @@ export class AuthService {
|
|||||||
* @param password
|
* @param password
|
||||||
* @returns The login response.
|
* @returns The login response.
|
||||||
*/
|
*/
|
||||||
public async login(username: string, password: string): Promise<LoginResponse> {
|
public async login(username: string, password: string, earlySuccessCallback: () => void): Promise<WhoAmI> {
|
||||||
const user = {
|
const user = {
|
||||||
username: username,
|
username: username,
|
||||||
password: password
|
password: password
|
||||||
};
|
};
|
||||||
const response = await this.http.post<LoginResponse>(environment.urlPrefix + '/users/login/', user);
|
const response = await this.http.post<WhoAmI>(environment.urlPrefix + '/users/login/', user);
|
||||||
|
earlySuccessCallback();
|
||||||
|
await this.operator.setWhoAmI(response);
|
||||||
|
await this.redirectUser(response.user_id);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirects the user to the page where he came from. Boots OpenSlides,
|
||||||
|
* if it wasn't done before.
|
||||||
|
*/
|
||||||
|
public async redirectUser(userId: number): Promise<void> {
|
||||||
|
if (!this.OpenSlides.booted) {
|
||||||
|
await this.OpenSlides.afterLoginBootup(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
let redirect = this.OpenSlides.redirectUrl ? this.OpenSlides.redirectUrl : '/';
|
||||||
|
if (redirect.includes('login')) {
|
||||||
|
redirect = '/';
|
||||||
|
}
|
||||||
|
this.router.navigate([redirect]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login for guests.
|
||||||
|
*/
|
||||||
|
public async guestLogin(): Promise<void> {
|
||||||
|
this.redirectUser(null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logout function for both the client and the server.
|
* Logout function for both the client and the server.
|
||||||
*
|
*
|
||||||
* Will clear the current operator and datastore and
|
* Will clear the datastore, update the current operator and
|
||||||
* send a `post`-request to `/apps/users/logout/'`. Restarts OpenSlides.
|
* send a `post`-request to `/apps/users/logout/'`. Restarts OpenSlides.
|
||||||
*/
|
*/
|
||||||
public async logout(): Promise<void> {
|
public async logout(): Promise<void> {
|
||||||
await this.operator.setUser(null);
|
let response = null;
|
||||||
try {
|
try {
|
||||||
await this.http.post(environment.urlPrefix + '/users/logout/', {});
|
response = await this.http.post<WhoAmI>(environment.urlPrefix + '/users/logout/', {});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// We do nothing on failures. Reboot OpenSlides anyway.
|
// We do nothing on failures. Reboot OpenSlides anyway.
|
||||||
}
|
}
|
||||||
// Clear the DataStore
|
await this.DS.clear();
|
||||||
this.DS.clear();
|
await this.operator.setWhoAmI(response);
|
||||||
|
await this.OpenSlides.reboot();
|
||||||
this.router.navigate(['/']);
|
this.router.navigate(['/']);
|
||||||
this.OpenSlides.reboot();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { take } from 'rxjs/operators';
|
||||||
|
|
||||||
import { WebsocketService } from './websocket.service';
|
import { WebsocketService } from './websocket.service';
|
||||||
import { OperatorService, WhoAmIResponse } from './operator.service';
|
import { OperatorService } from './operator.service';
|
||||||
import { StorageService } from './storage.service';
|
import { StorageService } from './storage.service';
|
||||||
import { AutoupdateService } from './autoupdate.service';
|
import { AutoupdateService } from './autoupdate.service';
|
||||||
import { DataStoreService } from './data-store.service';
|
import { DataStoreService } from './data-store.service';
|
||||||
import { take } from 'rxjs/operators';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the bootup/showdown of this application.
|
* Handles the bootup/showdown of this application.
|
||||||
@ -16,10 +17,20 @@ import { take } from 'rxjs/operators';
|
|||||||
})
|
})
|
||||||
export class OpenSlidesService {
|
export class OpenSlidesService {
|
||||||
/**
|
/**
|
||||||
* if the user tries to access a certain URL without being authenticated, the URL will be stored here
|
* If the user tries to access a certain URL without being authenticated, the URL will be stored here
|
||||||
*/
|
*/
|
||||||
public redirectUrl: string;
|
public redirectUrl: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves, if OpenSlides is fully booted. This means, that a user must be logged in
|
||||||
|
* (Anonymous is also a user in this case). This is the case after `afterLoginBootup`.
|
||||||
|
*/
|
||||||
|
private _booted = false;
|
||||||
|
|
||||||
|
public get booted(): boolean {
|
||||||
|
return this._booted;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor to create the OpenSlidesService. Registers itself to the WebsocketService.
|
* Constructor to create the OpenSlidesService. Registers itself to the WebsocketService.
|
||||||
* @param storageService
|
* @param storageService
|
||||||
@ -53,10 +64,6 @@ export class OpenSlidesService {
|
|||||||
public async bootup(): Promise<void> {
|
public async bootup(): Promise<void> {
|
||||||
// start autoupdate if the user is logged in:
|
// start autoupdate if the user is logged in:
|
||||||
const response = await this.operator.whoAmIFromStorage();
|
const response = await this.operator.whoAmIFromStorage();
|
||||||
await this.bootupWithWhoAmI(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async bootupWithWhoAmI(response: WhoAmIResponse): Promise<void> {
|
|
||||||
if (!response.user && !response.guest_enabled) {
|
if (!response.user && !response.guest_enabled) {
|
||||||
this.redirectUrl = location.pathname;
|
this.redirectUrl = location.pathname;
|
||||||
|
|
||||||
@ -85,9 +92,10 @@ export class OpenSlidesService {
|
|||||||
* the login bootup-sequence: Check (and maybe clear) the cache und setup the DataStore
|
* the login bootup-sequence: Check (and maybe clear) the cache und setup the DataStore
|
||||||
* and websocket. This "login" also may be the "login" of an anonymous when he is using
|
* and websocket. This "login" also may be the "login" of an anonymous when he is using
|
||||||
* OpenSlides as a guest.
|
* OpenSlides as a guest.
|
||||||
* @param userId
|
* @param userId the id or null for guest
|
||||||
*/
|
*/
|
||||||
public async afterLoginBootup(userId: number): Promise<void> {
|
public async afterLoginBootup(userId: number | null): Promise<void> {
|
||||||
|
console.log('user id', userId);
|
||||||
// Check, which user was logged in last time
|
// Check, which user was logged in last time
|
||||||
const lastUserId = await this.storageService.get<number>('lastUserLoggedIn');
|
const lastUserId = await this.storageService.get<number>('lastUserLoggedIn');
|
||||||
// if the user changed, reset the cache and save the new user.
|
// if the user changed, reset the cache and save the new user.
|
||||||
@ -96,6 +104,8 @@ export class OpenSlidesService {
|
|||||||
await this.storageService.set('lastUserLoggedIn', userId);
|
await this.storageService.set('lastUserLoggedIn', userId);
|
||||||
}
|
}
|
||||||
await this.setupDataStoreAndWebSocket();
|
await this.setupDataStoreAndWebSocket();
|
||||||
|
// Now finally booted.
|
||||||
|
this._booted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -121,16 +131,16 @@ export class OpenSlidesService {
|
|||||||
/**
|
/**
|
||||||
* Shuts OpenSlides down. The websocket is closed and the operator is not set.
|
* Shuts OpenSlides down. The websocket is closed and the operator is not set.
|
||||||
*/
|
*/
|
||||||
public async shutdown(): Promise<void> {
|
public shutdown(): void {
|
||||||
this.websocketService.close();
|
this.websocketService.close();
|
||||||
await this.operator.setUser(null);
|
this._booted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shutdown and bootup.
|
* Shutdown and bootup.
|
||||||
*/
|
*/
|
||||||
public async reboot(): Promise<void> {
|
public async reboot(): Promise<void> {
|
||||||
await this.shutdown();
|
this.shutdown();
|
||||||
await this.bootup();
|
await this.bootup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ import { UserRepositoryService } from '../repositories/users/user-repository.ser
|
|||||||
import { CollectionStringMapperService } from './collectionStringMapper.service';
|
import { CollectionStringMapperService } from './collectionStringMapper.service';
|
||||||
import { StorageService } from './storage.service';
|
import { StorageService } from './storage.service';
|
||||||
import { HttpService } from './http.service';
|
import { HttpService } from './http.service';
|
||||||
|
import { filter, auditTime } from 'rxjs/operators';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Permissions on the client are just strings. This makes clear, that
|
* Permissions on the client are just strings. This makes clear, that
|
||||||
@ -23,18 +24,24 @@ export type Permission = string;
|
|||||||
/**
|
/**
|
||||||
* Response format of the WhoAmI request.
|
* Response format of the WhoAmI request.
|
||||||
*/
|
*/
|
||||||
export interface WhoAmIResponse {
|
export interface WhoAmI {
|
||||||
user_id: number;
|
user_id: number;
|
||||||
guest_enabled: boolean;
|
guest_enabled: boolean;
|
||||||
user: User;
|
user: User;
|
||||||
|
permissions: Permission[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function isWhoAmIResponse(obj: any): obj is WhoAmIResponse {
|
function isWhoAmI(obj: any): obj is WhoAmI {
|
||||||
if (!obj) {
|
if (!obj) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const whoAmI = obj as WhoAmIResponse;
|
const whoAmI = obj as WhoAmI;
|
||||||
return whoAmI.guest_enabled !== undefined && whoAmI.user !== undefined && whoAmI.user_id !== undefined;
|
return (
|
||||||
|
whoAmI.guest_enabled !== undefined &&
|
||||||
|
whoAmI.user !== undefined &&
|
||||||
|
whoAmI.user_id !== undefined &&
|
||||||
|
whoAmI.permissions !== undefined
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const WHOAMI_STORAGE_KEY = 'whoami';
|
const WHOAMI_STORAGE_KEY = 'whoami';
|
||||||
@ -80,7 +87,7 @@ export class OperatorService implements OnAfterAppsLoaded {
|
|||||||
* Save, if guests are enabled.
|
* Save, if guests are enabled.
|
||||||
*/
|
*/
|
||||||
public get guestsEnabled(): boolean {
|
public get guestsEnabled(): boolean {
|
||||||
return this.currentWhoAmIResponse ? this.currentWhoAmIResponse.guest_enabled : false;
|
return this.currentWhoAmI ? this.currentWhoAmI.guest_enabled : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -106,7 +113,7 @@ export class OperatorService implements OnAfterAppsLoaded {
|
|||||||
/**
|
/**
|
||||||
* The current WhoAmI response to extract the user (the operator) from.
|
* The current WhoAmI response to extract the user (the operator) from.
|
||||||
*/
|
*/
|
||||||
private currentWhoAmIResponse: WhoAmIResponse | null;
|
private currentWhoAmI: WhoAmI | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up an observer for watching changes in the DS. If the operator user or groups are changed,
|
* Sets up an observer for watching changes in the DS. If the operator user or groups are changed,
|
||||||
@ -124,43 +131,51 @@ export class OperatorService implements OnAfterAppsLoaded {
|
|||||||
private storageService: StorageService
|
private storageService: StorageService
|
||||||
) {
|
) {
|
||||||
this.DS.changeObservable.subscribe(newModel => {
|
this.DS.changeObservable.subscribe(newModel => {
|
||||||
if (this._user) {
|
if (this._user && newModel instanceof User && this._user.id === newModel.id) {
|
||||||
if (newModel instanceof Group) {
|
this._user = newModel;
|
||||||
this.updatePermissions();
|
this.updateUserInCurrentWhoAmI();
|
||||||
}
|
|
||||||
|
|
||||||
if (newModel instanceof User && this._user.id === newModel.id) {
|
|
||||||
this.updateUser(newModel);
|
|
||||||
}
|
|
||||||
} else if (newModel instanceof Group && newModel.id === 1) {
|
|
||||||
// Group 1 (default) for anonymous changed
|
|
||||||
this.updatePermissions();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
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());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the current WHoAmI response from the storage.
|
* Returns a default WhoAmI response
|
||||||
*/
|
*/
|
||||||
public async whoAmIFromStorage(): Promise<WhoAmIResponse> {
|
private getDefaultWhoAmIResponse(): WhoAmI {
|
||||||
const defaultResponse = {
|
return {
|
||||||
user_id: null,
|
user_id: null,
|
||||||
guest_enabled: false,
|
guest_enabled: false,
|
||||||
user: null
|
user: null,
|
||||||
|
permissions: []
|
||||||
};
|
};
|
||||||
let response: WhoAmIResponse;
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current WhoAmI response from the storage.
|
||||||
|
*/
|
||||||
|
public async whoAmIFromStorage(): Promise<WhoAmI> {
|
||||||
|
let response: WhoAmI;
|
||||||
try {
|
try {
|
||||||
response = await this.storageService.get<WhoAmIResponse>(WHOAMI_STORAGE_KEY);
|
response = await this.storageService.get<WhoAmI>(WHOAMI_STORAGE_KEY);
|
||||||
if (response) {
|
if (!response) {
|
||||||
this.processWhoAmIResponse(response);
|
response = this.getDefaultWhoAmIResponse();
|
||||||
} else {
|
|
||||||
response = defaultResponse;
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
response = defaultResponse;
|
response = this.getDefaultWhoAmIResponse();
|
||||||
}
|
}
|
||||||
this.currentWhoAmIResponse = response;
|
await this.updateCurrentWhoAmI(response);
|
||||||
return this.currentWhoAmIResponse;
|
return this.currentWhoAmI;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -177,27 +192,11 @@ export class OperatorService implements OnAfterAppsLoaded {
|
|||||||
* Sets the operator user. Will be saved to storage
|
* Sets the operator user. Will be saved to storage
|
||||||
* @param user The new operator.
|
* @param user The new operator.
|
||||||
*/
|
*/
|
||||||
public async setUser(user: User): Promise<void> {
|
public async setWhoAmI(whoami: WhoAmI | null): Promise<void> {
|
||||||
await this.updateUser(user, true);
|
if (whoami === null) {
|
||||||
}
|
whoami = this.getDefaultWhoAmIResponse();
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the user and update the permissions.
|
|
||||||
*
|
|
||||||
* @param user The user to set.
|
|
||||||
* @param saveToStoare Whether to save the user to the storage WhoAmI.
|
|
||||||
*/
|
|
||||||
private async updateUser(user: User | null, saveToStorage: boolean = false): Promise<void> {
|
|
||||||
this._user = user;
|
|
||||||
if (saveToStorage) {
|
|
||||||
await this.saveUserToStorate();
|
|
||||||
}
|
}
|
||||||
if (user && this.userRepository) {
|
await this.updateCurrentWhoAmI(whoami);
|
||||||
this._viewUser = this.userRepository.getViewModel(user.id);
|
|
||||||
} else {
|
|
||||||
this._viewUser = null;
|
|
||||||
}
|
|
||||||
this.updatePermissions();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -205,51 +204,56 @@ export class OperatorService implements OnAfterAppsLoaded {
|
|||||||
*
|
*
|
||||||
* @returns The response of the WhoAmI request.
|
* @returns The response of the WhoAmI request.
|
||||||
*/
|
*/
|
||||||
public async whoAmI(): Promise<WhoAmIResponse> {
|
public async whoAmI(): Promise<WhoAmI> {
|
||||||
try {
|
try {
|
||||||
const response = await this.http.get(environment.urlPrefix + '/users/whoami/');
|
const response = await this.http.get(environment.urlPrefix + '/users/whoami/');
|
||||||
if (isWhoAmIResponse(response)) {
|
if (isWhoAmI(response)) {
|
||||||
this.processWhoAmIResponse(response);
|
await this.updateCurrentWhoAmI(response);
|
||||||
await this.storageService.set(WHOAMI_STORAGE_KEY, response);
|
|
||||||
this.currentWhoAmIResponse = response;
|
|
||||||
} else {
|
} else {
|
||||||
this.offlineService.goOfflineBecauseFailedWhoAmI();
|
this.offlineService.goOfflineBecauseFailedWhoAmI();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.offlineService.goOfflineBecauseFailedWhoAmI();
|
this.offlineService.goOfflineBecauseFailedWhoAmI();
|
||||||
}
|
}
|
||||||
return this.currentWhoAmIResponse;
|
return this.currentWhoAmI;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves the user to storage by wrapping it into a (maybe existing)
|
* Saves the user to storage by wrapping it into a (maybe existing)
|
||||||
* WhoAMI response.
|
* WhoAMI response.
|
||||||
*/
|
*/
|
||||||
private async saveUserToStorate(): Promise<void> {
|
private async updateUserInCurrentWhoAmI(): Promise<void> {
|
||||||
if (!this.currentWhoAmIResponse) {
|
if (!this.currentWhoAmI) {
|
||||||
this.currentWhoAmIResponse = {
|
this.currentWhoAmI = this.getDefaultWhoAmIResponse();
|
||||||
user_id: null,
|
|
||||||
guest_enabled: false,
|
|
||||||
user: null
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
if (this.user) {
|
if (this.isAnonymous) {
|
||||||
this.currentWhoAmIResponse.user_id = this.user.id;
|
this.currentWhoAmI.user_id = null;
|
||||||
this.currentWhoAmIResponse.user = this.user;
|
this.currentWhoAmI.user = null;
|
||||||
} else {
|
} else {
|
||||||
this.currentWhoAmIResponse.user_id = null;
|
this.currentWhoAmI.user_id = this.user.id;
|
||||||
this.currentWhoAmIResponse.user = null;
|
this.currentWhoAmI.user = this.user;
|
||||||
}
|
}
|
||||||
await this.storageService.set(WHOAMI_STORAGE_KEY, this.currentWhoAmIResponse);
|
this.currentWhoAmI.permissions = this.permissions;
|
||||||
|
await this.updateCurrentWhoAmI();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes a WhoAmI response and set the user appropriately.
|
* Updates the user and update the permissions.
|
||||||
*
|
|
||||||
* @param response The WhoAMI response
|
|
||||||
*/
|
*/
|
||||||
private processWhoAmIResponse(response: WhoAmIResponse): void {
|
private async updateCurrentWhoAmI(whoami?: WhoAmI): Promise<void> {
|
||||||
this.updateUser(response.user ? new User(response.user) : null);
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -306,24 +310,42 @@ export class OperatorService implements OnAfterAppsLoaded {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the operators permissions and publish the operator afterwards.
|
* Update the operators permissions and publish the operator afterwards.
|
||||||
|
* Saves the current WhoAmI to storage with the updated permissions
|
||||||
*/
|
*/
|
||||||
private updatePermissions(): void {
|
private async updatePermissions(): Promise<void> {
|
||||||
this.permissions = [];
|
this.permissions = [];
|
||||||
// Anonymous or users in the default group.
|
|
||||||
if (!this.user || this.user.groups_id.length === 0) {
|
// If we do not have any groups, take the permissions from the
|
||||||
const defaultGroup = this.DS.get<Group>('users/group', 1);
|
// latest WhoAmI response.
|
||||||
if (defaultGroup && defaultGroup.permissions instanceof Array) {
|
if (this.DS.getAll(Group).length === 0) {
|
||||||
this.permissions = defaultGroup.permissions;
|
if (this.currentWhoAmI) {
|
||||||
|
this.permissions = this.currentWhoAmI.permissions;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const permissionSet = new Set();
|
// Anonymous or users in the default group.
|
||||||
this.DS.getMany(Group, this.user.groups_id).forEach(group => {
|
if (!this.user || this.user.groups_id.length === 0) {
|
||||||
group.permissions.forEach(permission => {
|
const defaultGroup = this.DS.get<Group>('users/group', 1);
|
||||||
permissionSet.add(permission);
|
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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
this.permissions = Array.from(permissionSet.values());
|
||||||
this.permissions = Array.from(permissionSet.values());
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save perms to current WhoAmI
|
||||||
|
if (!this.currentWhoAmI) {
|
||||||
|
this.currentWhoAmI = this.getDefaultWhoAmIResponse();
|
||||||
|
}
|
||||||
|
this.currentWhoAmI.permissions = this.permissions;
|
||||||
|
await this.storageService.set(WHOAMI_STORAGE_KEY, this.currentWhoAmI);
|
||||||
|
|
||||||
// publish changes in the operator.
|
// publish changes in the operator.
|
||||||
this.operatorSubject.next(this.user);
|
this.operatorSubject.next(this.user);
|
||||||
this.viewOperatorSubject.next(this.viewUser);
|
this.viewOperatorSubject.next(this.viewUser);
|
||||||
|
@ -9,11 +9,9 @@ import { BaseComponent } from 'app/base.component';
|
|||||||
import { AuthService } from 'app/core/core-services/auth.service';
|
import { AuthService } from 'app/core/core-services/auth.service';
|
||||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||||
import { environment } from 'environments/environment';
|
import { environment } from 'environments/environment';
|
||||||
import { OpenSlidesService } from 'app/core/core-services/openslides.service';
|
|
||||||
import { LoginDataService, LoginData } from 'app/core/ui-services/login-data.service';
|
import { LoginDataService, LoginData } from 'app/core/ui-services/login-data.service';
|
||||||
import { ParentErrorStateMatcher } from 'app/shared/parent-error-state-matcher';
|
import { ParentErrorStateMatcher } from 'app/shared/parent-error-state-matcher';
|
||||||
import { HttpService } from 'app/core/core-services/http.service';
|
import { HttpService } from 'app/core/core-services/http.service';
|
||||||
import { User } from 'app/shared/models/users/user';
|
|
||||||
|
|
||||||
interface LoginDataWithInfoText extends LoginData {
|
interface LoginDataWithInfoText extends LoginData {
|
||||||
info_text?: string;
|
info_text?: string;
|
||||||
@ -81,7 +79,6 @@ export class LoginMaskComponent extends BaseComponent implements OnInit, OnDestr
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private httpService: HttpService,
|
private httpService: HttpService,
|
||||||
private OpenSlides: OpenSlidesService,
|
|
||||||
private loginDataService: LoginDataService
|
private loginDataService: LoginDataService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
@ -112,8 +109,7 @@ export class LoginMaskComponent extends BaseComponent implements OnInit, OnDestr
|
|||||||
this.operatorSubscription = this.operator.getUserObservable().subscribe(user => {
|
this.operatorSubscription = this.operator.getUserObservable().subscribe(user => {
|
||||||
if (user) {
|
if (user) {
|
||||||
this.clearOperatorSubscription();
|
this.clearOperatorSubscription();
|
||||||
this.redirectUser();
|
this.authService.redirectUser(user.id);
|
||||||
this.OpenSlides.afterLoginBootup(user.id);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -154,30 +150,16 @@ export class LoginMaskComponent extends BaseComponent implements OnInit, OnDestr
|
|||||||
this.loginErrorMsg = '';
|
this.loginErrorMsg = '';
|
||||||
this.inProcess = true;
|
this.inProcess = true;
|
||||||
try {
|
try {
|
||||||
const response = await this.authService.login(this.loginForm.value.username, this.loginForm.value.password);
|
await this.authService.login(this.loginForm.value.username, this.loginForm.value.password, () => {
|
||||||
this.clearOperatorSubscription(); // We take control, not the subscription.
|
this.clearOperatorSubscription(); // We take control, not the subscription.
|
||||||
this.inProcess = false;
|
});
|
||||||
await this.operator.setUser(new User(response.user));
|
|
||||||
this.OpenSlides.afterLoginBootup(response.user_id);
|
|
||||||
this.redirectUser();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.loginForm.setErrors({
|
this.loginForm.setErrors({
|
||||||
notFound: true
|
notFound: true
|
||||||
});
|
});
|
||||||
this.loginErrorMsg = e;
|
this.loginErrorMsg = e;
|
||||||
this.inProcess = false;
|
|
||||||
}
|
}
|
||||||
}
|
this.inProcess = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* Redirects the user to the page where he came from.
|
|
||||||
*/
|
|
||||||
private redirectUser(): void {
|
|
||||||
let redirect = this.OpenSlides.redirectUrl ? this.OpenSlides.redirectUrl : '/';
|
|
||||||
if (redirect.includes('login')) {
|
|
||||||
redirect = '/';
|
|
||||||
}
|
|
||||||
this.router.navigate([redirect]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -198,6 +180,6 @@ export class LoginMaskComponent extends BaseComponent implements OnInit, OnDestr
|
|||||||
* Guests (if enabled) can navigate directly to the main page.
|
* Guests (if enabled) can navigate directly to the main page.
|
||||||
*/
|
*/
|
||||||
public guestLogin(): void {
|
public guestLogin(): void {
|
||||||
this.router.navigate(['/']);
|
this.authService.guestLogin();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import smtplib
|
import smtplib
|
||||||
import textwrap
|
import textwrap
|
||||||
from typing import List
|
from typing import List, Set
|
||||||
|
|
||||||
from asgiref.sync import async_to_sync
|
from asgiref.sync import async_to_sync
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -471,7 +471,46 @@ class PersonalNoteViewSet(ModelViewSet):
|
|||||||
# Special API views
|
# Special API views
|
||||||
|
|
||||||
|
|
||||||
class UserLoginView(APIView):
|
class WhoAmIDataView(APIView):
|
||||||
|
def get_whoami_data(self):
|
||||||
|
"""
|
||||||
|
Appends the user id to the context. Uses None for the anonymous
|
||||||
|
user. Appends also a flag if guest users are enabled in the config.
|
||||||
|
Appends also the serialized user if available.
|
||||||
|
"""
|
||||||
|
user_id = self.request.user.pk or 0
|
||||||
|
guest_enabled = anonymous_is_enabled()
|
||||||
|
|
||||||
|
if user_id:
|
||||||
|
user_data = async_to_sync(element_cache.get_element_restricted_data)(
|
||||||
|
user_id, self.request.user.get_collection_string(), user_id
|
||||||
|
)
|
||||||
|
group_ids = user_data["groups_id"] or [GROUP_DEFAULT_PK]
|
||||||
|
else:
|
||||||
|
user_data = None
|
||||||
|
group_ids = [GROUP_DEFAULT_PK] if guest_enabled else []
|
||||||
|
|
||||||
|
# collect all permissions
|
||||||
|
permissions: Set[str] = set()
|
||||||
|
group_all_data = async_to_sync(element_cache.get_all_full_data_ordered)()[
|
||||||
|
"users/group"
|
||||||
|
]
|
||||||
|
for group_id in group_ids:
|
||||||
|
permissions.update(group_all_data[group_id]["permissions"])
|
||||||
|
|
||||||
|
return {
|
||||||
|
"user_id": user_id or None,
|
||||||
|
"guest_enabled": guest_enabled,
|
||||||
|
"user": user_data,
|
||||||
|
"permissions": list(permissions),
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_context_data(self, **context):
|
||||||
|
context.update(self.get_whoami_data())
|
||||||
|
return super().get_context_data(**context)
|
||||||
|
|
||||||
|
|
||||||
|
class UserLoginView(WhoAmIDataView):
|
||||||
"""
|
"""
|
||||||
Login the user.
|
Login the user.
|
||||||
"""
|
"""
|
||||||
@ -525,14 +564,11 @@ class UserLoginView(APIView):
|
|||||||
context["theme"] = config["openslides_theme"]
|
context["theme"] = config["openslides_theme"]
|
||||||
else:
|
else:
|
||||||
# self.request.method == 'POST'
|
# self.request.method == 'POST'
|
||||||
context["user_id"] = self.user.pk
|
context.update(self.get_whoami_data())
|
||||||
context["user"] = async_to_sync(element_cache.get_element_restricted_data)(
|
|
||||||
self.user.pk or 0, self.user.get_collection_string(), self.user.pk
|
|
||||||
)
|
|
||||||
return super().get_context_data(**context)
|
return super().get_context_data(**context)
|
||||||
|
|
||||||
|
|
||||||
class UserLogoutView(APIView):
|
class UserLogoutView(WhoAmIDataView):
|
||||||
"""
|
"""
|
||||||
Logout the user.
|
Logout the user.
|
||||||
"""
|
"""
|
||||||
@ -546,33 +582,13 @@ class UserLogoutView(APIView):
|
|||||||
return super().post(*args, **kwargs)
|
return super().post(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class WhoAmIView(APIView):
|
class WhoAmIView(WhoAmIDataView):
|
||||||
"""
|
"""
|
||||||
Returns the id of the requesting user.
|
Returns the id of the requesting user.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
http_method_names = ["get"]
|
http_method_names = ["get"]
|
||||||
|
|
||||||
def get_context_data(self, **context):
|
|
||||||
"""
|
|
||||||
Appends the user id to the context. Uses None for the anonymous
|
|
||||||
user. Appends also a flag if guest users are enabled in the config.
|
|
||||||
Appends also the serialized user if available.
|
|
||||||
"""
|
|
||||||
user_id = self.request.user.pk or 0
|
|
||||||
if user_id:
|
|
||||||
user_data = async_to_sync(element_cache.get_element_restricted_data)(
|
|
||||||
user_id, self.request.user.get_collection_string(), user_id
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
user_data = None
|
|
||||||
return super().get_context_data(
|
|
||||||
user_id=user_id or None,
|
|
||||||
guest_enabled=anonymous_is_enabled(),
|
|
||||||
user=user_data,
|
|
||||||
**context,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SetPasswordView(APIView):
|
class SetPasswordView(APIView):
|
||||||
"""
|
"""
|
||||||
|
@ -15,7 +15,7 @@ class TestWhoAmIView(TestCase):
|
|||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
json.loads(response.content.decode()),
|
json.loads(response.content.decode()),
|
||||||
{"user_id": None, "user": None, "guest_enabled": False},
|
{"user_id": None, "user": None, "permissions": [], "guest_enabled": False},
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_authenticated_user(self):
|
def test_get_authenticated_user(self):
|
||||||
@ -56,6 +56,10 @@ class TestUserLogoutView(TestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertFalse(hasattr(self.client.session, "test_key"))
|
self.assertFalse(hasattr(self.client.session, "test_key"))
|
||||||
|
self.assertEqual(
|
||||||
|
json.loads(response.content.decode()),
|
||||||
|
{"user_id": None, "user": None, "permissions": [], "guest_enabled": False},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestUserLoginView(TestCase):
|
class TestUserLoginView(TestCase):
|
||||||
|
Loading…
Reference in New Issue
Block a user