Merge pull request #4471 from FinnStutzenstein/permissionsInWhoAmI

rework login system (again)
This commit is contained in:
Finn Stutzenstein 2019-03-11 14:03:46 +01:00 committed by GitHub
commit 3ac7788fe8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 224 additions and 169 deletions

View File

@ -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) {

View File

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

View File

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

View File

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

View File

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

View File

@ -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):
""" """

View File

@ -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):