use std::{
error::Error,
fs::{self, DirBuilder},
os::unix::fs::{DirBuilderExt, PermissionsExt},
path::Path,
time::SystemTime,
};
use log::{debug, error, info, warn};
use serde::{Deserialize, Serialize};
use crate::{utils, DEFAULT_SESSION_TIMEOUT, SESSION_PATH};
#[derive(Serialize, Deserialize)]
pub(crate) struct Token {
tty_name: String,
tty_uuid: String,
timestamp: SystemTime,
final_timestamp: SystemTime,
}
impl Token {
pub(crate) fn new(tty_name: &str, tty_uuid: &str) -> Result<Self, Box<dyn Error>> {
debug!("Create the timestamp of the token");
let timestamp = SystemTime::now();
debug!("Create the final timestamp to determine the maximum validity of the session");
let duration = std::time::Duration::from_secs(DEFAULT_SESSION_TIMEOUT);
let final_timestamp = match timestamp.checked_add(duration) {
Some(time) => time,
None => return Err(From::from("Couldn't create final timestamp")),
};
Ok(Self {
tty_name: String::from(tty_name),
tty_uuid: String::from(tty_uuid),
timestamp,
final_timestamp,
})
}
pub(crate) fn create_token_file(&self, username: &str) -> Result<(), Box<dyn Error>> {
let token_path_string = format!("{}{}{}", SESSION_PATH, username, self.tty_name);
let token_path = Path::new(&token_path_string);
debug!(
"token_path has been created, will verify if it exists : {}",
token_path_string
);
if token_path.exists() {
debug!("token_path exist will be erased and replace");
fs::remove_file(token_path)?;
} else {
debug!("token_path doesn't exist, will create it");
let path = match token_path.parent() {
Some(path) => path,
None => return Err(From::from("Error in token_path! Unable to give the parent")),
};
let path_str = match path.to_str() {
Some(data) => data,
None => return Err(From::from("Couldn't convert a path to str!")),
};
debug!(
"Create directory: {} with mode 600 to restraint access",
path_str
);
DirBuilder::new().mode(0o600).recursive(true).create(path)?;
}
debug!("Put Token in a string");
let token_file = serde_yaml::to_string(&self)?;
utils::create_file(token_path, 0o600, &token_file)?;
Ok(())
}
pub(crate) fn verify_token(
&self,
tty_name: &str,
tty_uuid: &str,
) -> Result<(), Box<dyn Error>> {
let clock = SystemTime::now();
if self.final_timestamp <= clock {
debug!("Session has expired");
Err(From::from("Session has expired"))
} else if self.tty_name == tty_name
&& self.tty_uuid == tty_uuid
&& self.final_timestamp > clock
{
debug!("Session is valid, will reuse it");
Ok(())
} else {
debug!("Not the same session");
Err(From::from("Not the same session"))
}
}
}
pub(crate) fn create_dir_run(username: &str) -> Result<(), Box<dyn Error>> {
let run_path = Path::new(SESSION_PATH);
debug!("Verify that {} exist", SESSION_PATH);
if !run_path.exists() {
info!(
"{} doesn't exist, creating it with mode 600 to restraint access",
SESSION_PATH
);
DirBuilder::new()
.mode(0o600)
.recursive(true)
.create(SESSION_PATH)?;
}
let metadata = fs::metadata(SESSION_PATH)?;
let mut perms = metadata.permissions();
debug!("Verifying permission on {}", SESSION_PATH);
if perms.mode() != 0o600 {
warn!("Permissions are incorrect and will be adjusted to 600");
perms.set_mode(0o600);
fs::set_permissions(SESSION_PATH, perms)?;
}
let user_path_string = format!("{}{}/", SESSION_PATH, username);
let user_path = Path::new(&user_path_string);
debug!("Verifying that user_path exist: {}", user_path_string);
if !user_path.exists() {
info!(
"{} doesn't exist, creating it with mode 600 to restraint access",
user_path_string
);
DirBuilder::new()
.mode(0o600)
.recursive(true)
.create(user_path)?;
} else if user_path.is_file() {
let err = format!("Error: {} is not a directory", user_path_string);
error!("{}", err);
return Err(From::from(err));
} else {
let metadata = fs::metadata(user_path)?;
let mut perms = metadata.permissions();
debug!("Verifying {} permissions", user_path_string);
if perms.mode() != 0o600 {
warn!("Permissions are incorrect, adjusting it to 600 to restraint access");
perms.set_mode(0o600);
fs::set_permissions(user_path, perms)?;
}
}
Ok(())
}
pub(crate) fn read_token_file(token_path: &str) -> Result<Token, Box<dyn Error>> {
debug!(
"Open the file at {} and put it's content in a buffer",
token_path
);
let buffer = fs::read_to_string(token_path)?;
debug!("Transform the buffer to the token structure");
let token: Token = serde_yaml::from_str(&buffer)?;
Ok(token)
}
#[cfg(test)]
mod tests {
use super::{Error, Token, DEFAULT_SESSION_TIMEOUT};
#[test]
fn test_timestamp() -> Result<(), Box<dyn Error>> {
let token = Token::new("name", "1234")?;
let duration = std::time::Duration::from_secs(DEFAULT_SESSION_TIMEOUT);
if token.final_timestamp - duration == token.timestamp {
Ok(())
} else {
Err(From::from("Test failed: timestamp creation got wrong"))
}
}
}