From 11e725d30277dd3a44246abf56a1bf51d01f1467 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 12 Sep 2020 21:37:47 +0200 Subject: [PATCH] Add some comments and fix some style issues Signed-off-by: trivernis --- src/database/database_error.rs | 0 src/database/mod.rs | 3 ++- src/database/models.rs | 8 +++++++ src/database/permissions.rs | 3 +++ src/database/role_permissions.rs | 3 +++ src/database/roles.rs | 6 +++++ src/database/tokens.rs | 39 +++++++++++++++++++++++++++++--- src/database/user_roles.rs | 2 ++ src/database/users.rs | 19 +++++++++++++--- src/main.rs | 12 +++++++++- src/server/http_server.rs | 6 +++++ src/server/user_rpc.rs | 10 ++++++++ src/utils/mod.rs | 5 ++++ 13 files changed, 108 insertions(+), 8 deletions(-) delete mode 100644 src/database/database_error.rs diff --git a/src/database/database_error.rs b/src/database/database_error.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/database/mod.rs b/src/database/mod.rs index a5f9e7a..3925c47 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -9,7 +9,6 @@ use postgres::NoTls; use r2d2::Pool; use r2d2_postgres::PostgresConnectionManager; -pub mod database_error; pub mod models; pub mod permissions; pub mod role_permissions; @@ -32,6 +31,8 @@ pub trait Table { fn init(&self) -> DatabaseResult<()>; } +/// A structure that provides access to the databases and handles the creation of models. +/// Since it uses a Pool it can be cloned without losing the connection. #[derive(Clone)] pub struct Database { pool: PostgresPool, diff --git a/src/database/models.rs b/src/database/models.rs index d2bc72a..c89ab31 100644 --- a/src/database/models.rs +++ b/src/database/models.rs @@ -2,6 +2,7 @@ use postgres::Row; use serde::{Deserialize, Serialize}; use zeroize::Zeroize; +/// Record to store data in when retrieving rows from the users table #[derive(Clone, Debug, Zeroize)] #[zeroize(drop)] pub struct UserRecord { @@ -24,6 +25,8 @@ impl UserRecord { } } +/// A row of the permission table that can be serialized and sent +/// via the rcp connection #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Permission { pub id: i32, @@ -31,6 +34,8 @@ pub struct Permission { pub description: String, } +/// A row of the role table that can be serialized and sent +/// via the rcp connection #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Role { pub id: i32, @@ -38,6 +43,9 @@ pub struct Role { pub description: String, } +/// A CreatePermissionEntry data structure that is used as an argument for the +/// bulk permission creation function of the Users Model and can directly be deserialized +/// from the corresponding rcp message. #[derive(Deserialize)] pub struct CreatePermissionsEntry { pub name: String, diff --git a/src/database/permissions.rs b/src/database/permissions.rs index ec000b3..aec5e28 100644 --- a/src/database/permissions.rs +++ b/src/database/permissions.rs @@ -2,6 +2,7 @@ use crate::database::models::{CreatePermissionsEntry, Permission}; use crate::database::{DatabaseResult, PostgresPool, Table, ADMIN_ROLE_NAME}; use crate::utils::error::DBError; +/// The permissions table that stores defined #[derive(Clone)] pub struct Permissions { pool: PostgresPool, @@ -27,6 +28,8 @@ impl Table for Permissions { } impl Permissions { + /// Creates new permissions that are automatically assigned + /// to the admin role upon creation pub fn create_permissions( &self, permissions: Vec, diff --git a/src/database/role_permissions.rs b/src/database/role_permissions.rs index 4e98cd4..52030fa 100644 --- a/src/database/role_permissions.rs +++ b/src/database/role_permissions.rs @@ -2,6 +2,8 @@ use crate::database::models::Permission; use crate::database::{DatabaseResult, PostgresPool, Table}; use crate::utils::error::DBError; +/// The m-n connection table for +/// roles and permissions #[derive(Clone)] pub struct RolePermissions { pool: PostgresPool, @@ -28,6 +30,7 @@ impl Table for RolePermissions { } impl RolePermissions { + /// Returns all permissions for a role pub fn by_role(&self, role_id: i32) -> DatabaseResult> { let mut connection = self.pool.get()?; let rows = connection.query( diff --git a/src/database/roles.rs b/src/database/roles.rs index 0ddd675..d5ded2b 100644 --- a/src/database/roles.rs +++ b/src/database/roles.rs @@ -3,6 +3,8 @@ use crate::database::role_permissions::RolePermissions; use crate::database::{DatabaseResult, PostgresPool, Table, DEFAULT_ADMIN_EMAIL, ENV_ADMIN_EMAIL}; use crate::utils::error::DBError; +/// The role table that stores +/// all defined roles #[derive(Clone)] pub struct Roles { pool: PostgresPool, @@ -32,6 +34,10 @@ impl Table for Roles { } impl Roles { + /// Creates a new role with the given permissions + /// that are then automatically assigned to the role + /// + /// The role is automatically assigned to the default admin user pub fn create_role( &self, name: String, diff --git a/src/database/tokens.rs b/src/database/tokens.rs index ef3b119..6a56a92 100644 --- a/src/database/tokens.rs +++ b/src/database/tokens.rs @@ -8,6 +8,7 @@ use zeroize::Zeroize; const REQUEST_TOKEN_EXPIRE_SECONDS: u32 = 60 * 10; const REFRESH_TOKEN_EXPIRE_SECONDS: u32 = 60 * 60 * 24; +/// A struct to store session tokens of a user in a API-readable format #[derive(Clone, Debug, Zeroize, Serialize)] #[zeroize(drop)] pub struct SessionTokens { @@ -18,6 +19,7 @@ pub struct SessionTokens { } impl SessionTokens { + /// Creates a new sessions token entry with newly generated tokens pub fn new(user_id: i32) -> Self { Self { request_token: base64::encode(create_user_token(user_id)), @@ -27,7 +29,8 @@ impl SessionTokens { } } - pub fn from_tokens(request_token: String, refresh_token: String) -> Self { + /// Creates a sessions token entry with the given tokens + pub fn with_tokens(request_token: String, refresh_token: String) -> Self { Self { request_token, refresh_token, @@ -36,6 +39,8 @@ impl SessionTokens { } } + /// Creates a new session tokens instance from a token store + /// entry pub fn from_entry(other: &TokenStoreEntry) -> Option { let request_token = other.request_token()?; let refresh_token = other.refresh_token()?; @@ -47,6 +52,7 @@ impl SessionTokens { }) } + /// Refreshes the request token pub fn refresh(&mut self) { self.request_token = base64::encode(create_user_token(self.get_user_id())); } @@ -62,6 +68,10 @@ impl SessionTokens { } } +/// A store entry for tokens that keeps track of the token +/// expirations and provides an abstracted access to those. +/// The tokens are stored as their actual bytes representation +/// to decrease the memory impact #[derive(Clone, Debug)] pub struct TokenStoreEntry { request_token: [u8; TOKEN_LENGTH], @@ -72,6 +82,8 @@ pub struct TokenStoreEntry { } impl TokenStoreEntry { + /// Creates a new token store entry with the given tokens + /// and sets the expiration to the configured maximum token lifetime pub fn new(request_token: &String, refresh_token: &String) -> Result { let request_token = base64::decode(request_token).unwrap(); let refresh_token = &base64::decode(refresh_token).unwrap(); @@ -92,6 +104,9 @@ impl TokenStoreEntry { }) } + /// Returns the ttl for the request token that is + /// calculated from the stored instant. + /// If the token is expired -1 is returned. pub fn request_ttl(&self) -> i32 { max( (self.request_ttl - self.ttl_start.elapsed().as_secs() as u32) as i32, @@ -99,6 +114,9 @@ impl TokenStoreEntry { ) } + /// Returns the ttl for the refresh token + /// that is calculated from the stored instant. + /// If the token is expired -1 is returned. pub fn refresh_ttl(&self) -> i32 { max( (self.refresh_ttl - self.ttl_start.elapsed().as_secs() as u32) as i32, @@ -106,6 +124,7 @@ impl TokenStoreEntry { ) } + /// Returns the request token if it hasn't expired pub fn request_token(&self) -> Option { if self.request_ttl() > 0 { Some(base64::encode(&self.request_token)) @@ -114,6 +133,7 @@ impl TokenStoreEntry { } } + /// Returns the refresh token if it hasn't expired pub fn refresh_token(&self) -> Option { if self.refresh_ttl() > 0 { Some(base64::encode(&self.refresh_token)) @@ -122,6 +142,8 @@ impl TokenStoreEntry { } } + /// Sets a new request token and resets + /// the expiration time for the request and refresh token pub fn set_request_token(&mut self, token: String) -> i32 { self.request_token .copy_from_slice(base64::decode(token).unwrap().as_slice()); @@ -132,6 +154,9 @@ impl TokenStoreEntry { self.request_ttl as i32 } + /// Resets the timer that keeps track of the tokens expiration times + /// before resetting the current expiration times are stored so the ttl + /// for both tokens won't reset fn reset_timer(&mut self) { self.request_ttl = min(self.request_ttl(), 0) as u32; self.refresh_ttl = min(self.refresh_ttl(), 0) as u32; @@ -151,7 +176,8 @@ impl TokenStore { } } - pub fn get_request_token(&self, request_token: &String) -> Option<&TokenStoreEntry> { + /// Returns the token store entry for a given request token + pub fn get_by_request_token(&self, request_token: &String) -> Option<&TokenStoreEntry> { let user_id = get_user_id_from_token(&request_token); if let Some(user_tokens) = self.tokens.get(&user_id) { user_tokens.iter().find(|e| { @@ -165,7 +191,9 @@ impl TokenStore { None } } - pub fn get_refresh_token(&self, refresh_token: &String) -> Option<&TokenStoreEntry> { + + /// Returns the token store entry by the given refresh token + pub fn get_by_refresh_token(&self, refresh_token: &String) -> Option<&TokenStoreEntry> { let user_id = get_user_id_from_token(&refresh_token); if let Some(user_tokens) = self.tokens.get(&user_id) { user_tokens.iter().find(|e| { @@ -179,6 +207,9 @@ impl TokenStore { None } } + + /// Sets the request token for a given refresh token + /// Also clears all expired token entries. pub fn set_request_token(&mut self, refresh_token: &String, request_token: &String) { self.clear_expired(); let user_id = get_user_id_from_token(&request_token); @@ -193,6 +224,7 @@ impl TokenStore { } } + /// Inserts a new pair of request and refresh token pub fn insert(&mut self, request_token: &String, refresh_token: &String) -> Result<(), String> { let user_id = get_user_id_from_token(refresh_token); let user_tokens = if let Some(user_tokens) = self.tokens.get_mut(&user_id) { @@ -218,6 +250,7 @@ impl TokenStore { Ok(()) } + /// Deletes all expired tokens from the store pub fn clear_expired(&mut self) { for (key, entry) in &self.tokens.clone() { self.tokens.insert( diff --git a/src/database/user_roles.rs b/src/database/user_roles.rs index f4b07c6..188fa4a 100644 --- a/src/database/user_roles.rs +++ b/src/database/user_roles.rs @@ -2,6 +2,7 @@ use crate::database::models::Role; use crate::database::{DatabaseResult, PostgresPool, Table}; use crate::utils::error::DBError; +/// A table that stores the relation between users and roles #[derive(Clone)] pub struct UserRoles { pool: PostgresPool, @@ -28,6 +29,7 @@ impl Table for UserRoles { } impl UserRoles { + /// Returns all roles a user is asigned to pub fn by_user(&self, user_id: i32) -> DatabaseResult> { let mut connection = self.pool.get()?; let rows = connection.query( diff --git a/src/database/users.rs b/src/database/users.rs index 797840c..fcb2f3d 100644 --- a/src/database/users.rs +++ b/src/database/users.rs @@ -9,6 +9,7 @@ use parking_lot::Mutex; use std::sync::Arc; use zeroize::{Zeroize, Zeroizing}; +/// Table that stores users with their email addresses and hashed passwords #[derive(Clone)] pub struct Users { pool: PostgresPool, @@ -41,6 +42,9 @@ impl Table for Users { } impl Users { + /// Creates a new user and returns an error if the user already exists. + /// When creating the user first a salt is generated, then the password is hashed + /// with BCrypt and the given salt. The salt and the hashed password are then stored into the database pub fn create_user( &self, name: String, @@ -69,6 +73,8 @@ impl Users { Ok(UserRecord::from_ordered_row(&row)) } + /// 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( &self, email: &String, @@ -88,9 +94,11 @@ impl Users { } } + /// Validates a request token and returns if it's valid and the + /// ttl of the token pub fn validate_request_token(&self, token: &String) -> DatabaseResult<(bool, i32)> { let store = self.token_store.lock(); - let entry = store.get_request_token(&token); + let entry = store.get_by_request_token(&token); if let Some(entry) = entry { Ok((true, entry.request_ttl())) @@ -99,9 +107,10 @@ impl Users { } } + /// Validates a refresh token and returns if it's valid and the ttl pub fn validate_refresh_token(&self, token: &String) -> DatabaseResult<(bool, i32)> { let store = self.token_store.lock(); - let entry = store.get_refresh_token(&token); + let entry = store.get_by_refresh_token(&token); if let Some(entry) = entry { Ok((true, entry.refresh_ttl())) @@ -110,9 +119,11 @@ impl Users { } } + /// Returns a new request token for a given refresh token + /// if the refresh token is valid pub fn refresh_tokens(&self, refresh_token: &String) -> DatabaseResult { let mut token_store = self.token_store.lock(); - let tokens = token_store.get_refresh_token(refresh_token); + let tokens = token_store.get_by_refresh_token(refresh_token); if let Some(mut tokens) = tokens.and_then(|t| SessionTokens::from_entry(t)) { tokens.refresh(); tokens.store(&mut token_store)?; @@ -123,6 +134,8 @@ 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 { let mut connection = self.pool.get()?; let row = connection diff --git a/src/main.rs b/src/main.rs index 13ab47f..2753d68 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,13 +11,19 @@ use std::thread::Builder; fn main() { init_logger(); + // Create a new database and initialize it let database = Database::new().unwrap(); database.init().unwrap(); + + // Create the required servers let rpc_server = UserRpcServer::new(&database); let http_server = UserHttpServer::new(&database); + + // Create a new waitgroup that is used to wait for both servers to exit let wg = WaitGroup::new(); { let wg = WaitGroup::clone(&wg); + // Build a thread named rpc and start the rpc server inside of it Builder::new() .name("rpc".to_string()) .spawn(move || { @@ -28,7 +34,7 @@ fn main() { } { let wg = WaitGroup::clone(&wg); - let http_server = http_server; + // Build a new http thread and start the http server inside of it Builder::new() .name("http".to_string()) .spawn(move || { @@ -37,9 +43,13 @@ fn main() { }) .unwrap(); } + + // Wait for both servers to exit wg.wait(); } +/// Initializes the env_logger with a custom format +/// that also logs the thread names fn init_logger() { env_logger::Builder::from_env(Env::default().default_filter_or("info")) .format(|buf, record| { diff --git a/src/server/http_server.rs b/src/server/http_server.rs index 7c18de5..9d8ab71 100644 --- a/src/server/http_server.rs +++ b/src/server/http_server.rs @@ -10,6 +10,8 @@ use std::io::Read; const LISTEN_ADDRESS: &str = "HTTP_SERVER_ADDRESS"; const DEFAULT_LISTEN_ADDRESS: &str = "127.0.0.1:8080"; +/// The HTTP server of the user management that provides a +/// REST api for login and requesting tokens pub struct UserHttpServer { database: Database, } @@ -57,6 +59,8 @@ impl UserHttpServer { } } + /// Stats the server. + /// This call blocks until the server is shut down. pub fn start(&self) { log::info!("Starting HTTP-Server..."); let listen_address = @@ -78,6 +82,7 @@ impl UserHttpServer { server.run() } + /// Handles the login part of the REST api fn login(database: &Database, request: &Request) -> HTTPResult { if let Some(mut data) = request.data() { let mut data_string = String::new(); @@ -95,6 +100,7 @@ impl UserHttpServer { } } + /// Handles the new token part of the rest api fn new_token(database: &Database, request: &Request) -> HTTPResult { if let Some(mut data) = request.data() { let mut data_string = String::new(); diff --git a/src/server/user_rpc.rs b/src/server/user_rpc.rs index 600170f..3e9aa33 100644 --- a/src/server/user_rpc.rs +++ b/src/server/user_rpc.rs @@ -17,6 +17,9 @@ use std::thread::Builder; const RPC_SERVER_ADDRESS: &str = "RPC_SERVER_ADDRESS"; const DEFAULT_SERVER_ADDRESS: &str = "127.0.0.1:5555"; +/// The RPC server that provides an interface +/// for applications to validate request tokens +/// and request the assigned roles pub struct UserRpcServer { database: Database, } @@ -30,6 +33,7 @@ impl UserRpcServer { } } + /// Stats the user rpc server with 2 x num-cpus threads. pub fn start(&self) { let listen_address = dotenv::var(RPC_SERVER_ADDRESS).unwrap_or(DEFAULT_SERVER_ADDRESS.to_string()); @@ -70,6 +74,7 @@ impl UserRpcServer { } } + /// Handles the validation of request tokens fn handle_validate_token(database: Database, data: &Vec) -> RpcResult { log::trace!("Validating token."); let message = TokenRequest::deserialize(&mut Deserializer::new(&mut data.as_slice())) @@ -84,6 +89,7 @@ impl UserRpcServer { Ok(Message::new(VALIDATE_TOKEN, data)) } + /// Handles a INFO message that returns all valid methods of the rpc sserver fn handle_info() -> RpcResult { log::trace!("Get Info"); Ok(Message::new_with_serialize( @@ -124,6 +130,7 @@ impl UserRpcServer { )) } + /// Returns all permissions of a role fn handle_get_permissions(database: Database, data: &Vec) -> RpcResult { log::trace!("Get Permissions"); let message = @@ -141,6 +148,7 @@ impl UserRpcServer { )) } + /// Returns all roles of a user fn handle_get_roles(database: Database, data: &Vec) -> RpcResult { log::trace!("Get Roles"); let message = TokenRequest::deserialize(&mut Deserializer::new(&mut data.as_slice())) @@ -159,6 +167,7 @@ impl UserRpcServer { Ok(Message::new_with_serialize(GET_ROLES, response_data)) } + /// 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())) @@ -171,6 +180,7 @@ impl UserRpcServer { Ok(Message::new_with_serialize(CREATE_ROLE, role)) } + /// Handles the request for creating new permissions. fn handle_create_permissions(database: Database, data: &Vec) -> RpcResult { log::trace!("Create Permission"); let message = diff --git a/src/utils/mod.rs b/src/utils/mod.rs index dfd6ae5..3354d07 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -8,6 +8,7 @@ pub mod error; pub const TOKEN_LENGTH: usize = 32; const SALT_LENGTH: usize = 16; +/// Creates a new random salt pub fn create_salt() -> [u8; SALT_LENGTH] { let mut rng = rand::thread_rng(); let mut salt = [0u8; SALT_LENGTH]; @@ -16,6 +17,8 @@ pub fn create_salt() -> [u8; SALT_LENGTH] { salt } +/// Creates a new random user token where the first 4 bytes represent +/// the userId pub fn create_user_token(user_id: i32) -> [u8; TOKEN_LENGTH] { let mut rng = rand::thread_rng(); let mut value = [0u8; TOKEN_LENGTH]; @@ -25,11 +28,13 @@ pub fn create_user_token(user_id: i32) -> [u8; TOKEN_LENGTH] { value } +/// Extracts the userId from a request token pub fn get_user_id_from_token(token: &String) -> i32 { let token = base64::decode(&token).unwrap(); BigEndian::read_i32(token.as_slice()) } +/// Hashes a password with a salt by using BCrypt pub fn hash_password(password: &[u8], salt: &[u8]) -> Result<[u8; 24], String> { panic::catch_unwind(|| { let mut pw_hash = [0u8; 24];