Refactored the username mask concept behind a struct.

This commit is contained in:
Antoine POPINEAU 2023-11-08 15:56:16 +01:00
parent 571677dec6
commit 6c1e01840d
No known key found for this signature in database
GPG Key ID: E8379674E92D25D2
9 changed files with 103 additions and 40 deletions

View File

@ -27,7 +27,7 @@ use crate::{
},
power::PowerOption,
ui::{
common::menu::Menu,
common::{masked::MaskedString, menu::Menu},
power::Power,
sessions::{Session, SessionSource, SessionType},
users::User,
@ -124,10 +124,8 @@ pub struct Greeter {
pub user_menu: bool,
// Menu for user selection.
pub users: Menu<User>,
// Current username.
pub username: String,
// Value to display in place of the username (e.g. full name of the user).
pub username_mask: Option<String>,
// Current username. Masked to display the full name if available.
pub username: MaskedString,
// Prompt that should be displayed to ask for entry.
pub prompt: Option<String>,
@ -194,8 +192,7 @@ impl Greeter {
// If we should remember the last logged-in user.
if greeter.remember {
if let Some(username) = get_last_user_username() {
greeter.username = username.clone();
greeter.username_mask = get_last_user_name();
greeter.username = MaskedString::from(username.clone(), get_last_user_name());
// If, on top of that, we should remember their last session.
if greeter.remember_user_session {
@ -237,7 +234,6 @@ impl Greeter {
if !soft {
self.username.zeroize();
self.username_mask.zeroize();
}
if scrub_message {

View File

@ -13,6 +13,7 @@ use nix::sys::utsname;
use crate::{
ui::{
common::masked::MaskedString,
sessions::{Session, SessionType},
users::User,
},
@ -101,10 +102,10 @@ pub fn get_last_user_name() -> Option<String> {
}
}
pub fn write_last_username(username: &str, name: Option<&str>) {
let _ = fs::write(LAST_USER_USERNAME, username);
pub fn write_last_username(username: &MaskedString) {
let _ = fs::write(LAST_USER_USERNAME, username.value.clone());
if let Some(name) = name {
if let Some(ref name) = username.mask {
let _ = fs::write(LAST_USER_NAME, name);
} else {
let _ = fs::remove_file(LAST_USER_NAME);

View File

@ -104,19 +104,19 @@ impl Ipc {
Response::Success => {
if greeter.done {
if greeter.remember {
write_last_username(&greeter.username, greeter.username_mask.as_deref());
write_last_username(&greeter.username);
if greeter.remember_user_session {
match greeter.session_source {
SessionSource::Command(ref command) => {
write_last_user_session(&greeter.username, command);
delete_last_user_session_path(&greeter.username);
write_last_user_session(&greeter.username.value, command);
delete_last_user_session_path(&greeter.username.value);
}
SessionSource::Session(index) => {
if let Some(Session { path: Some(session_path), .. }) = greeter.sessions.options.get(index) {
write_last_user_session_path(&greeter.username, session_path);
delete_last_user_session(&greeter.username);
write_last_user_session_path(&greeter.username.value, session_path);
delete_last_user_session(&greeter.username.value);
}
}
@ -161,7 +161,11 @@ impl Ipc {
match error_type {
ErrorType::AuthError => {
greeter.message = Some(fl!("failed"));
self.send(Request::CreateSession { username: greeter.username.clone() }).await;
self
.send(Request::CreateSession {
username: greeter.username.value.clone(),
})
.await;
greeter.reset(true).await;
}

View File

@ -9,6 +9,7 @@ use crate::{
ipc::Ipc,
power::power,
ui::{
common::masked::MaskedString,
sessions::{Session, SessionSource},
users::User,
},
@ -31,7 +32,7 @@ pub async fn handle(greeter: Arc<RwLock<Greeter>>, input: KeyEvent, ipc: Ipc) ->
modifiers: KeyModifiers::CONTROL,
..
} => match greeter.mode {
Mode::Username => greeter.username = String::new(),
Mode::Username => greeter.username = MaskedString::default(),
Mode::Password => greeter.buffer = String::new(),
Mode::Command => greeter.buffer = String::new(),
_ => {}
@ -163,7 +164,7 @@ pub async fn handle(greeter: Arc<RwLock<Greeter>>, input: KeyEvent, ipc: Ipc) ->
} => {
let value = {
match greeter.mode {
Mode::Username => &greeter.username,
Mode::Username => &greeter.username.value,
_ => &greeter.buffer,
}
};
@ -180,13 +181,13 @@ pub async fn handle(greeter: Arc<RwLock<Greeter>>, input: KeyEvent, ipc: Ipc) ->
// Tab should validate the username entry (same as Enter).
KeyEvent { code: KeyCode::Tab, .. } => match greeter.mode {
Mode::Username if !greeter.username.is_empty() => validate_username(&mut greeter, &ipc).await,
Mode::Username if !greeter.username.value.is_empty() => validate_username(&mut greeter, &ipc).await,
_ => {}
},
// Enter validates the current entry, depending on the active mode.
KeyEvent { code: KeyCode::Enter, .. } => match greeter.mode {
Mode::Username if !greeter.username.is_empty() => validate_username(&mut greeter, &ipc).await,
Mode::Username if !greeter.username.value.is_empty() => validate_username(&mut greeter, &ipc).await,
Mode::Username if greeter.user_menu => {
greeter.previous_mode = match greeter.mode {
@ -230,8 +231,7 @@ pub async fn handle(greeter: Arc<RwLock<Greeter>>, input: KeyEvent, ipc: Ipc) ->
let username = greeter.users.options.get(greeter.users.selected).cloned();
if let Some(User { username, name }) = username {
greeter.username = username;
greeter.username_mask = name;
greeter.username = MaskedString::from(username, name);
}
validate_username(&mut greeter, &ipc).await;
@ -287,7 +287,7 @@ pub async fn handle(greeter: Arc<RwLock<Greeter>>, input: KeyEvent, ipc: Ipc) ->
// current mode and the position of the cursor.
async fn insert_key(greeter: &mut Greeter, c: char) {
let value = match greeter.mode {
Mode::Username => &greeter.username,
Mode::Username => &greeter.username.value,
Mode::Password => &greeter.buffer,
Mode::Command => &greeter.buffer,
Mode::Users | Mode::Sessions | Mode::Power | Mode::Processing => return,
@ -301,7 +301,7 @@ async fn insert_key(greeter: &mut Greeter, c: char) {
let mode = greeter.mode;
match mode {
Mode::Username => greeter.username = value,
Mode::Username => greeter.username.value = value,
Mode::Password => greeter.buffer = value,
Mode::Command => greeter.buffer = value,
_ => {}
@ -313,7 +313,7 @@ async fn insert_key(greeter: &mut Greeter, c: char) {
// of the cursor.
async fn delete_key(greeter: &mut Greeter, key: KeyCode) {
let value = match greeter.mode {
Mode::Username => &greeter.username,
Mode::Username => &greeter.username.value,
Mode::Password => &greeter.buffer,
Mode::Command => &greeter.buffer,
Mode::Users | Mode::Sessions | Mode::Power | Mode::Processing => return,
@ -332,7 +332,7 @@ async fn delete_key(greeter: &mut Greeter, key: KeyCode) {
let value = left.chain(right).collect();
match greeter.mode {
Mode::Username => greeter.username = value,
Mode::Username => greeter.username.value = value,
Mode::Password => greeter.buffer = value,
Mode::Command => greeter.buffer = value,
Mode::Users | Mode::Sessions | Mode::Power | Mode::Processing => return,
@ -349,18 +349,22 @@ async fn validate_username(greeter: &mut Greeter, ipc: &Ipc) {
greeter.working = true;
greeter.message = None;
ipc.send(Request::CreateSession { username: greeter.username.clone() }).await;
ipc
.send(Request::CreateSession {
username: greeter.username.value.clone(),
})
.await;
greeter.buffer = String::new();
if greeter.remember_user_session {
if let Ok(last_session) = get_last_user_session_path(&greeter.username) {
if let Ok(last_session) = get_last_user_session_path(&greeter.username.value) {
if let Some(last_session) = Session::from_path(greeter, last_session).cloned() {
greeter.sessions.selected = greeter.sessions.options.iter().position(|sess| sess.path == last_session.path).unwrap_or(0);
greeter.session_source = SessionSource::Session(greeter.sessions.selected);
}
}
if let Ok(command) = get_last_user_session(&greeter.username) {
if let Ok(command) = get_last_user_session(&greeter.username.value) {
greeter.session_source = SessionSource::Command(command);
}
}
@ -374,7 +378,11 @@ mod test {
use tokio::sync::RwLock;
use super::handle;
use crate::{ipc::Ipc, ui::sessions::SessionSource, Greeter, Mode};
use crate::{
ipc::Ipc,
ui::{common::masked::MaskedString, sessions::SessionSource},
Greeter, Mode,
};
#[tokio::test]
async fn ctrl_u() {
@ -383,7 +391,7 @@ mod test {
{
let mut greeter = greeter.write().await;
greeter.mode = Mode::Username;
greeter.username = "apognu".to_string();
greeter.username = MaskedString::from("apognu".to_string(), None);
}
let result = handle(greeter.clone(), KeyEvent::new(KeyCode::Char('u'), KeyModifiers::CONTROL), Ipc::new()).await;
@ -392,7 +400,7 @@ mod test {
let status = greeter.read().await;
assert!(matches!(result, Ok(_)));
assert_eq!(status.username, "".to_string());
assert_eq!(status.username.value, "".to_string());
}
{

View File

@ -54,8 +54,12 @@ async fn run() -> Result<(), Box<dyn Error>> {
let mut events = Events::new().await;
let ipc = Ipc::new();
if greeter.remember && !greeter.username.is_empty() {
ipc.send(Request::CreateSession { username: greeter.username.clone() }).await;
if greeter.remember && !greeter.username.value.is_empty() {
ipc
.send(Request::CreateSession {
username: greeter.username.value.clone(),
})
.await;
}
let greeter = Arc::new(RwLock::new(greeter));

49
src/ui/common/masked.rs Normal file
View File

@ -0,0 +1,49 @@
use zeroize::Zeroize;
#[derive(Default)]
pub struct MaskedString {
pub value: String,
pub mask: Option<String>,
}
impl MaskedString {
pub fn from(value: String, mask: Option<String>) -> MaskedString {
MaskedString { value, mask }
}
pub fn get(&self) -> &str {
match self.mask {
Some(ref mask) => mask,
None => &self.value,
}
}
pub fn zeroize(&mut self) {
self.value.zeroize();
if let Some(ref mut mask) = self.mask {
mask.zeroize();
}
self.mask = None;
}
}
#[cfg(test)]
mod tests {
use super::MaskedString;
#[test]
fn get_value_when_unmasked() {
let masked = MaskedString::from("value".to_string(), None);
assert_eq!(masked.get(), "value");
}
#[test]
fn get_mask_when_masked() {
let masked = MaskedString::from("value".to_string(), Some("mask".to_string()));
assert_eq!(masked.get(), "mask");
}
}

View File

@ -1 +1,2 @@
pub mod masked;
pub mod menu;

View File

@ -53,7 +53,7 @@ pub fn draw(greeter: &mut Greeter, f: &mut Frame) -> Result<(u16, u16), Box<dyn
f.render_widget(greeting_label, chunks[GREETING_INDEX]);
}
let username_label = if greeter.user_menu && greeter.username.is_empty() {
let username_label = if greeter.user_menu && greeter.username.value.is_empty() {
let prompt_text = Span::from(fl!("select_user"));
Paragraph::new(prompt_text).alignment(Alignment::Center)
@ -63,7 +63,7 @@ pub fn draw(greeter: &mut Greeter, f: &mut Frame) -> Result<(u16, u16), Box<dyn
Paragraph::new(username_text)
};
let username = greeter.username_mask.as_deref().unwrap_or_else(|| greeter.username.as_ref());
let username = greeter.username.get();
let username_value_text = Span::from(username);
let username_value = Paragraph::new(username_value_text);
@ -71,7 +71,7 @@ pub fn draw(greeter: &mut Greeter, f: &mut Frame) -> Result<(u16, u16), Box<dyn
Mode::Username | Mode::Password => {
f.render_widget(username_label, chunks[USERNAME_INDEX]);
if !greeter.user_menu || !greeter.username.is_empty() {
if !greeter.user_menu || !greeter.username.value.is_empty() {
f.render_widget(
username_value,
Rect::new(
@ -124,7 +124,7 @@ pub fn draw(greeter: &mut Greeter, f: &mut Frame) -> Result<(u16, u16), Box<dyn
match greeter.mode {
Mode::Username => {
let username_length = greeter.username.chars().count();
let username_length = greeter.username.get().chars().count();
let offset = get_cursor_offset(greeter, username_length);
Ok((2 + cursor.x + fl!("username").chars().count() as u16 + offset as u16, USERNAME_INDEX as u16 + cursor.y))

View File

@ -12,7 +12,7 @@ pub fn titleize(message: &str) -> String {
pub fn should_hide_cursor(greeter: &Greeter) -> bool {
greeter.working
|| greeter.done
|| (greeter.user_menu && greeter.mode == Mode::Username && greeter.username.is_empty())
|| (greeter.user_menu && greeter.mode == Mode::Username && greeter.username.value.is_empty())
|| (greeter.mode == Mode::Password && greeter.prompt.is_none())
|| greeter.mode == Mode::Users
|| greeter.mode == Mode::Sessions