gobjectify provider

This commit is contained in:
Bilal Elmoussaoui 2020-10-28 07:03:48 +01:00
parent 7db25ac562
commit 46d0902a36
18 changed files with 435 additions and 99 deletions

2
Cargo.lock generated
View file

@ -63,9 +63,9 @@ dependencies = [
name = "authenticator"
version = "0.1.0"
dependencies = [
"anyhow 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
"diesel 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"diesel_migrations 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"gdk-pixbuf 0.9.0 (git+https://github.com/gtk-rs/gdk-pixbuf)",
"gdk4 0.1.0 (git+https://github.com/gtk-rs/gdk4)",
"gettext-rs 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",

View file

@ -8,7 +8,7 @@ log = "0.4"
gettext-rs= { version = "0.5", features = ["gettext-system"] }
pretty_env_logger = "0.3"
lazy_static = "1.3"
failure = "0.1"
anyhow = "1.0"
diesel = { version = "1.4", features = ["sqlite", "r2d2"] }
diesel_migrations = { version = "1.4" , features = ["sqlite"] }
serde = "1.0"

View file

@ -149,6 +149,9 @@
<child>
<object class="HdyComboRow" id="algorithm_comborow">
<property name="title" translatable="yes">Algorithm</property>
<property name="expression">
<lookup type="HdyEnumValueObject" name="name"/>
</property>
</object>
</child>
<style>

View file

@ -1,6 +1,7 @@
-- Your SQL goes here
CREATE TABLE "accounts" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE,
"username" VARCHAR NOT NULL,
"token_id" VARCHAR NOT NULL,
"provider" INTEGER NOT NULL)
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE,
"username" VARCHAR NOT NULL,
"token_id" VARCHAR NOT NULL,
"provider_id" INTEGER NOT NULL
)

View file

@ -1,8 +1,10 @@
-- Your SQL goes here
CREATE TABLE "providers" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE,
"name" VARCHAR NOT NULL,
"website" VARCHAR NULL,
"help_url" VARCHAR NULL,
"image_uri" VARCHAR NULL
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE,
"name" VARCHAR NOT NULL,
"website" VARCHAR NULL,
"help_url" VARCHAR NULL,
"image_uri" VARCHAR NULL,
"period" INTEGER NULL DEFAULT 30,
"algorithm" VARCHAR DEFAULT "OTP"
);

0
src/helpers/qrcode.rs Normal file
View file

View file

@ -1,8 +1,6 @@
#[macro_use]
extern crate log;
#[macro_use]
extern crate failure;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate diesel_migrations;

View file

@ -1,9 +1,8 @@
use crate::schema::accounts;
use crate::models::database;
use crate::schema::accounts;
use anyhow::Result;
use diesel::RunQueryDsl;
pub use failure::Error;
use diesel::prelude::*;
@ -24,8 +23,8 @@ pub struct NewAccount {
}
impl database::Insert<Account> for NewAccount {
type Error = database::Error;
fn insert(&self) -> Result<Account, database::Error> {
type Error = anyhow::Error;
fn insert(&self) -> Result<Account> {
let db = database::connection();
let conn = db.get()?;

38
src/models/algorithm.rs Normal file
View file

@ -0,0 +1,38 @@
use glib::StaticType;
use std::str::FromStr;
use std::string::ToString;
#[derive(Debug, Eq, PartialEq, Clone, Copy, GEnum)]
#[repr(u32)]
#[genum(type_name = "ProviderAlgorithm")]
pub enum Algorithm {
#[genum(name = "OTP")]
OTP = 0,
#[genum(name = "HOTP")]
HOTP = 1,
#[genum(name = "Steam")]
Steam = 2,
}
impl FromStr for Algorithm {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_ref() {
"otp" => Ok(Self::OTP),
"hotp" => Ok(Self::HOTP),
"steam" => Ok(Self::Steam),
_ => anyhow::bail!("Unsupported algorithm"),
}
}
}
impl ToString for Algorithm {
fn to_string(&self) -> String {
match *self {
Algorithm::OTP => "otp",
Algorithm::HOTP => "hotp",
Algorithm::Steam => "steam",
}
.to_string()
}
}

View file

@ -1,9 +1,8 @@
use crate::models::{Account, Provider};
use anyhow::Result;
use diesel::prelude::*;
use diesel::r2d2;
use diesel::r2d2::ConnectionManager;
pub use failure::Error;
use std::path::PathBuf;
use std::{fs, fs::File};
@ -20,15 +19,15 @@ pub(crate) fn connection() -> Pool {
POOL.clone()
}
fn run_migration_on(connection: &SqliteConnection) -> Result<(), Error> {
fn run_migration_on(connection: &SqliteConnection) -> Result<()> {
info!("Running DB Migrations...");
embedded_migrations::run_with_output(connection, &mut std::io::stdout()).map_err(From::from)
}
fn init_pool() -> Result<Pool, Error> {
fn init_pool() -> Result<Pool> {
let db_path = &DB_PATH;
fs::create_dir_all(&db_path.to_str().unwrap())?;
let db_path = db_path.join("library.db");
let db_path = db_path.join("authenticator.db");
if !db_path.exists() {
File::create(&db_path.to_str().unwrap())?;
}
@ -49,29 +48,21 @@ pub trait Insert<T> {
fn insert(&self) -> Result<T, Self::Error>;
}
pub fn get_accounts_by_provider(provider_model: Provider) -> Result<Vec<Account>, Error> {
pub fn get_accounts_by_provider(provider_model: Provider) -> Result<Vec<Account>> {
use crate::schema::accounts::dsl::*;
let db = connection();
let conn = db.get()?;
accounts
.filter(provider.eq(provider_model.id))
.filter(provider.eq(provider_model.id()))
.load::<Account>(&conn)
.map_err(From::from)
}
pub fn get_accounts() -> Result<Vec<Account>, Error> {
pub fn get_accounts() -> Result<Vec<Account>> {
use crate::schema::accounts::dsl::*;
let db = connection();
let conn = db.get()?;
accounts.load::<Account>(&conn).map_err(From::from)
}
pub fn get_providers() -> Result<Vec<Provider>, Error> {
use crate::schema::providers::dsl::*;
let db = connection();
let conn = db.get()?;
providers.load::<Provider>(&conn).map_err(From::from)
}

View file

@ -1,5 +1,6 @@
mod account;
mod accounts;
mod algorithm;
pub mod database;
mod object_wrapper;
mod provider;
@ -7,6 +8,7 @@ mod providers;
pub use self::account::{Account, NewAccount};
pub use self::accounts::AccountsModel;
pub use self::algorithm::Algorithm;
pub use self::object_wrapper::ObjectWrapper;
pub use self::provider::{NewProvider, Provider};
pub use self::provider::Provider;
pub use self::providers::ProvidersModel;

View file

@ -1,43 +1,277 @@
use crate::schema::providers;
use super::algorithm::Algorithm;
use crate::models::database;
use anyhow::Result;
use diesel::RunQueryDsl;
pub use failure::Error;
use diesel::prelude::*;
use glib::subclass;
use glib::subclass::prelude::*;
use glib::translate::*;
use glib::Cast;
use glib::{StaticType, ToValue};
use std::cell::{Cell, RefCell};
use std::str::FromStr;
use std::string::ToString;
#[derive(Queryable, Hash, PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
pub struct Provider {
struct DiProvider {
pub id: i32,
pub name: String,
pub website: String,
pub help_url: String,
pub image_uri: String,
pub period: i32,
pub algorithm: String,
pub website: Option<String>,
pub help_url: Option<String>,
pub image_uri: Option<String>,
}
#[derive(Insertable)]
#[table_name = "providers"]
pub struct NewProvider {
pub name: String,
pub website: String,
pub help_url: String,
pub image_uri: String,
pub struct ProviderPriv {
pub id: Cell<i32>,
pub name: RefCell<String>,
pub period: Cell<i32>,
pub algorithm: RefCell<String>,
pub website: RefCell<Option<String>>,
pub help_url: RefCell<Option<String>>,
pub image_uri: RefCell<Option<String>>,
}
impl database::Insert<Provider> for NewProvider {
type Error = database::Error;
fn insert(&self) -> Result<Provider, database::Error> {
static PROPERTIES: [subclass::Property; 7] = [
subclass::Property("id", |name| {
glib::ParamSpec::int(name, "id", "Id", 0, 1000, 0, glib::ParamFlags::READWRITE)
}),
subclass::Property("name", |name| {
glib::ParamSpec::string(name, "name", "Name", None, glib::ParamFlags::READWRITE)
}),
subclass::Property("period", |name| {
glib::ParamSpec::int(
name,
"period",
"Period",
0,
1000,
30,
glib::ParamFlags::READWRITE,
)
}),
subclass::Property("algorithm", |name| {
glib::ParamSpec::string(
name,
"algorithm",
"Algorithm",
Some(&Algorithm::OTP.to_string()),
glib::ParamFlags::READWRITE,
)
}),
subclass::Property("website", |name| {
glib::ParamSpec::string(
name,
"website",
"Website",
None,
glib::ParamFlags::READWRITE,
)
}),
subclass::Property("help-url", |name| {
glib::ParamSpec::string(
name,
"help url",
"Help URL",
None,
glib::ParamFlags::READWRITE,
)
}),
subclass::Property("image-uri", |name| {
glib::ParamSpec::string(
name,
"image uri",
"Image URI",
None,
glib::ParamFlags::READWRITE,
)
}),
];
impl ObjectSubclass for ProviderPriv {
const NAME: &'static str = "Provider";
type ParentType = glib::Object;
type Instance = subclass::simple::InstanceStruct<Self>;
type Class = subclass::simple::ClassStruct<Self>;
glib_object_subclass!();
fn class_init(klass: &mut Self::Class) {
klass.install_properties(&PROPERTIES);
}
fn new() -> Self {
Self {
id: Cell::new(0),
name: RefCell::new("".to_string()),
website: RefCell::new(None),
help_url: RefCell::new(None),
image_uri: RefCell::new(None),
algorithm: RefCell::new(Algorithm::OTP.to_string()),
period: Cell::new(30),
}
}
}
impl ObjectImpl for ProviderPriv {
fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) {
let prop = &PROPERTIES[id];
match *prop {
subclass::Property("id", ..) => {
let id = value
.get()
.expect("type conformity checked by `Object::set_property`")
.unwrap();
self.id.replace(id);
}
subclass::Property("name", ..) => {
let name = value
.get()
.expect("type conformity checked by `Object::set_property`")
.unwrap();
self.name.replace(name);
}
subclass::Property("period", ..) => {
let period = value
.get_some()
.expect("type conformity checked by `Object::set_property`");
self.period.replace(period);
}
subclass::Property("algorithm", ..) => {
let algorithm = value
.get()
.expect("type conformity checked by `Object::set_property`")
.unwrap();
self.algorithm.replace(algorithm);
}
subclass::Property("website", ..) => {
let website = value
.get()
.expect("type conformity checked by `Object::set_property`");
self.website.replace(website);
}
subclass::Property("help-url", ..) => {
let help_url = value
.get()
.expect("type conformity checked by `Object::set_property`");
self.help_url.replace(help_url);
}
subclass::Property("image-uri", ..) => {
let image_uri = value
.get()
.expect("type conformity checked by `Object::set_property`");
self.image_uri.replace(image_uri);
}
_ => unimplemented!(),
}
}
fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
let prop = &PROPERTIES[id];
match *prop {
subclass::Property("id", ..) => Ok(self.id.get().to_value()),
subclass::Property("name", ..) => Ok(self.name.borrow().to_value()),
subclass::Property("period", ..) => Ok(self.period.get().to_value()),
subclass::Property("algorithm", ..) => Ok(self.algorithm.borrow().to_value()),
subclass::Property("website", ..) => Ok(self.website.borrow().to_value()),
subclass::Property("help-url", ..) => Ok(self.help_url.borrow().to_value()),
subclass::Property("image-uri", ..) => Ok(self.image_uri.borrow().to_value()),
_ => unimplemented!(),
}
}
}
glib_wrapper! {
pub struct Provider(Object<subclass::simple::InstanceStruct<ProviderPriv>, subclass::simple::ClassStruct<ProviderPriv>, ProviderClass>);
match fn {
get_type => || ProviderPriv::get_type().to_glib(),
}
}
impl Provider {
pub fn load() -> Result<Vec<Self>> {
use crate::schema::providers::dsl::*;
let db = database::connection();
let conn = db.get()?;
diesel::insert_into(providers::table)
.values(self)
.execute(&conn)?;
let results = providers
.load::<DiProvider>(&conn)?
.into_iter()
.map(|p| {
Self::new(
p.id,
&p.name,
p.website,
p.help_url,
p.image_uri,
p.period,
Algorithm::from_str(&p.algorithm).unwrap(),
)
})
.collect::<Vec<Provider>>();
Ok(results)
}
providers::table
.order(providers::columns::id.desc())
.first::<Provider>(&conn)
.map_err(From::from)
pub fn new(
id: i32,
name: &str,
website: Option<String>,
help_url: Option<String>,
image_uri: Option<String>,
period: i32,
algorithm: Algorithm,
) -> Provider {
glib::Object::new(
Provider::static_type(),
&[
("id", &id),
("name", &name),
("website", &website),
("help-url", &help_url),
("image-uri", &image_uri),
("period", &period),
("algorithm", &algorithm.to_string()),
],
)
.expect("Failed to create provider")
.downcast()
.expect("Created provider is of wrong type")
}
pub fn id(&self) -> i32 {
let priv_ = ProviderPriv::from_instance(self);
priv_.id.get()
}
pub fn name(&self) -> String {
let priv_ = ProviderPriv::from_instance(self);
priv_.name.borrow().clone()
}
pub fn period(&self) -> i32 {
let priv_ = ProviderPriv::from_instance(self);
priv_.period.get()
}
pub fn algorithm(&self) -> Algorithm {
let priv_ = ProviderPriv::from_instance(self);
Algorithm::from_str(&priv_.algorithm.borrow().clone()).unwrap()
}
pub fn website(&self) -> Option<String> {
let priv_ = ProviderPriv::from_instance(self);
priv_.website.borrow().clone()
}
pub fn help_url(&self) -> Option<String> {
let priv_ = ProviderPriv::from_instance(self);
priv_.help_url.borrow().clone()
}
pub fn image_uri(&self) -> Option<String> {
let priv_ = ProviderPriv::from_instance(self);
priv_.image_uri.borrow().clone()
}
}

View file

@ -1,36 +1,60 @@
use super::accounts::AccountsModel;
use super::database;
use super::provider::Provider;
use std::collections::HashMap;
use gio::prelude::*;
use glib::StaticType;
use gtk::prelude::*;
pub struct ProvidersModel {
pub model: HashMap<Provider, AccountsModel>,
pub model: gio::ListStore,
}
impl ProvidersModel {
pub fn new() -> Self {
let mut model = Self {
model: HashMap::new(),
let model = Self {
model: gio::ListStore::new(Provider::static_type()),
};
model.init();
model
}
pub fn add_provider(&mut self, provider: Provider) {
let accounts_model = AccountsModel::from_provider(&provider);
self.model.insert(provider, accounts_model);
pub fn find_by_id(&self, id: i32) -> Option<Provider> {
for pos in 0..self.count() {
let obj = self.model.get_object(pos).unwrap();
let provider = obj.downcast::<Provider>().unwrap();
if provider.id() == id {
return Some(provider);
}
}
None
}
pub fn get_count(&self) -> usize {
println!("{}", self.model.len());
self.model.len()
pub fn completion_model(&self) -> gtk::ListStore {
let store = gtk::ListStore::new(&[i32::static_type(), String::static_type()]);
for pos in 0..self.count() {
let obj = self.model.get_object(pos).unwrap();
let provider = obj.downcast_ref::<Provider>().unwrap();
store.set(
&store.append(),
&[0, 1],
&[&provider.id(), &provider.name()],
);
}
store
}
fn init(&mut self) {
pub fn add_provider(&self, provider: &Provider) {
//let accounts_model = AccountsModel::from_provider(&provider);
self.model.append(provider);
}
pub fn count(&self) -> u32 {
self.model.get_n_items()
}
fn init(&self) {
// fill in the providers from the database
let providers = database::get_providers().unwrap();
let providers = Provider::load().unwrap();
for provider in providers.into_iter() {
for provider in providers.iter() {
self.add_provider(provider);
}
}

View file

@ -11,8 +11,10 @@ table! {
providers (id) {
id -> Integer,
name -> Text,
website -> Text,
help_url -> Text,
image_uri -> Text,
period -> Integer,
algorithm -> Text,
website -> Nullable<Text>,
help_url -> Nullable<Text>,
image_uri -> Nullable<Text>,
}
}

View file

@ -1,15 +1,21 @@
use crate::application::Action;
use crate::models::database::{self, *};
use crate::models::{Account, NewAccount};
use crate::models::database::*;
use crate::models::{Account, Algorithm, NewAccount, Provider, ProvidersModel};
use anyhow::Result;
use gio::prelude::*;
use glib::Sender;
use glib::StaticType;
use glib::{signal::Inhibit, Sender};
use gtk::prelude::*;
use libhandy::ComboRowExt;
use std::cell::RefCell;
use std::rc::Rc;
pub struct AddAccountDialog {
pub widget: libhandy::Window,
builder: gtk::Builder,
sender: Sender<Action>,
model: Rc<ProvidersModel>,
selected_provider: Rc<RefCell<Option<Provider>>>,
}
impl AddAccountDialog {
@ -21,15 +27,17 @@ impl AddAccountDialog {
widget: add_dialog,
builder,
sender,
model: Rc::new(ProvidersModel::new()),
selected_provider: Rc::new(RefCell::new(None)),
});
add_account_dialog.setup_actions(add_account_dialog.clone());
add_account_dialog.setup_signals();
add_account_dialog.setup_widgets();
add_account_dialog.setup_widgets(add_account_dialog.clone());
add_account_dialog
}
fn add_account(&self, account: NewAccount) -> Result<Account, database::Error> {
fn add_account(&self, account: NewAccount) -> Result<Account> {
// TODO: add the account to the provider model.
account.insert()
}
@ -91,7 +99,7 @@ impl AddAccountDialog {
action!(
actions,
"sqcan-qr",
"scan-qr",
clone!(@strong self.sender as sender => move |_, _| {
// sender.send(Action::OpenAddAccountDialog).unwrap();
@ -100,16 +108,49 @@ impl AddAccountDialog {
self.widget.insert_action_group("add", Some(&actions));
}
fn setup_widgets(&self) {
// Fill the providers gtk::ListStore
/*get_widget!(self.builder, gtk::ListStore, providers_store);
if let Ok(providers) = database::get_providers() {
for provider in providers.iter() {
let values: [&dyn ToValue; 2] = [&provider.id, &provider.name];
providers_store.set(&providers_store.append(), &[0, 1], &values);
}
}*/
fn setup_widgets(&self, dialog: Rc<Self>) {
get_widget!(self.builder, gtk::EntryCompletion, provider_completion);
provider_completion.set_model(Some(&self.model.completion_model()));
get_widget!(self.builder, gtk::Entry, @token_entry)
.set_property_secondary_icon_sensitive(false);
get_widget!(self.builder, libhandy::ComboRow, algorithm_comborow);
let algoirthms_model = libhandy::EnumListModel::new(Algorithm::static_type());
algorithm_comborow.set_model(Some(&algoirthms_model));
provider_completion.connect_match_selected(
clone!(@strong dialog,
@strong self.model as model,
@strong self.builder as builder =>
move |completion, store, iter| {
let provider_id = store.get_value(iter, 0). get_some::<i32>().unwrap();
let provider = model.find_by_id(provider_id).unwrap();
get_widget!(builder, gtk::Entry, provider_website_entry);
if let Some(ref website) = provider.website() {
provider_website_entry.set_text(website);
}
get_widget!(builder, gtk::SpinButton, @period_spinbutton).set_value(provider.period() as f64);
//let selected_position = algorithms_model.position(provider.algorithm()).unwrap_or(0);
//get_widget!(builder, libhandy::ComboRow, @algorithm_comborow).set_selected(selected_position);
get_widget!(builder, gtk::Entry, @token_entry)
.set_property_secondary_icon_sensitive(provider.help_url().is_some());
dialog.selected_provider.replace(Some(provider));
Inhibit(false)
}));
get_widget!(self.builder, gtk::Entry, token_entry);
token_entry.connect_icon_press(clone!(@strong dialog => move |entry, pos| {
if pos == gtk::EntryIconPosition::Secondary {
if let Some(ref provider) = dialog.selected_provider.borrow().clone() {
gio::AppInfo::launch_default_for_uri(&provider.help_url().unwrap(), None::<&gio::AppLaunchContext>);
}
}
}));
get_widget!(self.builder, gtk::SpinButton, @period_spinbutton).set_value(30.0);
}
}

View file

@ -1,6 +1,6 @@
use gtk::prelude::*;
use glib::{signal::Inhibit, Sender};
use glib::Sender;
use crate::application::Action;
use crate::models::{Account, AccountsModel, ObjectWrapper, Provider};
@ -36,7 +36,7 @@ impl<'a> AccountsList<'a> {
fn init(&self) {
get_widget!(self.builder, gtk::Label, provider_name);
provider_name.set_text(&self.provider.name);
provider_name.set_text(&self.provider.name());
get_widget!(self.builder, gtk::ListBox, listbox);
listbox.bind_model(

View file

@ -32,7 +32,7 @@ impl ProvidersList {
fn init(&self) {
get_widget!(self.builder, gtk::Box, providers_container);
/*
for (provider, accounts_model) in &self.model.borrow().model {
if accounts_model.get_count() != 0 {
let accounts_list =
@ -40,5 +40,6 @@ impl ProvidersList {
providers_container.append(&accounts_list.widget);
}
}
*/
}
}

View file

@ -4,7 +4,7 @@ use crate::widgets::providers::ProvidersList;
use crate::window_state;
use gio::prelude::*;
use glib::{signal::Inhibit, Sender, WeakRef};
use glib::{signal::Inhibit, Sender};
use gtk::prelude::*;
use std::cell::RefCell;