mirror of
https://github.com/YGGverse/Yoda.git
synced 2025-01-15 17:20:08 +00:00
complete certificate export feature
This commit is contained in:
parent
8a62ef46df
commit
3dfcbc279d
@ -23,7 +23,7 @@ impl Gemini {
|
|||||||
/// Create new `Self` for given Profile
|
/// Create new `Self` for given Profile
|
||||||
pub fn new(profile: Rc<Profile>, action: Rc<Action>, auth_uri: Uri) -> Self {
|
pub fn new(profile: Rc<Profile>, action: Rc<Action>, auth_uri: Uri) -> Self {
|
||||||
// Init widget
|
// Init widget
|
||||||
let widget = Rc::new(Widget::new());
|
let widget = Rc::new(Widget::new(profile.clone()));
|
||||||
|
|
||||||
// Init shared components
|
// Init shared components
|
||||||
let auth_url = auth_uri.to_string();
|
let auth_url = auth_uri.to_string();
|
||||||
|
@ -4,6 +4,7 @@ pub mod form;
|
|||||||
use action::Action;
|
use action::Action;
|
||||||
use form::{list::item::value::Value, Form};
|
use form::{list::item::value::Value, Form};
|
||||||
|
|
||||||
|
use crate::profile::Profile;
|
||||||
use adw::{
|
use adw::{
|
||||||
prelude::{AdwDialogExt, AlertDialogExt, AlertDialogExtManual},
|
prelude::{AdwDialogExt, AlertDialogExt, AlertDialogExtManual},
|
||||||
AlertDialog, ResponseAppearance,
|
AlertDialog, ResponseAppearance,
|
||||||
@ -32,12 +33,12 @@ impl Widget {
|
|||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
/// Create new `Self`
|
/// Create new `Self`
|
||||||
pub fn new() -> Self {
|
pub fn new(profile: Rc<Profile>) -> Self {
|
||||||
// Init actions
|
// Init actions
|
||||||
let action = Rc::new(Action::new());
|
let action = Rc::new(Action::new());
|
||||||
|
|
||||||
// Init child container
|
// Init child container
|
||||||
let form = Rc::new(Form::new(action.clone()));
|
let form = Rc::new(Form::new(profile, action.clone()));
|
||||||
|
|
||||||
// Init main `GObject`
|
// Init main `GObject`
|
||||||
let gobject = AlertDialog::builder()
|
let gobject = AlertDialog::builder()
|
||||||
|
@ -9,6 +9,7 @@ use name::Name;
|
|||||||
use save::Save;
|
use save::Save;
|
||||||
|
|
||||||
use super::Action;
|
use super::Action;
|
||||||
|
use crate::profile::Profile;
|
||||||
use gtk::{prelude::BoxExt, Box, Orientation};
|
use gtk::{prelude::BoxExt, Box, Orientation};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
@ -17,7 +18,7 @@ pub struct Form {
|
|||||||
pub file: Rc<File>,
|
pub file: Rc<File>,
|
||||||
pub list: Rc<List>,
|
pub list: Rc<List>,
|
||||||
pub name: Rc<Name>,
|
pub name: Rc<Name>,
|
||||||
pub save: Rc<Save>,
|
// pub save: Rc<Save>,
|
||||||
pub gobject: Box,
|
pub gobject: Box,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,12 +26,12 @@ impl Form {
|
|||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
/// Create new `Self`
|
/// Create new `Self`
|
||||||
pub fn new(action: Rc<Action>) -> Self {
|
pub fn new(profile: Rc<Profile>, action: Rc<Action>) -> Self {
|
||||||
// Init components
|
// Init components
|
||||||
let file = Rc::new(File::new(action.clone()));
|
let file = Rc::new(File::new(action.clone()));
|
||||||
let list = Rc::new(List::new());
|
let list = Rc::new(List::new());
|
||||||
let name = Rc::new(Name::new(action.clone()));
|
let name = Rc::new(Name::new(action.clone()));
|
||||||
let save = Rc::new(Save::new(action.clone()));
|
let save = Rc::new(Save::new(profile));
|
||||||
|
|
||||||
// Init main container
|
// Init main container
|
||||||
let gobject = Box::builder().orientation(Orientation::Vertical).build();
|
let gobject = Box::builder().orientation(Orientation::Vertical).build();
|
||||||
@ -48,18 +49,18 @@ impl Form {
|
|||||||
let update = action.update.clone();
|
let update = action.update.clone();
|
||||||
move |item| {
|
move |item| {
|
||||||
// Change name entry visibility
|
// Change name entry visibility
|
||||||
name.show(matches!(item, Value::GenerateNewAuth));
|
name.update(matches!(item, Value::GenerateNewAuth));
|
||||||
|
|
||||||
// Change file choose button visibility
|
// Change file choose button visibility
|
||||||
file.show(matches!(item, Value::ImportPem));
|
file.update(matches!(item, Value::ImportPem));
|
||||||
|
|
||||||
// Change export button visibility, update holder @TODO
|
// Change export button visibility by update it holder value
|
||||||
match item {
|
match item {
|
||||||
Value::ProfileIdentityGeminiId(id) => {
|
Value::ProfileIdentityGeminiId(value) => {
|
||||||
// save.show(Some(id));
|
save.update(Some(value));
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// save.show(None);
|
save.update(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,7 +76,7 @@ impl Form {
|
|||||||
file,
|
file,
|
||||||
list,
|
list,
|
||||||
name,
|
name,
|
||||||
save,
|
// save,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ impl File {
|
|||||||
|
|
||||||
/// Change visibility status
|
/// Change visibility status
|
||||||
/// * grab focus on `is_visible`
|
/// * grab focus on `is_visible`
|
||||||
pub fn show(&self, is_visible: bool) {
|
pub fn update(&self, is_visible: bool) {
|
||||||
self.gobject.set_visible(is_visible);
|
self.gobject.set_visible(is_visible);
|
||||||
if is_visible {
|
if is_visible {
|
||||||
self.gobject.grab_focus();
|
self.gobject.grab_focus();
|
||||||
|
@ -39,7 +39,7 @@ impl Name {
|
|||||||
|
|
||||||
/// Change visibility status
|
/// Change visibility status
|
||||||
/// * grab focus on `is_visible`
|
/// * grab focus on `is_visible`
|
||||||
pub fn show(&self, is_visible: bool) {
|
pub fn update(&self, is_visible: bool) {
|
||||||
self.gobject.set_visible(is_visible);
|
self.gobject.set_visible(is_visible);
|
||||||
if is_visible {
|
if is_visible {
|
||||||
self.gobject.grab_focus();
|
self.gobject.grab_focus();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
mod certificate;
|
mod certificate;
|
||||||
use certificate::Certificate;
|
use certificate::Certificate;
|
||||||
|
|
||||||
use super::Action;
|
use crate::profile::Profile;
|
||||||
use gtk::{
|
use gtk::{
|
||||||
gio::{Cancellable, ListStore},
|
gio::{Cancellable, ListStore},
|
||||||
prelude::{ButtonExt, FileExt, WidgetExt},
|
prelude::{ButtonExt, FileExt, WidgetExt},
|
||||||
@ -13,7 +13,7 @@ const LABEL: &str = "Export to file..";
|
|||||||
const MARGIN: i32 = 8;
|
const MARGIN: i32 = 8;
|
||||||
|
|
||||||
pub struct Save {
|
pub struct Save {
|
||||||
certificate: Rc<RefCell<Option<Certificate>>>,
|
profile_identity_gemini_id: Rc<RefCell<Option<i64>>>,
|
||||||
pub gobject: Button,
|
pub gobject: Button,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,9 +21,9 @@ impl Save {
|
|||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
/// Create new `Self`
|
/// Create new `Self`
|
||||||
pub fn new(action: Rc<Action>) -> Self {
|
pub fn new(profile: Rc<Profile>) -> Self {
|
||||||
// Init `Certificate` holder
|
// Init selected option holder
|
||||||
let certificate = Rc::new(RefCell::new(None::<Certificate>));
|
let profile_identity_gemini_id = Rc::new(RefCell::new(None));
|
||||||
|
|
||||||
// Init `GObject`
|
// Init `GObject`
|
||||||
let gobject = Button::builder()
|
let gobject = Button::builder()
|
||||||
@ -34,19 +34,18 @@ impl Save {
|
|||||||
|
|
||||||
// Init events
|
// Init events
|
||||||
gobject.connect_clicked({
|
gobject.connect_clicked({
|
||||||
let certificate = certificate.clone();
|
let profile_identity_gemini_id = profile_identity_gemini_id.clone();
|
||||||
let gobject = gobject.clone();
|
let gobject = gobject.clone();
|
||||||
move |_| {
|
move |_| {
|
||||||
// Get certificate selected from holder
|
// Get selected identity from holder
|
||||||
match certificate.borrow().as_ref() {
|
match profile_identity_gemini_id.borrow().as_ref() {
|
||||||
Some(certificate) => {
|
Some(profile_identity_gemini_id) => {
|
||||||
// Copy certificate holder values
|
|
||||||
let name = certificate.name.clone();
|
|
||||||
let data = certificate.data.clone();
|
|
||||||
|
|
||||||
// Lock open button (prevent double click)
|
// Lock open button (prevent double click)
|
||||||
gobject.set_sensitive(false);
|
gobject.set_sensitive(false);
|
||||||
|
|
||||||
|
// Create PEM file based on option ID selected
|
||||||
|
match Certificate::new(profile.clone(), *profile_identity_gemini_id) {
|
||||||
|
Ok(certificate) => {
|
||||||
// Init file filters related with PEM extension
|
// Init file filters related with PEM extension
|
||||||
let filters = ListStore::new::<FileFilter>();
|
let filters = ListStore::new::<FileFilter>();
|
||||||
|
|
||||||
@ -64,7 +63,7 @@ impl Save {
|
|||||||
FileDialog::builder()
|
FileDialog::builder()
|
||||||
.default_filter(&filter_pem)
|
.default_filter(&filter_pem)
|
||||||
.filters(&filters)
|
.filters(&filters)
|
||||||
.initial_name(format!("{name}.pem"))
|
.initial_name(format!("{}.pem", certificate.name))
|
||||||
.build()
|
.build()
|
||||||
.save(None::<&Window>, None::<&Cancellable>, {
|
.save(None::<&Window>, None::<&Cancellable>, {
|
||||||
let gobject = gobject.clone();
|
let gobject = gobject.clone();
|
||||||
@ -73,37 +72,54 @@ impl Save {
|
|||||||
Ok(file) => match file.path() {
|
Ok(file) => match file.path() {
|
||||||
Some(path) => match File::create(&path) {
|
Some(path) => match File::create(&path) {
|
||||||
Ok(mut destination) => {
|
Ok(mut destination) => {
|
||||||
match destination.write_all(data.as_bytes()) {
|
match destination.write_all(
|
||||||
|
certificate.data.as_bytes(),
|
||||||
|
) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
// @TODO
|
gobject.set_css_classes(&[
|
||||||
gobject.set_css_classes(&["success"]);
|
"success",
|
||||||
|
]);
|
||||||
gobject.set_label(&format!(
|
gobject.set_label(&format!(
|
||||||
"Saved to {}",
|
"Saved to {}",
|
||||||
path.to_string_lossy()
|
path.to_string_lossy()
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
Err(reason) => {
|
Err(e) => {
|
||||||
|
gobject.set_css_classes(&[
|
||||||
|
"error",
|
||||||
|
]);
|
||||||
|
gobject
|
||||||
|
.set_label(&e.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
gobject.set_css_classes(&["error"]);
|
gobject.set_css_classes(&["error"]);
|
||||||
gobject.set_label(&reason.to_string())
|
gobject.set_label(&e.to_string())
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(reason) => {
|
|
||||||
gobject.set_css_classes(&["error"]);
|
|
||||||
gobject.set_label(&reason.to_string())
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => todo!(),
|
None => {
|
||||||
},
|
|
||||||
Err(reason) => {
|
|
||||||
gobject.set_css_classes(&["warning"]);
|
gobject.set_css_classes(&["warning"]);
|
||||||
gobject.set_label(reason.message())
|
gobject.set_label(
|
||||||
|
"Could not init destination path",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
gobject.set_css_classes(&["warning"]);
|
||||||
|
gobject.set_label(e.message())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gobject.set_sensitive(true); // unlock
|
gobject.set_sensitive(true); // unlock
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Err(e) => {
|
||||||
|
gobject.set_css_classes(&["error"]);
|
||||||
|
gobject.set_label(&e.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
None => todo!(), // unexpected
|
None => todo!(), // unexpected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -111,16 +127,25 @@ impl Save {
|
|||||||
|
|
||||||
// Return activated `Self`
|
// Return activated `Self`
|
||||||
Self {
|
Self {
|
||||||
certificate,
|
profile_identity_gemini_id,
|
||||||
gobject,
|
gobject,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
|
|
||||||
/// Change visibility status
|
/// Update `profile_identity_gemini_id` holder,
|
||||||
/// * grab focus on `is_visible`
|
/// toggle visibility depending on given value
|
||||||
pub fn show(&self, is_visible: bool) {
|
pub fn update(&self, profile_identity_gemini_id: Option<i64>) {
|
||||||
self.gobject.set_visible(is_visible)
|
self.gobject.set_visible(match profile_identity_gemini_id {
|
||||||
|
Some(value) => {
|
||||||
|
self.profile_identity_gemini_id.replace(Some(value));
|
||||||
|
true
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
self.profile_identity_gemini_id.replace(None);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,38 @@
|
|||||||
|
mod error;
|
||||||
|
pub use error::Error;
|
||||||
|
|
||||||
|
use crate::profile::Profile;
|
||||||
|
use gtk::{gio::TlsCertificate, prelude::TlsCertificateExt};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
/// Certificate details holder for export to file action
|
||||||
pub struct Certificate {
|
pub struct Certificate {
|
||||||
pub name: String,
|
|
||||||
pub data: String,
|
pub data: String,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Certificate {
|
||||||
|
// Constructors
|
||||||
|
|
||||||
|
/// Create new `Self`
|
||||||
|
pub fn new(profile: Rc<Profile>, profile_identity_gemini_id: i64) -> Result<Self, Error> {
|
||||||
|
match profile
|
||||||
|
.identity
|
||||||
|
.gemini
|
||||||
|
.database
|
||||||
|
.record(profile_identity_gemini_id)
|
||||||
|
{
|
||||||
|
Ok(record) => match record {
|
||||||
|
Some(identity) => match TlsCertificate::from_pem(&identity.pem) {
|
||||||
|
Ok(certificate) => Ok(Self {
|
||||||
|
data: identity.pem,
|
||||||
|
name: certificate.subject_name().unwrap().replace("CN=", ""),
|
||||||
|
}),
|
||||||
|
Err(reason) => Err(Error::TlsCertificate(reason)),
|
||||||
|
},
|
||||||
|
None => Err(Error::NotFound(profile_identity_gemini_id)),
|
||||||
|
},
|
||||||
|
Err(reason) => Err(Error::Database(reason)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
use gtk::glib;
|
||||||
|
use std::fmt::{Display, Formatter, Result};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
Database(sqlite::Error),
|
||||||
|
NotFound(i64),
|
||||||
|
TlsCertificate(glib::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||||
|
match self {
|
||||||
|
Self::Database(e) => {
|
||||||
|
write!(f, "Database error: {e}")
|
||||||
|
}
|
||||||
|
Self::NotFound(profile_identity_gemini_id) => {
|
||||||
|
write!(f, "Record for `{profile_identity_gemini_id}` not found")
|
||||||
|
}
|
||||||
|
Self::TlsCertificate(e) => {
|
||||||
|
write!(f, "TLS certificate error: {e}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -45,6 +45,21 @@ impl Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get single record match `id`
|
||||||
|
pub fn record(&self, id: i64) -> Result<Option<Table>, Error> {
|
||||||
|
let readable = self.connection.read().unwrap();
|
||||||
|
let tx = readable.unchecked_transaction()?;
|
||||||
|
let records = select(&tx, *self.profile_identity_id)?; // @TODO single record query
|
||||||
|
|
||||||
|
for record in records {
|
||||||
|
if record.id == id {
|
||||||
|
return Ok(Some(record));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
/// Get all records match current `profile_identity_id`
|
/// Get all records match current `profile_identity_id`
|
||||||
pub fn records(&self) -> Result<Vec<Table>, Error> {
|
pub fn records(&self) -> Result<Vec<Table>, Error> {
|
||||||
let readable = self.connection.read().unwrap(); // @TODO
|
let readable = self.connection.read().unwrap(); // @TODO
|
||||||
|
Loading…
x
Reference in New Issue
Block a user