From 81ae1a5c3ee3440a570e70745195f9c6a4e0cac8 Mon Sep 17 00:00:00 2001 From: trivernis Date: Fri, 13 Nov 2020 17:07:45 +0100 Subject: [PATCH 1/6] Add update user method Signed-off-by: trivernis --- src/database/mod.rs | 4 +-- src/database/models.rs | 10 +++++++- src/database/permissions.rs | 25 +++++++++++------- src/database/users.rs | 51 ++++++++++++++++++++++++++++++++++++- src/server/http_server.rs | 51 +++++++++++++++++++++++++++++-------- src/server/messages.rs | 7 +++++ 6 files changed, 125 insertions(+), 23 deletions(-) diff --git a/src/database/mod.rs b/src/database/mod.rs index f55dbba..385f97b 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -8,7 +8,7 @@ use r2d2::Pool; use r2d2_postgres::PostgresConnectionManager; use crate::database::models::CreatePermissionsEntry; -use crate::database::permissions::{Permissions, DEFAULT_PERMISSIONS}; +use crate::database::permissions::{Permissions, USER_MANAGEMENT_PERMISSIONS}; use crate::database::role_permissions::RolePermissions; use crate::database::roles::Roles; use crate::database::user_roles::UserRoles; @@ -94,7 +94,7 @@ impl Database { log::debug!("Failed to create admin role {}", e.to_string()) } self.permissions.create_permissions( - DEFAULT_PERMISSIONS + USER_MANAGEMENT_PERMISSIONS .iter() .map(|(name, description)| CreatePermissionsEntry { name: name.to_string(), diff --git a/src/database/models.rs b/src/database/models.rs index 6a0b57c..8e3d400 100644 --- a/src/database/models.rs +++ b/src/database/models.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use zeroize::Zeroize; /// Record to store data in when retrieving rows from the users table -#[derive(Clone, Debug, Zeroize)] +#[derive(Clone, Debug, Zeroize, Serialize)] #[zeroize(drop)] pub struct UserRecord { pub id: i32, @@ -55,3 +55,11 @@ pub struct CreatePermissionsEntry { pub name: String, pub description: String, } + +/// Information about the user that doesn't contain any critical information +#[derive(Serialize, Deserialize, JsonSchema)] +pub struct UserInformation { + pub id: i32, + pub name: String, + pub email: String, +} diff --git a/src/database/permissions.rs b/src/database/permissions.rs index 078026d..2ea5be6 100644 --- a/src/database/permissions.rs +++ b/src/database/permissions.rs @@ -7,15 +7,22 @@ use crate::database::{DatabaseResult, PostgresPool, Table, ADMIN_ROLE_NAME}; use std::collections::HashSet; use std::iter::FromIterator; -pub(crate) const VIEW_ROLE_PERMISSION: &str = "ROLE_VIEW"; -pub(crate) const CREATE_ROLE_PERMISSION: &str = "ROLE_CREATE"; -pub(crate) const UPDATE_ROLE_PERMISSION: &str = "ROLE_UPDATE"; -pub(crate) const DELETE_ROLE_PERMISSION: &str = "ROLE_DELETE"; -pub(crate) const DEFAULT_PERMISSIONS: &[(&'static str, &'static str)] = &[ - (CREATE_ROLE_PERMISSION, "Allows the user to create roles"), - (UPDATE_ROLE_PERMISSION, "Allows the user to update roles"), - (DELETE_ROLE_PERMISSION, "Allows the user to delete roles"), - (VIEW_ROLE_PERMISSION, "Allows to see information for roles"), +pub(crate) const ROLE_VIEW_PERM: &str = "ROLE_VIEW"; +pub(crate) const ROLE_CREATE_PERM: &str = "ROLE_CREATE"; +pub(crate) const ROLE_UPDATE_PERM: &str = "ROLE_UPDATE"; +pub(crate) const ROLE_DELETE_PERM: &str = "ROLE_DELETE"; + +pub(crate) const USER_UPDATE_PERM: &str = "USER_UPDATE"; + +pub(crate) const USER_MANAGEMENT_PERMISSIONS: &[(&'static str, &'static str)] = &[ + (ROLE_CREATE_PERM, "Allows the user to create roles"), + (ROLE_UPDATE_PERM, "Allows the user to update roles"), + (ROLE_DELETE_PERM, "Allows the user to delete roles"), + (ROLE_VIEW_PERM, "Allows to see information for roles"), + ( + USER_UPDATE_PERM, + "Allows changing the name, password and email of a user", + ), ]; /// The permissions table that stores defined diff --git a/src/database/users.rs b/src/database/users.rs index 0c67622..054e435 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::UserRecord; +use crate::database::models::{UserInformation, UserRecord}; use crate::database::tokens::{SessionTokens, TokenStore}; use crate::database::user_roles::UserRoles; use crate::database::{DatabaseResult, PostgresPool, Table}; @@ -78,6 +78,55 @@ impl Users { Ok(UserRecord::from_ordered_row(&row)) } + pub fn update_user( + &self, + old_email: String, + name: String, + email: String, + password: String, + ) -> DatabaseResult { + let mut connection = self.pool.get()?; + let mut password = Zeroizing::new(password); + if connection + .query_opt("SELECT email FROM users WHERE email = $1", &[&old_email])? + .is_none() + { + log::trace!("Failed to create user: Record doesn't exist!"); + return Err(DBError::RecordDoesNotExist); + } + if old_email != email + && connection + .query_opt("SELECT email FROM users WHERE email = $1", &[&old_email])? + .is_some() + { + log::trace!("Failed to create user: New Record exists!"); + return Err(DBError::GenericError(format!( + "A user for the email {} already exists!", + email + ))); + } + let salt = Zeroizing::new(create_salt()); + let pw_hash = + hash_password(password.as_bytes(), &*salt).map_err(|e| DBError::GenericError(e))?; + password.zeroize(); + let new_record = connection.query_one( + "UPDATE users SET name = $1, email = $2, password_hash = $3, salt = $4 WHERE email = $5 RETURNING *", + &[&name, &email, &pw_hash.to_vec(), &salt.to_vec(), &old_email], + )?; + + Ok(serde_postgres::from_row::(&new_record)?) + } + + /// Returns information about a user by Id + pub fn get_user(&self, id: i32) -> DatabaseResult { + let mut connection = self.pool.get()?; + let result = connection + .query_opt("SELECT id, name, email FROM users WHERE id = $1", &[&id])? + .ok_or(DBError::RecordDoesNotExist)?; + + Ok(serde_postgres::from_row::(&result)?) + } + /// Creates new tokens for a user login that can be used by services /// that need those tokens to verify a user login pub fn create_tokens( diff --git a/src/server/http_server.rs b/src/server/http_server.rs index 94f0357..189d51d 100644 --- a/src/server/http_server.rs +++ b/src/server/http_server.rs @@ -11,19 +11,20 @@ use rouille::{Request, Response, Server}; use serde::export::Formatter; use serde::Serialize; -use crate::database::models::Role; +use crate::database::models::{Role, UserInformation}; use crate::database::permissions::{ - CREATE_ROLE_PERMISSION, DELETE_ROLE_PERMISSION, UPDATE_ROLE_PERMISSION, VIEW_ROLE_PERMISSION, + ROLE_CREATE_PERM, ROLE_DELETE_PERM, ROLE_UPDATE_PERM, ROLE_VIEW_PERM, USER_UPDATE_PERM, }; use crate::database::tokens::SessionTokens; use crate::database::Database; use crate::server::documentation::RESTDocumentation; use crate::server::messages::{ DeleteRoleResponse, ErrorMessage, FullRoleData, LoginMessage, LogoutConfirmation, - LogoutMessage, ModifyRoleRequest, RefreshMessage, + LogoutMessage, ModifyRoleRequest, RefreshMessage, UpdateUserRequest, }; use crate::utils::error::DBError; use crate::utils::get_user_id_from_token; +use serde::de::DeserializeOwned; macro_rules! require_permission { ($database:expr,$request:expr,$permission:expr) => { @@ -127,6 +128,9 @@ impl UserHttpServer { (POST) (/roles/{name: String}/delete) => { Self::delete_role(&database, request, name).unwrap_or_else(HTTPError::into) }, + (POST) (/users/{email: String}/update) => { + Self::update_user(&database, request, email).unwrap_or_else(HTTPError::into) + }, _ => if request.method() == "OPTIONS" { Response::empty_204() } else { @@ -196,6 +200,11 @@ impl UserHttpServer { "POST", "Deletes a role", )?; + doc.add_path::( + "/users/{email:String}/update", + "POST", + "Change user information", + )?; Ok(doc) } @@ -241,7 +250,7 @@ impl UserHttpServer { /// Returns the data for a given role fn get_role(database: &Database, request: &Request, name: String) -> HTTPResult { - require_permission!(database, request, VIEW_ROLE_PERMISSION); + require_permission!(database, request, ROLE_VIEW_PERM); let role = database.roles.get_role(name)?; let permissions = database.role_permission.by_role(role.id)?; @@ -254,14 +263,14 @@ impl UserHttpServer { /// Returns a list of all roles fn get_roles(database: &Database, request: &Request) -> HTTPResult { - require_permission!(database, request, VIEW_ROLE_PERMISSION); + require_permission!(database, request, ROLE_VIEW_PERM); let roles = database.roles.get_roles()?; Ok(Response::json(&roles)) } fn create_role(database: &Database, request: &Request) -> HTTPResult { - require_permission!(database, request, CREATE_ROLE_PERMISSION); + require_permission!(database, request, ROLE_CREATE_PERM); let message: ModifyRoleRequest = serde_json::from_str(parse_string_body(request)?.as_str()) .map_err(|e| HTTPError::new(e.to_string(), 400))?; let not_existing = database @@ -289,9 +298,9 @@ impl UserHttpServer { } fn update_role(database: &Database, request: &Request, name: String) -> HTTPResult { - require_permission!(database, request, UPDATE_ROLE_PERMISSION); - let message: ModifyRoleRequest = serde_json::from_str(parse_string_body(request)?.as_str()) - .map_err(|e| HTTPError::new(e.to_string(), 400))?; + require_permission!(database, request, ROLE_UPDATE_PERM); + let message: ModifyRoleRequest = deserialize_body(&request)?; + let not_existing = database .permissions .get_not_existing(&message.permissions)?; @@ -318,7 +327,7 @@ impl UserHttpServer { } fn delete_role(database: &Database, request: &Request, role: String) -> HTTPResult { - require_permission!(database, request, DELETE_ROLE_PERMISSION); + require_permission!(database, request, ROLE_DELETE_PERM); database.roles.delete_role(&role)?; Ok(Response::json(&DeleteRoleResponse { @@ -326,6 +335,22 @@ impl UserHttpServer { role, })) } + + fn update_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)?; + + if logged_in_user.email != message.email { + require_permission!(database, request, USER_UPDATE_PERM); + } + let record = + database + .users + .update_user(email, message.name, message.email, message.password)?; + + Ok(Response::json(&record)) + } } /// Parses the body of a http request into a string representation @@ -340,6 +365,12 @@ fn parse_string_body(request: &Request) -> HTTPResult { Ok(string_body) } +/// Deserialized a json body into the given type +fn deserialize_body(request: &Request) -> HTTPResult { + serde_json::from_str(parse_string_body(request)?.as_str()) + .map_err(|e| HTTPError::new(e.to_string(), 400)) +} + /// Parses and validates the request token from the http header fn validate_request_token(request: &Request, database: &Database) -> HTTPResult<(String, i32)> { lazy_static::lazy_static! {static ref BEARER_REGEX: Regex = Regex::new(r"^[bB]earer\s+").unwrap();} diff --git a/src/server/messages.rs b/src/server/messages.rs index 73e3fdc..0a1ce86 100644 --- a/src/server/messages.rs +++ b/src/server/messages.rs @@ -119,3 +119,10 @@ pub struct DeleteRoleResponse { pub success: bool, pub role: String, } + +#[derive(Deserialize, JsonSchema)] +pub struct UpdateUserRequest { + pub name: String, + pub email: String, + pub password: String, +} From 0fae280c180db3eb294f453b9f8ed9cab6584dfd Mon Sep 17 00:00:00 2001 From: trivernis Date: Fri, 13 Nov 2020 17:30:58 +0100 Subject: [PATCH 2/6] Change require user input The input now requires the password of the person changing the record. All other fields are now optiona. Signed-off-by: trivernis --- src/database/users.rs | 45 +++++++++++++++++++++++++++------------ src/server/http_server.rs | 22 ++++++++++++++----- src/server/messages.rs | 10 +++++---- 3 files changed, 54 insertions(+), 23 deletions(-) diff --git a/src/database/users.rs b/src/database/users.rs index 054e435..a773fc1 100644 --- a/src/database/users.rs +++ b/src/database/users.rs @@ -80,13 +80,12 @@ impl Users { pub fn update_user( &self, - old_email: String, - name: String, - email: String, - password: String, + old_email: &String, + name: &String, + email: &String, + password: &Option, ) -> DatabaseResult { let mut connection = self.pool.get()?; - let mut password = Zeroizing::new(password); if connection .query_opt("SELECT email FROM users WHERE email = $1", &[&old_email])? .is_none() @@ -105,14 +104,20 @@ impl Users { email ))); } - let salt = Zeroizing::new(create_salt()); - let pw_hash = - hash_password(password.as_bytes(), &*salt).map_err(|e| DBError::GenericError(e))?; - password.zeroize(); - let new_record = connection.query_one( - "UPDATE users SET name = $1, email = $2, password_hash = $3, salt = $4 WHERE email = $5 RETURNING *", - &[&name, &email, &pw_hash.to_vec(), &salt.to_vec(), &old_email], - )?; + let new_record = if let Some(password) = password { + let salt = Zeroizing::new(create_salt()); + let pw_hash = + hash_password(password.as_bytes(), &*salt).map_err(|e| DBError::GenericError(e))?; + connection.query_one( + "UPDATE users SET name = $1, email = $2, password_hash = $3, salt = $4 WHERE email = $5 RETURNING *", + &[&name, &email, &pw_hash.to_vec(), &salt.to_vec(), &old_email], + )? + } else { + connection.query_one( + "UPDATE users SET name = $1, email = $2 WHERE email = $3 RETURNING *", + &[&name, &email, &old_email], + )? + }; Ok(serde_postgres::from_row::(&new_record)?) } @@ -127,6 +132,18 @@ impl Users { Ok(serde_postgres::from_row::(&result)?) } + pub fn get_user_by_email(&self, email: &String) -> DatabaseResult { + let mut connection = self.pool.get()?; + let result = connection + .query_opt( + "SELECT id, name, email FROM users WHERE email = $1", + &[email], + )? + .ok_or(DBError::RecordDoesNotExist)?; + + Ok(serde_postgres::from_row::(&result)?) + } + /// Creates new tokens for a user login that can be used by services /// that need those tokens to verify a user login pub fn create_tokens( @@ -219,7 +236,7 @@ impl Users { /// Validates the login data of the user by creating the hash for the given password /// and comparing it with the database entry - fn validate_login(&self, email: &String, password: &String) -> DatabaseResult { + pub fn validate_login(&self, email: &String, password: &String) -> DatabaseResult { let mut connection = self.pool.get()?; let row = connection .query_opt( diff --git a/src/server/http_server.rs b/src/server/http_server.rs index 189d51d..3846501 100644 --- a/src/server/http_server.rs +++ b/src/server/http_server.rs @@ -340,14 +340,26 @@ impl UserHttpServer { let (_, id) = validate_request_token(request, database)?; 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)? + { + return Err(HTTPError::new( + "Invalid authentication data".to_string(), + 401, + )); + } - if logged_in_user.email != message.email { + if logged_in_user.email != email { require_permission!(database, request, USER_UPDATE_PERM); } - let record = - database - .users - .update_user(email, message.name, message.email, message.password)?; + let user_record = database.users.get_user_by_email(&email)?; + let record = database.users.update_user( + &email, + &message.name.clone().unwrap_or(user_record.name), + &message.email.clone().unwrap_or(user_record.email), + &message.password, + )?; Ok(Response::json(&record)) } diff --git a/src/server/messages.rs b/src/server/messages.rs index 0a1ce86..99f1ba3 100644 --- a/src/server/messages.rs +++ b/src/server/messages.rs @@ -120,9 +120,11 @@ pub struct DeleteRoleResponse { pub role: String, } -#[derive(Deserialize, JsonSchema)] +#[derive(Deserialize, JsonSchema, Zeroize)] +#[zeroize(drop)] pub struct UpdateUserRequest { - pub name: String, - pub email: String, - pub password: String, + pub name: Option, + pub email: Option, + pub password: Option, + pub own_password: String, } From 7f274b82ab2ac7a1bbee0fb8302169574ebb5a3c Mon Sep 17 00:00:00 2001 From: trivernis Date: Fri, 13 Nov 2020 17:48:24 +0100 Subject: [PATCH 3/6] Add method to get information for a single user Signed-off-by: trivernis --- src/database/permissions.rs | 4 +++- src/database/users.rs | 9 +++++++++ src/server/documentation/mod.rs | 4 +++- src/server/http_server.rs | 16 ++++++++++++++++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/database/permissions.rs b/src/database/permissions.rs index 2ea5be6..c36c22f 100644 --- a/src/database/permissions.rs +++ b/src/database/permissions.rs @@ -13,16 +13,18 @@ pub(crate) const ROLE_UPDATE_PERM: &str = "ROLE_UPDATE"; pub(crate) const ROLE_DELETE_PERM: &str = "ROLE_DELETE"; pub(crate) const USER_UPDATE_PERM: &str = "USER_UPDATE"; +pub(crate) const USER_VIEW_PERM: &str = "USER_VIEW"; pub(crate) const USER_MANAGEMENT_PERMISSIONS: &[(&'static str, &'static str)] = &[ (ROLE_CREATE_PERM, "Allows the user to create roles"), (ROLE_UPDATE_PERM, "Allows the user to update roles"), (ROLE_DELETE_PERM, "Allows the user to delete roles"), - (ROLE_VIEW_PERM, "Allows to see information for roles"), + (ROLE_VIEW_PERM, "Allows to see information of roles"), ( USER_UPDATE_PERM, "Allows changing the name, password and email of a user", ), + (USER_VIEW_PERM, "Allows to see information of users"), ]; /// The permissions table that stores defined diff --git a/src/database/users.rs b/src/database/users.rs index a773fc1..652a665 100644 --- a/src/database/users.rs +++ b/src/database/users.rs @@ -85,6 +85,12 @@ impl Users { email: &String, password: &Option, ) -> DatabaseResult { + log::trace!( + "Updating user {} with new entries name: {}, email: {}", + old_email, + name, + email + ); let mut connection = self.pool.get()?; if connection .query_opt("SELECT email FROM users WHERE email = $1", &[&old_email])? @@ -124,6 +130,7 @@ impl Users { /// Returns information about a user by Id pub fn get_user(&self, id: i32) -> DatabaseResult { + log::trace!("Looking up entry for user with id {}", id); let mut connection = self.pool.get()?; let result = connection .query_opt("SELECT id, name, email FROM users WHERE id = $1", &[&id])? @@ -133,6 +140,7 @@ impl Users { } pub fn get_user_by_email(&self, email: &String) -> DatabaseResult { + log::trace!("Looking up entry for user with email {}", email); let mut connection = self.pool.get()?; let result = connection .query_opt( @@ -151,6 +159,7 @@ impl Users { email: &String, password: &String, ) -> DatabaseResult { + log::trace!("Creating new tokens for user with email {}", email); if self.validate_login(&email, password)? { let mut connection = self.pool.get()?; let row = connection.query_one("SELECT id FROM users WHERE email = $1", &[&email])?; diff --git a/src/server/documentation/mod.rs b/src/server/documentation/mod.rs index 9d1fda4..64699b0 100644 --- a/src/server/documentation/mod.rs +++ b/src/server/documentation/mod.rs @@ -23,7 +23,6 @@ impl RESTDocumentation { } pub fn get(&self, path: String) -> String { - log::trace!("Rendering help for {}.", path); format!( "{}", include_str!("style.css"), @@ -47,6 +46,7 @@ impl RESTDocumentation { method: &str, description: &str, ) -> Result<(), serde_json::error::Error> { + log::trace!("Prerendering documentation for {}", path); let input_schema = schema_for!(I); let output_schema = schema_for!(O); @@ -65,6 +65,8 @@ impl RESTDocumentation { self.base_path, method, path, description, input_json, output_json ); self.paths.insert(path.to_string(), content); + log::trace!("Documentation for {} rendered", path); + Ok(()) } } diff --git a/src/server/http_server.rs b/src/server/http_server.rs index 3846501..04af191 100644 --- a/src/server/http_server.rs +++ b/src/server/http_server.rs @@ -14,6 +14,7 @@ use serde::Serialize; use crate::database::models::{Role, UserInformation}; use crate::database::permissions::{ ROLE_CREATE_PERM, ROLE_DELETE_PERM, ROLE_UPDATE_PERM, ROLE_VIEW_PERM, USER_UPDATE_PERM, + USER_VIEW_PERM, }; use crate::database::tokens::SessionTokens; use crate::database::Database; @@ -128,6 +129,9 @@ impl UserHttpServer { (POST) (/roles/{name: String}/delete) => { Self::delete_role(&database, request, name).unwrap_or_else(HTTPError::into) }, + (GET) (/users/{email: String}) => { + Self::get_user(&database, request, email).unwrap_or_else(HTTPError::into) + }, (POST) (/users/{email: String}/update) => { Self::update_user(&database, request, email).unwrap_or_else(HTTPError::into) }, @@ -205,6 +209,11 @@ impl UserHttpServer { "POST", "Change user information", )?; + doc.add_path::<(), UserInformation>( + "/users/{email:String}", + "GET", + "See user information", + )?; Ok(doc) } @@ -336,6 +345,13 @@ impl UserHttpServer { })) } + fn get_user(database: &Database, request: &Request, email: String) -> HTTPResult { + require_permission!(database, request, USER_VIEW_PERM); + let user = database.users.get_user_by_email(&email)?; + + Ok(Response::json(&user)) + } + fn update_user(database: &Database, request: &Request, email: String) -> HTTPResult { let (_, id) = validate_request_token(request, database)?; let message = deserialize_body::(&request)?; From fee0f1e1aa2f736344a8984d59b05f9960227ce7 Mon Sep 17 00:00:00 2001 From: trivernis Date: Fri, 13 Nov 2020 17:56:00 +0100 Subject: [PATCH 4/6] Add method to get all users and change /users/* method The method /user/* method now returns the full informatio for a user that also includes the roles of the user. Signed-off-by: trivernis --- src/database/models.rs | 8 ++++++++ src/database/users.rs | 13 +++++++++++++ src/server/http_server.rs | 32 +++++++++++++++++++++++++++++--- 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/database/models.rs b/src/database/models.rs index 8e3d400..ae9dfb2 100644 --- a/src/database/models.rs +++ b/src/database/models.rs @@ -63,3 +63,11 @@ pub struct UserInformation { pub name: String, pub email: String, } + +#[derive(Serialize, Deserialize, JsonSchema)] +pub struct UserFullInformation { + pub id: i32, + pub name: String, + pub email: String, + pub roles: Vec, +} diff --git a/src/database/users.rs b/src/database/users.rs index 652a665..e9cf26f 100644 --- a/src/database/users.rs +++ b/src/database/users.rs @@ -152,6 +152,19 @@ impl Users { Ok(serde_postgres::from_row::(&result)?) } + pub fn get_users(&self) -> DatabaseResult> { + log::trace!("Returning a list of all users..."); + let mut connection = self.pool.get()?; + let results = connection.query("SELECT id, name, email FROM users", &[])?; + let mut users = Vec::new(); + + for result in results { + users.push(serde_postgres::from_row::(&result)?); + } + + Ok(users) + } + /// Creates new tokens for a user login that can be used by services /// that need those tokens to verify a user login pub fn create_tokens( diff --git a/src/server/http_server.rs b/src/server/http_server.rs index 04af191..e9770af 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, UserInformation}; +use crate::database::models::{Role, UserFullInformation, UserInformation}; use crate::database::permissions::{ ROLE_CREATE_PERM, ROLE_DELETE_PERM, ROLE_UPDATE_PERM, ROLE_VIEW_PERM, USER_UPDATE_PERM, USER_VIEW_PERM, @@ -132,6 +132,9 @@ impl UserHttpServer { (GET) (/users/{email: String}) => { Self::get_user(&database, request, email).unwrap_or_else(HTTPError::into) }, + (GET) (/users) => { + Self::get_users(&database, request).unwrap_or_else(HTTPError::into) + }, (POST) (/users/{email: String}/update) => { Self::update_user(&database, request, email).unwrap_or_else(HTTPError::into) }, @@ -209,11 +212,16 @@ impl UserHttpServer { "POST", "Change user information", )?; - doc.add_path::<(), UserInformation>( + doc.add_path::<(), UserFullInformation>( "/users/{email:String}", "GET", "See user information", )?; + doc.add_path::<(), Vec>( + "/users", + "GET", + "Returns information for all users", + )?; Ok(doc) } @@ -278,6 +286,7 @@ impl UserHttpServer { Ok(Response::json(&roles)) } + /// Creates a new role with the given permissions fn create_role(database: &Database, request: &Request) -> HTTPResult { require_permission!(database, request, ROLE_CREATE_PERM); let message: ModifyRoleRequest = serde_json::from_str(parse_string_body(request)?.as_str()) @@ -306,6 +315,7 @@ impl UserHttpServer { .with_status_code(201)) } + /// Updates information for a single role fn update_role(database: &Database, request: &Request, name: String) -> HTTPResult { require_permission!(database, request, ROLE_UPDATE_PERM); let message: ModifyRoleRequest = deserialize_body(&request)?; @@ -335,6 +345,7 @@ impl UserHttpServer { })) } + /// Deletes a role from the database fn delete_role(database: &Database, request: &Request, role: String) -> HTTPResult { require_permission!(database, request, ROLE_DELETE_PERM); database.roles.delete_role(&role)?; @@ -345,11 +356,26 @@ 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); let user = database.users.get_user_by_email(&email)?; + let roles = database.user_roles.by_user(user.id)?; + + Ok(Response::json(&UserFullInformation { + id: user.id, + name: user.name, + email: user.email, + roles, + })) + } + + /// Returns a list of all users + fn get_users(database: &Database, request: &Request) -> HTTPResult { + require_permission!(database, request, USER_VIEW_PERM); + let users = database.users.get_users()?; - Ok(Response::json(&user)) + Ok(Response::json(&users)) } fn update_user(database: &Database, request: &Request, email: String) -> HTTPResult { From 047ead2fd7b23b8d48242a860e005371b6bbd020 Mon Sep 17 00:00:00 2001 From: trivernis Date: Fri, 13 Nov 2020 18:12:13 +0100 Subject: [PATCH 5/6] Add method to create users and fix user update Signed-off-by: trivernis --- src/database/models.rs | 10 ++++++++++ src/database/permissions.rs | 2 ++ src/database/users.rs | 2 +- src/server/http_server.rs | 30 ++++++++++++++++++++++++++---- src/server/messages.rs | 8 ++++++++ 5 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/database/models.rs b/src/database/models.rs index ae9dfb2..92e25ad 100644 --- a/src/database/models.rs +++ b/src/database/models.rs @@ -71,3 +71,13 @@ pub struct UserFullInformation { pub email: String, pub roles: Vec, } + +impl From for UserInformation { + fn from(record: UserRecord) -> Self { + Self { + id: record.id.clone(), + name: record.name.clone(), + email: record.email.clone(), + } + } +} diff --git a/src/database/permissions.rs b/src/database/permissions.rs index c36c22f..fc00d67 100644 --- a/src/database/permissions.rs +++ b/src/database/permissions.rs @@ -14,6 +14,7 @@ pub(crate) const ROLE_DELETE_PERM: &str = "ROLE_DELETE"; pub(crate) const USER_UPDATE_PERM: &str = "USER_UPDATE"; pub(crate) const USER_VIEW_PERM: &str = "USER_VIEW"; +pub(crate) const USER_CREATE_PERM: &str = "USER_CREATE"; pub(crate) const USER_MANAGEMENT_PERMISSIONS: &[(&'static str, &'static str)] = &[ (ROLE_CREATE_PERM, "Allows the user to create roles"), @@ -25,6 +26,7 @@ pub(crate) const USER_MANAGEMENT_PERMISSIONS: &[(&'static str, &'static str)] = "Allows changing the name, password and email of a user", ), (USER_VIEW_PERM, "Allows to see information of users"), + (USER_CREATE_PERM, "Allows the creation of new users"), ]; /// The permissions table that stores defined diff --git a/src/database/users.rs b/src/database/users.rs index e9cf26f..3278029 100644 --- a/src/database/users.rs +++ b/src/database/users.rs @@ -101,7 +101,7 @@ impl Users { } if old_email != email && connection - .query_opt("SELECT email FROM users WHERE email = $1", &[&old_email])? + .query_opt("SELECT email FROM users WHERE email = $1", &[&email])? .is_some() { log::trace!("Failed to create user: New Record exists!"); diff --git a/src/server/http_server.rs b/src/server/http_server.rs index e9770af..44b1c9c 100644 --- a/src/server/http_server.rs +++ b/src/server/http_server.rs @@ -13,15 +13,15 @@ use serde::Serialize; use crate::database::models::{Role, UserFullInformation, UserInformation}; use crate::database::permissions::{ - ROLE_CREATE_PERM, ROLE_DELETE_PERM, ROLE_UPDATE_PERM, ROLE_VIEW_PERM, USER_UPDATE_PERM, - USER_VIEW_PERM, + ROLE_CREATE_PERM, ROLE_DELETE_PERM, ROLE_UPDATE_PERM, ROLE_VIEW_PERM, USER_CREATE_PERM, + USER_UPDATE_PERM, USER_VIEW_PERM, }; use crate::database::tokens::SessionTokens; use crate::database::Database; use crate::server::documentation::RESTDocumentation; use crate::server::messages::{ - DeleteRoleResponse, ErrorMessage, FullRoleData, LoginMessage, LogoutConfirmation, - LogoutMessage, ModifyRoleRequest, RefreshMessage, UpdateUserRequest, + CreateUserRequest, DeleteRoleResponse, ErrorMessage, FullRoleData, LoginMessage, + LogoutConfirmation, LogoutMessage, ModifyRoleRequest, RefreshMessage, UpdateUserRequest, }; use crate::utils::error::DBError; use crate::utils::get_user_id_from_token; @@ -135,6 +135,9 @@ impl UserHttpServer { (GET) (/users) => { Self::get_users(&database, request).unwrap_or_else(HTTPError::into) }, + (POST) (/users/create) => { + Self::create_user(&database, request).unwrap_or_else(HTTPError::into) + }, (POST) (/users/{email: String}/update) => { Self::update_user(&database, request, email).unwrap_or_else(HTTPError::into) }, @@ -222,6 +225,11 @@ impl UserHttpServer { "GET", "Returns information for all users", )?; + doc.add_path::( + "/users/create", + "POST", + "Creates a new user", + )?; Ok(doc) } @@ -378,6 +386,20 @@ impl UserHttpServer { Ok(Response::json(&users)) } + /// Creates a new user + fn create_user(database: &Database, request: &Request) -> HTTPResult { + require_permission!(database, request, USER_CREATE_PERM); + let message = deserialize_body::(&request)?; + let result = database.users.create_user( + message.name.clone(), + message.email.clone(), + message.password.clone(), + )?; + + Ok(Response::json(&UserInformation::from(result)).with_status_code(201)) + } + + /// Updates the information of a user fn update_user(database: &Database, request: &Request, email: String) -> HTTPResult { let (_, id) = validate_request_token(request, database)?; let message = deserialize_body::(&request)?; diff --git a/src/server/messages.rs b/src/server/messages.rs index 99f1ba3..a06192a 100644 --- a/src/server/messages.rs +++ b/src/server/messages.rs @@ -128,3 +128,11 @@ pub struct UpdateUserRequest { pub password: Option, pub own_password: String, } + +#[derive(Deserialize, JsonSchema, Zeroize)] +#[zeroize(drop)] +pub struct CreateUserRequest { + pub name: String, + pub email: String, + pub password: String, +} From 26c8e38c862ba14c8d7fc0075781a61adfc1ae7e Mon Sep 17 00:00:00 2001 From: trivernis Date: Fri, 13 Nov 2020 18:25:40 +0100 Subject: [PATCH 6/6] Add method to delete users Signed-off-by: trivernis --- src/database/permissions.rs | 2 ++ src/database/users.rs | 12 +++++++++++ src/server/http_server.rs | 43 +++++++++++++++++++++++++++++++++---- src/server/messages.rs | 12 +++++++++++ 4 files changed, 65 insertions(+), 4 deletions(-) diff --git a/src/database/permissions.rs b/src/database/permissions.rs index fc00d67..b9df05f 100644 --- a/src/database/permissions.rs +++ b/src/database/permissions.rs @@ -15,6 +15,7 @@ pub(crate) const ROLE_DELETE_PERM: &str = "ROLE_DELETE"; pub(crate) const USER_UPDATE_PERM: &str = "USER_UPDATE"; pub(crate) const USER_VIEW_PERM: &str = "USER_VIEW"; pub(crate) const USER_CREATE_PERM: &str = "USER_CREATE"; +pub(crate) const USER_DELETE_PERM: &str = "USER_DELETE"; pub(crate) const USER_MANAGEMENT_PERMISSIONS: &[(&'static str, &'static str)] = &[ (ROLE_CREATE_PERM, "Allows the user to create roles"), @@ -27,6 +28,7 @@ pub(crate) const USER_MANAGEMENT_PERMISSIONS: &[(&'static str, &'static str)] = ), (USER_VIEW_PERM, "Allows to see information of users"), (USER_CREATE_PERM, "Allows the creation of new users"), + (USER_DELETE_PERM, "Allows the deletion of users"), ]; /// The permissions table that stores defined diff --git a/src/database/users.rs b/src/database/users.rs index 3278029..59e0548 100644 --- a/src/database/users.rs +++ b/src/database/users.rs @@ -165,6 +165,18 @@ impl Users { Ok(users) } + pub fn delete_user(&self, email: &String) -> DatabaseResult<()> { + log::trace!("Deleting user with email {}", email); + let mut connection = self.pool.get()?; + let exists = connection.query_opt("SELECT id FROM users WHERE email = $1", &[email])?; + if exists.is_none() { + return Err(DBError::RecordDoesNotExist); + } + connection.query("DELETE FROM users WHERE email = $1", &[email])?; + + Ok(()) + } + /// Creates new tokens for a user login that can be used by services /// that need those tokens to verify a user login pub fn create_tokens( diff --git a/src/server/http_server.rs b/src/server/http_server.rs index 44b1c9c..c64b0a8 100644 --- a/src/server/http_server.rs +++ b/src/server/http_server.rs @@ -14,14 +14,15 @@ use serde::Serialize; use crate::database::models::{Role, UserFullInformation, UserInformation}; use crate::database::permissions::{ ROLE_CREATE_PERM, ROLE_DELETE_PERM, ROLE_UPDATE_PERM, ROLE_VIEW_PERM, USER_CREATE_PERM, - USER_UPDATE_PERM, USER_VIEW_PERM, + USER_DELETE_PERM, USER_UPDATE_PERM, USER_VIEW_PERM, }; use crate::database::tokens::SessionTokens; use crate::database::Database; use crate::server::documentation::RESTDocumentation; use crate::server::messages::{ - CreateUserRequest, DeleteRoleResponse, ErrorMessage, FullRoleData, LoginMessage, - LogoutConfirmation, LogoutMessage, ModifyRoleRequest, RefreshMessage, UpdateUserRequest, + CreateUserRequest, DeleteRoleResponse, DeleteUserRequest, DeleteUserResponse, ErrorMessage, + FullRoleData, LoginMessage, LogoutConfirmation, LogoutMessage, ModifyRoleRequest, + RefreshMessage, UpdateUserRequest, }; use crate::utils::error::DBError; use crate::utils::get_user_id_from_token; @@ -141,6 +142,9 @@ impl UserHttpServer { (POST) (/users/{email: String}/update) => { Self::update_user(&database, request, email).unwrap_or_else(HTTPError::into) }, + (POST) (/users/{email: String}/delete) => { + Self::delete_user(&database, request, email).unwrap_or_else(HTTPError::into) + }, _ => if request.method() == "OPTIONS" { Response::empty_204() } else { @@ -230,6 +234,11 @@ impl UserHttpServer { "POST", "Creates a new user", )?; + doc.add_path::( + "/users/{email:String}/delete", + "POST", + "Deletes a user", + )?; Ok(doc) } @@ -399,7 +408,7 @@ impl UserHttpServer { Ok(Response::json(&UserInformation::from(result)).with_status_code(201)) } - /// Updates the information of a user + /// 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 message = deserialize_body::(&request)?; @@ -427,6 +436,32 @@ impl UserHttpServer { Ok(Response::json(&record)) } + + /// 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)?; + + if !database + .users + .validate_login(&logged_in_user.email, &message.own_password)? + { + return Err(HTTPError::new( + "Invalid authentication data".to_string(), + 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 { + success: true, + email, + })) + } } /// Parses the body of a http request into a string representation diff --git a/src/server/messages.rs b/src/server/messages.rs index a06192a..85bf895 100644 --- a/src/server/messages.rs +++ b/src/server/messages.rs @@ -136,3 +136,15 @@ pub struct CreateUserRequest { pub email: String, pub password: String, } + +#[derive(Deserialize, JsonSchema, Zeroize)] +#[zeroize(drop)] +pub struct DeleteUserRequest { + pub own_password: String, +} + +#[derive(Serialize, JsonSchema)] +pub struct DeleteUserResponse { + pub email: String, + pub success: bool, +}