Skip to content

Commit

Permalink
change API to use more mixin approach
Browse files Browse the repository at this point in the history
  • Loading branch information
jreidinger committed Jun 27, 2023
1 parent 9b25a65 commit 7e56e5f
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 67 deletions.
96 changes: 62 additions & 34 deletions rust/agama-dbus-server/src/questions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::collections::HashMap;

use crate::error::Error;
use log;
use agama_lib::{connection_to,questions::{self, GenericQuestion}};
use agama_lib::{connection_to,questions::{self, GenericQuestion, WithPassword}};
use anyhow::Context;
use zbus::{dbus_interface, fdo::ObjectManager, zvariant::ObjectPath, Connection};

Expand All @@ -16,6 +16,16 @@ impl GenericQuestionObject {
self.0.id
}

#[dbus_interface(property)]
pub fn class(&self) -> &str {
&self.0.class
}

#[dbus_interface(property)]
pub fn data(&self) -> HashMap<String, String> {
self.0.data.to_owned()
}

#[dbus_interface(property)]
pub fn text(&self) -> &str {
self.0.text.as_str()
Expand Down Expand Up @@ -45,10 +55,11 @@ impl GenericQuestionObject {
}
}

struct LuksQuestionObject(questions::LuksQuestion);
/// Mixin interface for questions that are base + contain question for password
struct WithPasswordObject(questions::WithPassword);

#[dbus_interface(name = "org.opensuse.Agama.Questions1.LuksActivation")]
impl LuksQuestionObject {
#[dbus_interface(name = "org.opensuse.Agama.Questions1.WithPassword")]
impl WithPasswordObject {
#[dbus_interface(property)]
pub fn password(&self) -> &str {
self.0.password.as_str()
Expand All @@ -58,22 +69,19 @@ impl LuksQuestionObject {
pub fn set_password(&mut self, value: &str) -> () {
self.0.password = value.to_string();
}

#[dbus_interface(property)]
pub fn attempt(&self) -> u8 {
self.0.attempt
}
}

/// Question types used to be able to properly remove object from dbus
enum QuestionType {
Generic,
Luks,
Base,
BaseWithPassword,
}

trait AnswerStrategy {
/// TODO: find way to be able to answer any type of question, not just generic
/// Provides answer for generic question
fn answer(&self, question: &GenericQuestion) -> Option<String>;
/// Provides answer and password for base question with password
fn answer_with_password(&self, question: &WithPassword) -> (Option<String>, Option<String>);
}

struct DefaultAnswers;
Expand All @@ -82,6 +90,10 @@ impl AnswerStrategy for DefaultAnswers {
fn answer(&self, question: &GenericQuestion) -> Option<String> {
return Some(question.default_option.clone())
}

fn answer_with_password(&self, question: &WithPassword) -> (Option<String>, Option<String>) {
return (Some(question.base.default_option.clone()), None)
}
}

pub struct Questions {
Expand All @@ -106,7 +118,6 @@ impl Questions {
let id = self.last_id;
self.last_id += 1; // TODO use some thread safety
let options = options.iter().map(|o| o.to_string()).collect();
// TODO: enforce default option and do not use array for it to avoid that unwrap
let mut question = questions::GenericQuestion::new(
id,
class.to_string(),
Expand All @@ -123,39 +134,43 @@ impl Questions {
.object_server()
.at(object_path.clone(), question_object)
.await?;
self.questions.insert(id, QuestionType::Generic);
self.questions.insert(id, QuestionType::Base);
Ok(object_path)
}

/// creates new specialized luks activation question without answer and password
async fn new_luks_activation(
async fn new_with_password(
&mut self,
device: &str,
label: &str,
size: &str,
attempt: u8,
data: HashMap<String, String>,
class: &str,
text: &str,
options: Vec<&str>,
default_option: &str,
data: HashMap<String, String>
) -> Result<ObjectPath, zbus::fdo::Error> {
let id = self.last_id;
self.last_id += 1; // TODO use some thread safety
let question = questions::LuksQuestion::new(
// TODO: share code better
let options = options.iter().map(|o| o.to_string()).collect();
let base = questions::GenericQuestion::new(
id,
"storage.encryption.activation".to_string(),
device.to_string(),
label.to_string(),
size.to_string(),
attempt,
data
class.to_string(),
text.to_string(),
options,
default_option.to_string(),
data,
);
let mut question = questions::WithPassword::new(
base
);
let object_path = ObjectPath::try_from(question.base.object_path()).unwrap();

let mut base_question = question.base.clone();
self.fill_answer(&mut base_question);
let base_question = question.base.clone();
self.fill_answer_with_password(&mut question);
let base_object = GenericQuestionObject(base_question);

self.connection
.object_server()
.at(object_path.clone(), LuksQuestionObject(question))
.at(object_path.clone(), WithPasswordObject(question))
.await?;
// NOTE: order here is important as each interface cause signal, so frontend should wait only for GenericQuestions
// which should be the last interface added
Expand All @@ -164,7 +179,7 @@ impl Questions {
.at(object_path.clone(), base_object)
.await?;

self.questions.insert(id, QuestionType::Luks);
self.questions.insert(id, QuestionType::BaseWithPassword);
Ok(object_path)
}

Expand All @@ -174,20 +189,20 @@ impl Questions {
let id: u32 = question.rsplit("/").next().unwrap().parse().unwrap();
let qtype = self.questions.get(&id).unwrap();
match qtype {
QuestionType::Generic => {
QuestionType::Base => {
self.connection
.object_server()
.remove::<GenericQuestionObject, _>(question.clone())
.await?;
}
QuestionType::Luks => {
QuestionType::BaseWithPassword => {
self.connection
.object_server()
.remove::<GenericQuestionObject, _>(question.clone())
.await?;
self.connection
.object_server()
.remove::<LuksQuestionObject, _>(question.clone())
.remove::<WithPasswordObject, _>(question.clone())
.await?;
}
};
Expand Down Expand Up @@ -224,6 +239,19 @@ impl Questions {
}
}
}

/// tries to provide answer to question using answer strategies
fn fill_answer_with_password(&self, question: &mut WithPassword) -> () {
for strategy in self.answer_strategies.iter() {
let (answer, password) = strategy.answer_with_password(question);
if let Some(password) = password {
question.password = password;
}
if let Some(answer) = answer {
question.base.answer = answer;
}
}
}
}

/// Starts questions dbus service together with Object manager
Expand Down
38 changes: 5 additions & 33 deletions rust/agama-lib/src/questions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,48 +41,20 @@ impl GenericQuestion {
}
}

/// Specialized question for Luks partition activation
/// Composition for questions which include password.
/// TODO: research a bit how ideally do mixins in rust
#[derive(Clone, Debug)]
pub struct LuksQuestion {
pub struct WithPassword {
/// Luks password. Empty means no password set.
pub password: String,
/// number of previous attempts to decrypt partition
pub attempt: u8,
/// rest of question data that is same as for other questions
pub base: GenericQuestion,
}

impl LuksQuestion {
fn device_info(device: &str, label: &str, size: &str) -> String {
let mut result = device.to_string();
if !label.is_empty() {
result = format!("{} {}", result, label);
}

if !size.is_empty() {
result = format!("{} ({})", result, size);
}

result
}

pub fn new(id: u32, class: String, device: String, label: String, size: String, attempt: u8, data: HashMap<String, String>) -> Self {
let msg = format!(
"The device {} is encrypted.",
Self::device_info(device.as_str(), label.as_str(), size.as_str())
);
let base = GenericQuestion::new(
id,
class,
msg,
vec!["skip".to_string(), "decrypt".to_string()],
"skip".to_string(),
data,
);

impl WithPassword {
pub fn new(base: GenericQuestion) -> Self {
Self {
password: "".to_string(),
attempt,
base,
}
}
Expand Down

0 comments on commit 7e56e5f

Please sign in to comment.