diff --git a/docs/db_struct.sql b/docs/db_struct.sql index c2a8618..4112730 100644 --- a/docs/db_struct.sql +++ b/docs/db_struct.sql @@ -296,3 +296,11 @@ CREATE TABLE `comunic_admin_roles` ( `role_id` VARCHAR(25) NULL, `time_insert` INT NULL, PRIMARY KEY (`id`)); + +CREATE TABLE `comunic_admin_log` ( + `id` INT NOT NULL AUTO_INCREMENT, + `admin_id` INT NULL, + `ip` VARCHAR(40) NULL, + `time` INT NULL, + `action` VARCHAR(100) NULL, + PRIMARY KEY (`id`)); diff --git a/docs/migration.sql b/docs/migration.sql index 530a9b4..db30ab4 100644 --- a/docs/migration.sql +++ b/docs/migration.sql @@ -22,3 +22,11 @@ CREATE TABLE `comunic_admin_roles` ( `role_id` VARCHAR(25) NULL, `time_insert` INT NULL, PRIMARY KEY (`id`)); + +CREATE TABLE `comunic_admin_log` ( + `id` INT NOT NULL AUTO_INCREMENT, + `admin_id` INT NULL, + `ip` VARCHAR(40) NULL, + `time` INT NULL, + `action` VARCHAR(100) NULL, + PRIMARY KEY (`id`)); diff --git a/src/constants.rs b/src/constants.rs index 29e1e12..cef8ba9 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -65,6 +65,7 @@ pub mod database_tables_names { pub const ADMIN_LIST_TABLE: &str = "comunic_admin"; pub const ADMIN_KEYS_TABLE: &str = "comunic_admin_key"; pub const ADMIN_ROLES_TABLE: &str = "comunic_admin_roles"; + pub const ADMIN_LOGS_TABLE: &str = "comunic_admin_log"; } /// Push Notifications Database prefix diff --git a/src/controllers/admin/admin_account_controller.rs b/src/controllers/admin/admin_account_controller.rs index 697cc99..cbce95f 100644 --- a/src/controllers/admin/admin_account_controller.rs +++ b/src/controllers/admin/admin_account_controller.rs @@ -9,20 +9,26 @@ use crate::api_data::admin::admin_id_api::AdminIDAPI; use crate::api_data::admin::admin_info_api::AdminInfoAPI; use crate::api_data::admin::admin_res_create_account::AdminResCreateAccount; use crate::api_data::admin::admin_res_create_reset_token::AdminResCreateResetToken; +use crate::constants::admin::AdminRole; use crate::data::admin::{NewAdmin, NewAdminGeneralSettings}; +use crate::data::admin_action_log::AdminAction; use crate::data::base_request_handler::BaseRequestHandler; use crate::data::http_request_handler::HttpRequestHandler; use crate::helpers::{admin_access_token_helper, admin_account_helper, admin_account_key_helper}; +use crate::helpers::admin_log_helper::log_admin_action; use crate::routes::RequestResult; use crate::utils::date_utils::time; -use crate::constants::admin::AdminRole; /// Create a new administrator account pub fn create(r: &mut HttpRequestHandler) -> RequestResult { let email = r.post_email("mail")?; let name = r.post_string_opt("name", 3, true)?; - let admin_id = admin_account_helper::create(&NewAdmin { name, email })?; + let new_admin = NewAdmin { name, email }; + let admin_id = admin_account_helper::create(&new_admin)?; + + log_admin_action(r.admin_id()?, &r.remote_ip(), + AdminAction::CreatedAdmin { id: admin_id, name: new_admin.name, email: new_admin.email })?; r.set_response(AdminResCreateAccount::new(admin_id)) } @@ -52,6 +58,9 @@ pub fn auth_with_reset_token(r: &mut HttpRequestHandler) -> RequestResult { let token = admin_access_token_helper::create(admin.id)?; + log_admin_action(admin.id, &r.remote_ip(), + AdminAction::AuthWithResetToken)?; + r.set_response(AdminAuthSuccess::new(token)) } @@ -102,10 +111,13 @@ pub fn update_general_settings(r: &mut HttpRequestHandler) -> RequestResult { admin_account_helper::set_general_settings(NewAdminGeneralSettings { id: admin_id, - name: new_name, - email: new_email, + name: new_name.to_string(), + email: new_email.to_string(), })?; + log_admin_action(r.admin_id()?, &r.remote_ip(), + AdminAction::UpdatedAdminGeneralSettings { target: admin_id, new_name, new_email })?; + r.ok() } @@ -119,5 +131,8 @@ pub fn generate_reset_token(r: &mut HttpRequestHandler) -> RequestResult { let token = admin_account_helper::create_new_reset_token(admin_id)?; + log_admin_action(r.admin_id()?, &r.remote_ip(), + AdminAction::GeneratedAdminResetToken { target: admin_id })?; + r.set_response(AdminResCreateResetToken::new(token)) } \ No newline at end of file diff --git a/src/controllers/admin/admin_keys_controller.rs b/src/controllers/admin/admin_keys_controller.rs index 32fb864..838a5a6 100644 --- a/src/controllers/admin/admin_keys_controller.rs +++ b/src/controllers/admin/admin_keys_controller.rs @@ -4,15 +4,17 @@ use crate::api_data::admin::admin_auth_success::AdminAuthSuccess; +use crate::api_data::admin::admin_keys_api::AdminKeyAPI; +use crate::constants::admin::AdminRole; use crate::data::admin::AdminKey; +use crate::data::admin_action_log::AdminAction; use crate::data::base_request_handler::BaseRequestHandler; use crate::data::error::Res; use crate::data::http_request_handler::HttpRequestHandler; use crate::data::webauthn_config::get_wan; use crate::helpers::{admin_access_token_helper, admin_account_helper, admin_account_key_helper, admin_key_authentication_challenges_helper, admin_key_registration_challenges_helper}; +use crate::helpers::admin_log_helper::log_admin_action; use crate::routes::RequestResult; -use crate::api_data::admin::admin_keys_api::AdminKeyAPI; -use crate::constants::admin::AdminRole; impl HttpRequestHandler { pub fn post_admin_auth_key(&mut self, name_mail: &str, name_key_id: &str) -> Res { @@ -73,6 +75,9 @@ pub fn register_key(r: &mut HttpRequestHandler) -> RequestResult { admin_account_key_helper::add_key(r.admin_id()?, &name, key)?; + log_admin_action(r.admin_id()?, &r.remote_ip(), + AdminAction::RegisteredAdminKey { name, target: r.admin_id()? })?; + r.ok() } @@ -87,6 +92,9 @@ pub fn delete_auth_key(r: &mut HttpRequestHandler) -> RequestResult { for key in admin_account_key_helper::get_admin_keys(admin_id)? { if key.id == key_id { + log_admin_action(r.admin_id()?, &r.remote_ip(), + AdminAction::DeletedAdminKey { name: key.name.to_string(), target: admin_id })?; + admin_account_key_helper::delete_key(key)?; return r.ok(); @@ -125,5 +133,8 @@ pub fn auth_with_key(r: &mut HttpRequestHandler) -> RequestResult { // Generate access token let token = admin_access_token_helper::create(key.admin_id)?; + log_admin_action(key.admin_id, &r.remote_ip(), + AdminAction::AuthWithAccessKey { key: key.name, key_id: key.id })?; + r.set_response(AdminAuthSuccess::new(token)) } \ No newline at end of file diff --git a/src/controllers/admin/admin_roles_controller.rs b/src/controllers/admin/admin_roles_controller.rs index 040496f..2e41bb6 100644 --- a/src/controllers/admin/admin_roles_controller.rs +++ b/src/controllers/admin/admin_roles_controller.rs @@ -4,8 +4,10 @@ use crate::api_data::admin::admin_role_api::AdminRoleDetailsAPI; use crate::constants::admin::{ADMIN_ROLES_LIST, AdminRole}; +use crate::data::admin_action_log::AdminAction; use crate::data::base_request_handler::BaseRequestHandler; use crate::data::http_request_handler::HttpRequestHandler; +use crate::helpers::admin_log_helper::log_admin_action; use crate::helpers::admin_roles_helper; use crate::routes::RequestResult; @@ -30,8 +32,13 @@ pub fn toggle(r: &mut HttpRequestHandler) -> RequestResult { if !enable { admin_roles_helper::remove_role(admin_id, role)?; + log_admin_action(r.admin_id()?, &r.remote_ip(), + AdminAction::RemoveAdminRole { target: admin_id, role: role_str })?; } else if !admin_roles_helper::has_role(admin_id, role)? { admin_roles_helper::add_role(admin_id, role)?; + + log_admin_action(r.admin_id()?, &r.remote_ip(), + AdminAction::AddAdminRole { target: admin_id, role: role_str })?; } r.ok() diff --git a/src/data/admin.rs b/src/data/admin.rs index c648f87..ef09fe7 100644 --- a/src/data/admin.rs +++ b/src/data/admin.rs @@ -2,9 +2,12 @@ //! //! @author Pierre Hubert +use mysql::serde::{Deserializer, Serializer}; +use serde::{Deserialize, Serialize}; use webauthn_rs::proto::Credential; use crate::constants::admin::{ADMIN_ROLES_LIST, AdminRole}; +use crate::data::u64_visitor::U64Visitor; #[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)] pub struct AdminID(u64); @@ -82,4 +85,18 @@ impl AdminRole { .next() .expect("Should have found a role!!!") } +} + +impl Serialize for AdminID { + fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where + S: Serializer { + serializer.serialize_u64(self.0) + } +} + +impl<'de> Deserialize<'de> for AdminID { + fn deserialize(deserializer: D) -> Result>::Error> where + D: Deserializer<'de> { + deserializer.deserialize_u64(U64Visitor {}).map(|id| AdminID::new(id)) + } } \ No newline at end of file diff --git a/src/data/admin_action_log.rs b/src/data/admin_action_log.rs new file mode 100644 index 0000000..34f2142 --- /dev/null +++ b/src/data/admin_action_log.rs @@ -0,0 +1,26 @@ +//! # Admin action log + +use crate::data::admin::AdminID; + +#[derive(serde::Serialize, serde::Deserialize)] +pub enum AdminAction { + AuthWithResetToken, + AuthWithAccessKey { key: String, key_id: u64 }, + RegisteredAdminKey { name: String, target: AdminID }, + DeletedAdminKey { name: String, target: AdminID }, + GeneratedAdminResetToken { target: AdminID }, + CreatedAdmin { id: AdminID, name: String, email: String }, + UpdatedAdminGeneralSettings { target: AdminID, new_email: String, new_name: String }, + AddAdminRole { target: AdminID, role: String }, + RemoveAdminRole { target: AdminID, role: String }, + UnsupportedAction, +} + + +pub struct AdminActionLog { + pub id: u64, + pub admin_id: AdminID, + pub ip: String, + pub time: u64, + pub action: AdminAction, +} \ No newline at end of file diff --git a/src/data/mod.rs b/src/data/mod.rs index 71b16e1..30f8d03 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -42,4 +42,6 @@ pub mod new_notifications_settings; pub mod push_notification; pub mod presence; pub mod admin; -pub mod webauthn_config; \ No newline at end of file +pub mod webauthn_config; +pub mod admin_action_log; +pub mod u64_visitor; \ No newline at end of file diff --git a/src/data/u64_visitor.rs b/src/data/u64_visitor.rs new file mode 100644 index 0000000..2910f0a --- /dev/null +++ b/src/data/u64_visitor.rs @@ -0,0 +1,20 @@ +use std::fmt; +use std::fmt::Formatter; + +use mysql::serde::de::Error; +use serde::de::Visitor; + +pub struct U64Visitor; + +impl<'de> Visitor<'de> for U64Visitor { + type Value = u64; + + fn expecting<'a>(&self, formatter: &mut Formatter<'a>) -> fmt::Result { + formatter.write_str("An unsigned integer value") + } + + fn visit_u64(self, v: u64) -> Result where + E: Error, { + Ok(v) + } +} \ No newline at end of file diff --git a/src/helpers/admin_log_helper.rs b/src/helpers/admin_log_helper.rs new file mode 100644 index 0000000..22a3645 --- /dev/null +++ b/src/helpers/admin_log_helper.rs @@ -0,0 +1,20 @@ +//! # Admin logs management +//! +//! @author Pierre Hubert + +use crate::constants::database_tables_names::ADMIN_LOGS_TABLE; +use crate::data::admin::AdminID; +use crate::data::admin_action_log::AdminAction; +use crate::data::error::Res; +use crate::helpers::database; +use crate::utils::date_utils::time; + +/// Record an administrator action +pub fn log_admin_action(admin: AdminID, ip: &str, action: AdminAction) -> Res { + database::InsertQuery::new(ADMIN_LOGS_TABLE) + .add_admin_id("admin_id", admin) + .add_str("ip", ip) + .add_u64("time", time()) + .add_str("action", &serde_json::to_string(&action)?) + .insert_drop_result() +} \ No newline at end of file diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs index c25db87..f6fdf21 100644 --- a/src/helpers/mod.rs +++ b/src/helpers/mod.rs @@ -27,4 +27,5 @@ pub mod admin_account_key_helper; pub mod admin_access_token_helper; pub mod admin_key_registration_challenges_helper; pub mod admin_key_authentication_challenges_helper; -pub mod admin_roles_helper; \ No newline at end of file +pub mod admin_roles_helper; +pub mod admin_log_helper; \ No newline at end of file