From 314da98c178e37f8a82011225668983fba7b264c Mon Sep 17 00:00:00 2001 From: trivernis Date: Fri, 13 Nov 2020 13:41:34 +0100 Subject: [PATCH] Add path roles/update to update a specific role Signed-off-by: trivernis --- src/database/permissions.rs | 22 +++++++ src/database/roles.rs | 117 +++++++++++++++++++++--------------- src/server/http_server.rs | 53 ++++++++++++++-- src/server/messages.rs | 4 +- src/server/user_rpc.rs | 4 +- 5 files changed, 142 insertions(+), 58 deletions(-) diff --git a/src/database/permissions.rs b/src/database/permissions.rs index 60a8c81..078026d 100644 --- a/src/database/permissions.rs +++ b/src/database/permissions.rs @@ -4,6 +4,8 @@ use crate::database::models::{CreatePermissionsEntry, Permission}; 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"; @@ -86,4 +88,24 @@ impl Permissions { Ok(created_permissions) } + + /// Returns a list of permission IDs that don't exist in the database + pub fn get_not_existing(&self, permissions_vec: &Vec) -> DatabaseResult> { + let permissions = HashSet::from_iter(permissions_vec.iter().cloned()); + let mut connection = self.pool.get()?; + let rows = connection.query( + "SELECT id FROM permissions WHERE id = ANY($1)", + &[permissions_vec], + )?; + let existing_perms = rows + .into_iter() + .map(|row| -> i32 { row.get(0) }) + .collect::>(); + let not_existing_perms = permissions + .difference(&existing_perms) + .cloned() + .collect::>(); + + Ok(not_existing_perms) + } } diff --git a/src/database/roles.rs b/src/database/roles.rs index e723046..963ae79 100644 --- a/src/database/roles.rs +++ b/src/database/roles.rs @@ -6,6 +6,8 @@ use crate::database::models::Role; use crate::database::role_permissions::RolePermissions; use crate::database::{DatabaseResult, PostgresPool, Table, DEFAULT_ADMIN_EMAIL, ENV_ADMIN_EMAIL}; use crate::utils::error::DBError; +use std::collections::HashSet; +use std::iter::FromIterator; /// The role table that stores /// all defined roles @@ -48,68 +50,39 @@ impl Roles { description: Option, permissions: Vec, ) -> DatabaseResult { + let permissions: HashSet = HashSet::from_iter(permissions.into_iter()); let mut connection = self.pool.get()?; let exists = connection.query_opt("SELECT id FROM roles WHERE name = $1", &[&name])?; if exists.is_some() { return Err(DBError::RecordExists); } - let permissions_exist = connection.query( - "SELECT id FROM permissions WHERE permissions.id = ANY ($1)", - &[&permissions], - )?; - if permissions_exist.len() != permissions.len() { - return Err(DBError::GenericError(format!( - "Not all provided permissions exist! Existing permissions: {:?}", - permissions_exist - .iter() - .map(|row| -> i32 { row.get(0) }) - .collect::>() - ))); - } log::trace!("Preparing transaction"); let admin_email = dotenv::var(ENV_ADMIN_EMAIL).unwrap_or(DEFAULT_ADMIN_EMAIL.to_string()); let mut transaction = connection.transaction()?; - let result: DatabaseResult = { - let row = transaction.query_one( - "INSERT INTO roles (name, description) VALUES ($1, $2) RETURNING *", - &[&name, &description], + let row = transaction.query_one( + "INSERT INTO roles (name, description) VALUES ($1, $2) RETURNING *", + &[&name, &description], + )?; + let role: Role = serde_postgres::from_row(&row)?; + for permission in permissions { + transaction.execute( + "INSERT INTO role_permissions (role_id, permission_id) VALUES ($1, $2);", + &[&role.id, &permission], )?; - let role: Role = serde_postgres::from_row(&row)?; - for permission in permissions { - transaction.execute( - "INSERT INTO role_permissions (role_id, permission_id) VALUES ($1, $2);", - &[&role.id, &permission], - )?; - } - if let Err(e) = transaction.execute( - "INSERT INTO user_roles (user_id, role_id) VALUES ((SELECT id FROM users WHERE email = $1), $2)", - &[&admin_email, &role.id], - ) { - log::debug!("Failed to add role to admin user: {}", e); - } - - Ok(role) - }; - match result { - Err(e) => { - log::warn!("Failed to create role {}: {}", name, e); - log::trace!("Rolling back..."); - transaction.rollback()?; - log::trace!("Rolled back!"); - Err(e) - } - Ok(role) => { - log::debug!("Successfully created role {} with id {}", name, role.id); - log::trace!("Committing..."); - transaction.commit()?; - log::trace!("Committed!"); - - Ok(role) - } } + if let Err(e) = transaction.execute( + "INSERT INTO user_roles (user_id, role_id) VALUES ((SELECT id FROM users WHERE email = $1), $2)", + &[&admin_email, &role.id], + ) { + log::debug!("Failed to add role to admin user: {}", e); + } + + transaction.commit()?; + + Ok(role) } /// Returns information for a role @@ -136,4 +109,50 @@ impl Roles { Ok(roles) } + + pub fn update_role( + &self, + name: String, + description: Option, + permissions: Vec, + ) -> DatabaseResult { + let permissions = HashSet::from_iter(permissions.into_iter()); + let mut connection = self.pool.get()?; + let mut transaction = connection.transaction()?; + + let id: i32 = transaction + .query_opt("SELECT id FROM roles WHERE name = $1", &[&name])? + .ok_or(DBError::RecordDoesNotExist)? + .get(0); + let update_result = transaction.query_one( + "UPDATE roles SET description = $2 WHERE id = $1 RETURNING *", + &[&id, &description], + )?; + let current_permissions = transaction + .query( + "SELECT permission_id from role_permissions WHERE role_id = $1", + &[&id], + )? + .into_iter() + .map(|r| -> i32 { r.get(0) }) + .collect::>(); + let new_permissions = permissions.difference(¤t_permissions); + let deleted_permissions = current_permissions.difference(&permissions); + + for new in new_permissions { + transaction.query( + "INSERT INTO role_permissions (role_id, permission_id) VALUES ($1, $2)", + &[&id, new], + )?; + } + for deleted in deleted_permissions { + transaction.query( + "DELETE FROM role_permissions WHERE role_id = $1 AND permission_id = $2", + &[&id, deleted], + )?; + } + transaction.commit()?; + + Ok(serde_postgres::from_row::(&update_result)?) + } } diff --git a/src/server/http_server.rs b/src/server/http_server.rs index d8524ae..23ecd2c 100644 --- a/src/server/http_server.rs +++ b/src/server/http_server.rs @@ -11,10 +11,13 @@ use rouille::{Request, Response, Server}; use serde::export::Formatter; use serde::Serialize; -use crate::database::permissions::{CREATE_ROLE_PERMISSION, VIEW_ROLE_PERMISSION}; +use crate::database::permissions::{ + CREATE_ROLE_PERMISSION, UPDATE_ROLE_PERMISSION, VIEW_ROLE_PERMISSION, +}; use crate::database::Database; use crate::server::messages::{ - CreateRoleRequest, FullRowData, LoginMessage, LogoutConfirmation, LogoutMessage, RefreshMessage, + ErrorMessage, FullRoleData, LoginMessage, LogoutConfirmation, LogoutMessage, ModifyRoleRequest, + RefreshMessage, }; use crate::utils::error::DBError; use crate::utils::get_user_id_from_token; @@ -112,6 +115,9 @@ impl UserHttpServer { (POST) (/roles/create) => { Self::create_role(&database, request).unwrap_or_else(HTTPError::into) }, + (POST) (/roles/update) => { + Self::update_role(&database, request).unwrap_or_else(HTTPError::into) + }, _ => if request.method() == "OPTIONS" { Response::empty_204() } else { @@ -180,7 +186,7 @@ impl UserHttpServer { let role = database.roles.get_role(name)?; let permissions = database.role_permission.by_role(role.id)?; - Ok(Response::json(&FullRowData { + Ok(Response::json(&FullRoleData { id: role.id, name: role.name, permissions, @@ -197,21 +203,58 @@ impl UserHttpServer { fn create_role(database: &Database, request: &Request) -> HTTPResult { require_permission!(database, request, CREATE_ROLE_PERMISSION); - let message: CreateRoleRequest = serde_json::from_str(parse_string_body(request)?.as_str()) + 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 + .permissions + .get_not_existing(&message.permissions)?; + if !not_existing.is_empty() { + return Ok(Response::json(&ErrorMessage::new(format!( + "The permissions {:?} don't exist", + not_existing + ))) + .with_status_code(400)); + } let role = database .roles .create_role(message.name, message.description, message.permissions)?; let permissions = database.role_permission.by_role(role.id)?; - Ok(Response::json(&FullRowData { + Ok(Response::json(&FullRoleData { id: role.id, permissions, name: role.name, }) .with_status_code(201)) } + + fn update_role(database: &Database, request: &Request) -> 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))?; + let not_existing = database + .permissions + .get_not_existing(&message.permissions)?; + if !not_existing.is_empty() { + return Ok(Response::json(&ErrorMessage::new(format!( + "The permissions {:?} don't exist", + not_existing + ))) + .with_status_code(400)); + } + let role = + database + .roles + .update_role(message.name, message.description, message.permissions)?; + let permissions = database.role_permission.by_role(role.id)?; + + Ok(Response::json(&FullRoleData { + id: role.id, + permissions, + name: role.name, + })) + } } /// Parses the body of a http request into a string representation diff --git a/src/server/messages.rs b/src/server/messages.rs index 98c9f51..c021372 100644 --- a/src/server/messages.rs +++ b/src/server/messages.rs @@ -72,7 +72,7 @@ pub struct GetPermissionsRequest { } #[derive(Deserialize)] -pub struct CreateRoleRequest { +pub struct ModifyRoleRequest { pub name: String, pub description: Option, pub permissions: Vec, @@ -108,7 +108,7 @@ pub struct LogoutConfirmation { } #[derive(Serialize)] -pub struct FullRowData { +pub struct FullRoleData { pub id: i32, pub name: String, pub permissions: Vec, diff --git a/src/server/user_rpc.rs b/src/server/user_rpc.rs index 1168852..08f04be 100644 --- a/src/server/user_rpc.rs +++ b/src/server/user_rpc.rs @@ -14,7 +14,7 @@ use serde::Deserialize; use crate::database::Database; use crate::server::messages::{ - CreatePermissionsRequest, CreateRoleRequest, ErrorMessage, GetPermissionsRequest, InfoEntry, + CreatePermissionsRequest, ErrorMessage, GetPermissionsRequest, InfoEntry, ModifyRoleRequest, TokenRequest, }; use crate::utils::get_user_id_from_token; @@ -185,7 +185,7 @@ impl UserRpcServer { /// Handles the requests for creating new roles fn handle_create_role(database: Database, data: &Vec) -> RpcResult { log::trace!("Create Role"); - let message = CreateRoleRequest::deserialize(&mut Deserializer::new(&mut data.as_slice())) + let message = ModifyRoleRequest::deserialize(&mut Deserializer::new(&mut data.as_slice())) .map_err(|e| ErrorMessage::new(e.to_string()))?; let role = database