diff --git a/fr.oupson.FooTerm.json b/fr.oupson.FooTerm.json index b4c7360..daa976b 100644 --- a/fr.oupson.FooTerm.json +++ b/fr.oupson.FooTerm.json @@ -12,7 +12,8 @@ "--share=ipc", "--socket=fallback-x11", "--device=dri", - "--socket=wayland" + "--socket=wayland", + "--talk-name=org.freedesktop.secrets" ], "build-options" : { "append-path" : "/usr/lib/sdk/vala/bin", diff --git a/src/model/Server.vala b/src/model/Server.vala index dd39581..d5776ed 100644 --- a/src/model/Server.vala +++ b/src/model/Server.vala @@ -20,12 +20,14 @@ namespace Footerm.Model { public class Server { + public int? id; public string name; public string hostname; public ushort port; public string username; - public Server(string name, string hostname, ushort port, string username) { + public Server(int? id, string name, string hostname, ushort port, string username) { + this.id = id; this.name = name; this.hostname = hostname; this.port = port; diff --git a/src/newpane.vala b/src/newpane.vala index 14719a6..275ac24 100644 --- a/src/newpane.vala +++ b/src/newpane.vala @@ -33,27 +33,38 @@ namespace Footerm { [GtkChild] private unowned Gtk.Button newpane_add_button; - public signal void on_server_selected(Footerm.Model.Server server); + public signal void on_server_selected (Footerm.Model.Server server); construct { - this.new_server.on_new_server.connect((s) => { - this.newpane_stack.set_visible_child(server_list.get_parent()); - var action_row = new Adw.ActionRow(); - action_row.set_title(s.hostname); - action_row.set_activatable(true); - action_row.activated.connect(() => { - this.on_server_selected(s); + this.new_server.on_new_server.connect ((s) => { + this.newpane_stack.set_visible_child (server_list.get_parent ()); + var action_row = new Adw.ActionRow (); + action_row.set_title (s.hostname); + action_row.set_activatable (true); + action_row.activated.connect (() => { + this.on_server_selected (s); }); - server_list.add(action_row); + server_list.add (action_row); }); this.newpane_add_button.clicked.connect (() => { - this.newpane_stack.set_visible_child(new_server.get_parent()); + this.newpane_stack.set_visible_child (new_server.get_parent ()); }); try { - var config = Footerm.Services.Config.get_instance(); + var config = Footerm.Services.Config.get_instance (); + + var stored_server_list = config.get_server_list (); + foreach (var server in stored_server_list) { + var action_row = new Adw.ActionRow (); + action_row.set_title (server.name); + action_row.set_activatable (true); + action_row.activated.connect (() => { + this.on_server_selected (server); + }); + server_list.add (action_row); + } } catch (Error e) { - GLib.warning("Failed to read server list : %s", e.message); + GLib.warning ("Failed to read server list : %s", e.message); } } } diff --git a/src/newserver.ui b/src/newserver.ui index 0a12c76..aece224 100644 --- a/src/newserver.ui +++ b/src/newserver.ui @@ -9,6 +9,11 @@ vertical + + + Name + + Hostname diff --git a/src/newserver.vala b/src/newserver.vala index c410d68..ec20e3a 100644 --- a/src/newserver.vala +++ b/src/newserver.vala @@ -22,6 +22,8 @@ namespace Footerm { [GtkTemplate(ui = "/fr/oupson/FooTerm/newserver.ui")] public class NewServer : Gtk.Box { public signal void on_new_server(Footerm.Model.Server server); + [GtkChild] + private unowned Adw.EntryRow name_entry; [GtkChild] private unowned Adw.EntryRow hostname_entry; @@ -42,7 +44,8 @@ namespace Footerm { add_server_button.clicked.connect(this.on_add_button_clicked); } - private void on_add_button_clicked() { + private async void on_add_button_clicked() { + var name = this.name_entry.get_text(); var hostname = this.hostname_entry.get_text(); var port = int.parse(this.port_entry.get_text()); var username = this.username_entry.get_text(); @@ -52,7 +55,11 @@ namespace Footerm { // Port is invalid } - // this.on_new_server(new Footerm.Model.Server(hostname, (ushort)port, username, password)); + var server = new Footerm.Model.Server(null, name, hostname, (ushort)port, username); + var config_service = Footerm.Services.Config.get_instance(); + yield config_service.save_server(server, password); + + this.on_new_server(server); } } } diff --git a/src/services/Config.vala b/src/services/Config.vala index 37a8b30..890e4a1 100644 --- a/src/services/Config.vala +++ b/src/services/Config.vala @@ -111,7 +111,7 @@ namespace Footerm.Services { public List get_server_list() throws ConfigError { List list = new List (); - var stm_str = "SELECT (serverId, serverName, serverHostName, serverPort) FROM SERVER"; + var stm_str = "SELECT serverId, serverName, serverHostName, serverPort, serverUsername FROM SERVER;"; Sqlite.Statement stm; var ec = this.db.prepare_v2(stm_str, stm_str.length, out stm); if (ec != Sqlite.OK) { @@ -119,10 +119,48 @@ namespace Footerm.Services { } while (stm.step() == Sqlite.ROW) { - list.append(new Footerm.Model.Server(stm.column_text(0), stm.column_text(1), (ushort) stm.column_int(2), stm.column_text(3))); + list.append(new Footerm.Model.Server(stm.column_int(0), stm.column_text(1), stm.column_text(2), (ushort) stm.column_int(3), stm.column_text(4))); } return list; } + + public async void save_server(Footerm.Model.Server server, string password) throws ConfigError, SecretError, Error { + var secrets = Secrets.get_instance(); + yield secrets.store_password(server, password); + + var stm_str = "INSERT INTO SERVER (serverName, serverHostName, serverPort, serverUsername, serverAuthentificationType) VALUES(?, ?, ?, ?, 'password')"; + Sqlite.Statement stm; + var ec = this.db.prepare_v2(stm_str, stm_str.length, out stm); + if (ec != Sqlite.OK) { + throw new ConfigError.DATABASE(@"Can't insert server: $(db.errcode ()): $(db.errmsg ())"); + } + + ec = stm.bind_text(1, server.name); + if (ec != Sqlite.OK) { + throw new ConfigError.DATABASE(@"Can't insert server: $(db.errcode ()): $(db.errmsg ())"); + } + ec = stm.bind_text(2, server.hostname); + if (ec != Sqlite.OK) { + throw new ConfigError.DATABASE(@"Can't insert server: $(db.errcode ()): $(db.errmsg ())"); + } + ec = stm.bind_int(3, server.port); + if (ec != Sqlite.OK) { + throw new ConfigError.DATABASE(@"Can't insert server: $(db.errcode ()): $(db.errmsg ())"); + } + ec = stm.bind_text(4, server.username); + if (ec != Sqlite.OK) { + throw new ConfigError.DATABASE(@"Can't insert server: $(db.errcode ()): $(db.errmsg ())"); + } + + ec = stm.step(); + stm.reset(); + + if (ec != Sqlite.DONE) { + throw new ConfigError.DATABASE(@"Can't insert server: $(db.errcode ()): $(db.errmsg ())"); + } + + server.id = (int)this.db.last_insert_rowid(); + } } } diff --git a/src/services/Secrets.vala b/src/services/Secrets.vala new file mode 100644 index 0000000..6fb308c --- /dev/null +++ b/src/services/Secrets.vala @@ -0,0 +1,68 @@ +/* Secrets.vala + * + * Copyright 2023 oupson + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +namespace Footerm.Services { + public errordomain SecretError { + FAILED_TO_STORE + } + + public class Secrets { + private static Secrets instance = null; + + public static Secrets get_instance() { + if (Secrets.instance == null) { + Secrets.instance = new Secrets(); + } + return Secrets.instance; + } + + private Secret.Schema password_schema; + + private Secrets() { + this.password_schema = new Secret.Schema("fr.oupson.FooTerm.Password", Secret.SchemaFlags.NONE, + "hostname", Secret.SchemaAttributeType.STRING, + "port", Secret.SchemaAttributeType.INTEGER, + "username", Secret.SchemaAttributeType.STRING); + } + + public async void store_password(Footerm.Model.Server server, string password) throws SecretError, Error { + var attributes = new GLib.HashTable (str_hash, str_equal); + attributes["hostname"] = server.hostname; + attributes["port"] = server.port.to_string(); + attributes["username"] = server.username; + + var res = yield Secret.password_storev(this.password_schema, attributes, Secret.COLLECTION_DEFAULT, @"$(server.username)@$(server.hostname):$(server.port)", password, null); + + if (!res) { + throw new SecretError.FAILED_TO_STORE("Failed to store the password"); + } + debug("Password is stored"); + } + + public async string get_password(Footerm.Model.Server server) throws Error { + var attributes = new GLib.HashTable (str_hash, str_equal); + attributes["hostname"] = server.hostname; + attributes["port"] = server.port.to_string(); + attributes["username"] = server.username; + + return yield Secret.password_lookupv(this.password_schema, attributes, null); + } + } +} diff --git a/src/services/meson.build b/src/services/meson.build index 5daf3f6..0238a74 100644 --- a/src/services/meson.build +++ b/src/services/meson.build @@ -1,3 +1,4 @@ footerm_sources += files( - 'Config.vala' + 'Config.vala', + 'Secrets.vala' ) \ No newline at end of file diff --git a/src/terminalpane.vala b/src/terminalpane.vala index 1ce330c..a9287ef 100644 --- a/src/terminalpane.vala +++ b/src/terminalpane.vala @@ -34,7 +34,13 @@ namespace Footerm { public TerminalPane(Footerm.Model.Server server) { this.server = server; this.terminal.set_enable_sixel(true); - this.connect_to_server(); + this.connect_to_server.begin((obj, res) => { + try { + this.connect_to_server.end(res); + } catch (Error e) { + warning("Failed to connect to the server : %s", e.message); + } + }); this.terminal.char_size_changed.connect(() => { int rows = 0; int columns = 0; @@ -51,7 +57,7 @@ namespace Footerm { // stdout.printf("all done!\n");) } - private void connect_to_server() throws GLib.IOError, GLib.Error { + private async void connect_to_server() throws GLib.IOError, GLib.Error { var addrs = new NetworkAddress(this.server.hostname, this.server.port); var addr = addrs.enumerate().next(); this.socket = new Socket(addr.get_family(), SocketType.STREAM, SocketProtocol.TCP); @@ -73,7 +79,10 @@ namespace Footerm { stdout.printf("\n"); // TODO QUERY PASSWORD FROM libsecret - if (session.auth_password(this.server.username, null) != SSH2.Error.NONE) { + var secrets = Footerm.Services.Secrets.get_instance(); + var password = yield secrets.get_password(this.server); + + if (session.auth_password(this.server.username, password) != SSH2.Error.NONE) { stdout.printf("\tAuthentication by password failed!\n"); session.disconnect("Normal Shutdown, Thank you for playing"); session = null; diff --git a/src/window.vala b/src/window.vala index 9c863b5..b8836a1 100644 --- a/src/window.vala +++ b/src/window.vala @@ -29,14 +29,14 @@ namespace Footerm { } construct { - var action = new SimpleAction("new_tab", null); + var action = new SimpleAction ("new_tab", null); action.activate.connect (() => { - var a = view.append (new Footerm.Pane()); + var a = view.append (new Footerm.Pane ()); a.set_title ("New Pane"); view.set_selected_page (a); }); this.add_action (action); - var a = view.append (new Footerm.Pane()); + var a = view.append (new Footerm.Pane ()); a.set_title ("New Pane"); } }