diff --git a/src/database/user_roles.rs b/src/database/user_roles.rs index e8916a7..07ad3ef 100644 --- a/src/database/user_roles.rs +++ b/src/database/user_roles.rs @@ -5,6 +5,8 @@ use crate::database::models::Role; use crate::database::{DatabaseResult, PostgresPool, Table}; use crate::utils::error::DBError; +use std::collections::HashSet; +use std::iter::FromIterator; /// A table that stores the relation between users and roles #[derive(Clone)] @@ -43,4 +45,37 @@ impl UserRoles { serde_postgres::from_rows(&rows).map_err(DBError::from) } + + pub fn update_roles(&self, user_id: i32, roles: Vec) -> DatabaseResult> { + let mut connection = self.pool.get()?; + let mut transaction = connection.transaction()?; + let role_ids_result = transaction.query( + "SELECT roles.id FROM roles WHERE roles.name = ANY ($1)", + &[&roles], + )?; + let role_ids: Vec = serde_postgres::from_rows(role_ids_result.iter())?; + let role_ids: HashSet = HashSet::from_iter(role_ids.into_iter()); + let role_result = transaction.query("SELECT roles.id FROM roles, user_roles WHERE roles.id = user_roles.role_id AND user_roles.user_id = $1", &[&user_id])?; + let current_roles: Vec = serde_postgres::from_rows(role_result.iter())?; + + let current_roles = HashSet::from_iter(current_roles.into_iter()); + let added_roles: HashSet<&i32> = role_ids.difference(¤t_roles).collect(); + let removed_roles: HashSet<&i32> = current_roles.difference(&role_ids).collect(); + + for role in removed_roles { + transaction.query( + "DELETE FROM user_roles WHERE role_id = $1 AND user_id = $2", + &[role, &user_id], + )?; + } + for role in added_roles { + transaction.query( + "INSERT INTO user_roles (user_id, role_id) VALUES ($1, $2)", + &[&user_id, role], + )?; + } + transaction.commit()?; + + Ok(self.by_user(user_id)?) + } } diff --git a/src/database/users.rs b/src/database/users.rs index 59e0548..eb3bd2d 100644 --- a/src/database/users.rs +++ b/src/database/users.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use parking_lot::Mutex; use zeroize::{Zeroize, Zeroizing}; -use crate::database::models::{UserInformation, UserRecord}; +use crate::database::models::{Permission, UserInformation, UserRecord}; use crate::database::tokens::{SessionTokens, TokenStore}; use crate::database::user_roles::UserRoles; use crate::database::{DatabaseResult, PostgresPool, Table}; @@ -242,6 +242,7 @@ impl Users { pub fn delete_tokens(&self, request_token: &String) -> DatabaseResult { let mut token_store = self.token_store.lock(); let tokens = token_store.get_by_request_token(request_token); + if let Some(tokens) = tokens { tokens.invalidate(); @@ -288,4 +289,22 @@ impl Users { Ok(pw_hash == *original_pw_hash.as_slice()) } + + pub fn get_permissions(&self, email: &String) -> DatabaseResult> { + let mut connection = self.pool.get()?; + let results = connection.query( + "\ + SELECT permissions.id, permissions.name, permissions.description + FROM permissions, role_permissions, user_roles, users + WHERE users.email = $1 + AND users.id = user_roles.user_id + AND role_permissions.role_id = user_roles.role_id + AND permissions.id = role_permissions.permission_id + ", + &[&email], + )?; + let permissions: Vec = serde_postgres::from_rows(results.iter())?; + + Ok(permissions) + } } diff --git a/src/server/http_server.rs b/src/server/http_server.rs index c64b0a8..a8850f3 100644 --- a/src/server/http_server.rs +++ b/src/server/http_server.rs @@ -11,7 +11,7 @@ use rouille::{Request, Response, Server}; use serde::export::Formatter; use serde::Serialize; -use crate::database::models::{Role, UserFullInformation, UserInformation}; +use crate::database::models::{Permission, Role, UserFullInformation, UserInformation}; use crate::database::permissions::{ ROLE_CREATE_PERM, ROLE_DELETE_PERM, ROLE_UPDATE_PERM, ROLE_VIEW_PERM, USER_CREATE_PERM, USER_DELETE_PERM, USER_UPDATE_PERM, USER_VIEW_PERM, @@ -133,6 +133,9 @@ impl UserHttpServer { (GET) (/users/{email: String}) => { Self::get_user(&database, request, email).unwrap_or_else(HTTPError::into) }, + (GET) (/users/{email: String}/permissions) => { + Self::get_user_permissions(&database, request, email).unwrap_or_else(HTTPError::into) + }, (GET) (/users) => { Self::get_users(&database, request).unwrap_or_else(HTTPError::into) }, @@ -239,6 +242,11 @@ impl UserHttpServer { "POST", "Deletes a user", )?; + doc.add_path::<(), Vec>( + "/users/{email:String}/permissions", + "GET", + "Returns a list of permissions the user was granted", + )?; Ok(doc) } @@ -375,7 +383,7 @@ impl UserHttpServer { /// Returns information for a single user fn get_user(database: &Database, request: &Request, email: String) -> HTTPResult { - require_permission!(database, request, USER_VIEW_PERM); + check_user_permission_or_self(request, database, &email, USER_VIEW_PERM)?; let user = database.users.get_user_by_email(&email)?; let roles = database.user_roles.by_user(user.id)?; @@ -410,9 +418,10 @@ impl UserHttpServer { /// Updates the information of a user. This requires the operating user to revalidate his password fn update_user(database: &Database, request: &Request, email: String) -> HTTPResult { - let (_, id) = validate_request_token(request, database)?; + let logged_in_user = + check_user_permission_or_self(request, database, &email, USER_UPDATE_PERM)?; let message = deserialize_body::(&request)?; - let logged_in_user = database.users.get_user(id)?; + if !database .users .validate_login(&logged_in_user.email, &message.own_password)? @@ -423,9 +432,6 @@ impl UserHttpServer { )); } - if logged_in_user.email != email { - require_permission!(database, request, USER_UPDATE_PERM); - } let user_record = database.users.get_user_by_email(&email)?; let record = database.users.update_user( &email, @@ -433,15 +439,26 @@ impl UserHttpServer { &message.email.clone().unwrap_or(user_record.email), &message.password, )?; + let roles = if let Some(roles) = &message.roles { + require_permission!(database, request, USER_UPDATE_PERM); + database.user_roles.update_roles(record.id, roles.clone())? + } else { + database.user_roles.by_user(record.id)? + }; - Ok(Response::json(&record)) + Ok(Response::json(&UserFullInformation { + id: record.id, + email: record.email, + name: record.name, + roles, + })) } /// Deletes a user completely fn delete_user(database: &Database, request: &Request, email: String) -> HTTPResult { - let (_, id) = validate_request_token(request, database)?; - let message = deserialize_body::(&request)?; - let logged_in_user = database.users.get_user(id)?; + let logged_in_user = + check_user_permission_or_self(request, database, &email, USER_DELETE_PERM)?; + let message = deserialize_body::(request)?; if !database .users @@ -452,9 +469,7 @@ impl UserHttpServer { 401, )); } - if !database.users.has_permission(id, USER_DELETE_PERM)? { - return Err(HTTPError::new("Insufficient permissions".to_string(), 403)); - } + database.users.delete_user(&email)?; Ok(Response::json(&DeleteUserResponse { @@ -462,6 +477,18 @@ impl UserHttpServer { email, })) } + + /// Returns a list of permissions the user has + fn get_user_permissions( + database: &Database, + request: &Request, + email: String, + ) -> HTTPResult { + check_user_permission_or_self(request, database, &email, USER_VIEW_PERM)?; + let permissions = database.users.get_permissions(&email)?; + + Ok(Response::json(&permissions)) + } } /// Parses the body of a http request into a string representation @@ -500,3 +527,20 @@ fn validate_request_token(request: &Request, database: &Database) -> HTTPResult< )) } } + +/// Returns if the user has a certain permission or queries him/herself +fn check_user_permission_or_self( + request: &Request, + database: &Database, + email: &String, + permission: &str, +) -> HTTPResult { + let (_, id) = validate_request_token(request, database)?; + let logged_in_user = database.users.get_user(id)?; + + if &logged_in_user.email != email && !database.users.has_permission(id, permission)? { + Err(HTTPError::new("Insufficient permission".to_string(), 403)) + } else { + Ok(logged_in_user) + } +} diff --git a/src/server/messages.rs b/src/server/messages.rs index 85bf895..c23d590 100644 --- a/src/server/messages.rs +++ b/src/server/messages.rs @@ -126,6 +126,7 @@ pub struct UpdateUserRequest { pub name: Option, pub email: Option, pub password: Option, + pub roles: Option>, pub own_password: String, }