-
Notifications
You must be signed in to change notification settings - Fork 60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement secret storage using libsecret #206
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,7 @@ | |
|
||
#include "commands/auth-commands.hpp" | ||
#include "util/i18n.hpp" | ||
#include "util/secrets.hpp" | ||
|
||
#include <libinfinity/common/inf-xmpp-connection.h> | ||
#include <libinfinity/common/inf-error.h> | ||
|
@@ -134,6 +135,21 @@ void Gobby::AuthCommands::set_sasl_error(InfXmppConnection* connection, | |
g_error_free(error); | ||
} | ||
|
||
namespace { | ||
|
||
Glib::ustring get_remote_hostname(InfXmppConnection* xmpp) | ||
{ | ||
gchar* remote_id; | ||
g_object_get(G_OBJECT(xmpp), | ||
"remote-hostname", &remote_id, | ||
NULL); | ||
Glib::ustring remote_id_(remote_id); | ||
g_free(remote_id); | ||
return remote_id_; | ||
}; | ||
|
||
} // namespace | ||
|
||
void Gobby::AuthCommands::sasl_callback(InfSaslContextSession* session, | ||
InfXmppConnection* xmpp, | ||
Gsasl_property prop) | ||
|
@@ -172,21 +188,35 @@ void Gobby::AuthCommands::sasl_callback(InfSaslContextSession* session, | |
inf_sasl_context_session_continue(session, | ||
GSASL_OK); | ||
} | ||
else if (!info.attempted_fetch_from_secret_store) | ||
{ | ||
Glib::ustring remote_id = get_remote_hostname(xmpp); | ||
info.attempted_fetch_from_secret_store = true; | ||
lookup_secret(remote_id, username, [=] (std::optional<std::string> password) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
RetryMap::iterator i = m_retries.find(xmpp); | ||
if(i == m_retries.end()) { | ||
// By this point we should have an entry. Bail out if not, | ||
// because then something caused a disconnect in the meantime. | ||
return; | ||
} | ||
RetryInfo& info(i->second); | ||
if (password) { | ||
info.last_password = *password; | ||
} | ||
// Restart this function and do not re-query the secret store. | ||
sasl_callback(session, xmpp, prop); | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is dangerous--there is nothing that guarantees that Other than |
||
} | ||
else | ||
{ | ||
// Query user for password | ||
g_assert(info.password_dialog == NULL); | ||
|
||
gchar* remote_id; | ||
g_object_get(G_OBJECT(xmpp), | ||
"remote-hostname", &remote_id, | ||
NULL); | ||
Glib::ustring remote_id_(remote_id); | ||
g_free(remote_id); | ||
Glib::ustring remote_id = get_remote_hostname(xmpp); | ||
|
||
std::unique_ptr<PasswordDialog> dialog = | ||
PasswordDialog::create( | ||
m_parent, remote_id_, | ||
m_parent, remote_id, | ||
info.retries); | ||
info.password_dialog = dialog.release(); | ||
info.password_dialog->add_button( | ||
|
@@ -394,6 +424,7 @@ Gobby::AuthCommands::insert_retry_info(InfXmppConnection* xmpp) | |
std::make_pair(xmpp, | ||
RetryInfo())).first; | ||
iter->second.retries = 0; | ||
iter->second.username = m_preferences.user.name; | ||
iter->second.handle = g_signal_connect( | ||
G_OBJECT(xmpp), | ||
"notify::status", | ||
|
@@ -409,6 +440,17 @@ void Gobby::AuthCommands::on_notify_status(InfXmppConnection* connection) | |
InfXmlConnectionStatus status; | ||
g_object_get(G_OBJECT(connection), "status", &status, NULL); | ||
|
||
if (status == INF_XML_CONNECTION_OPEN) | ||
{ | ||
RetryMap::iterator iter = m_retries.find(connection); | ||
if (iter != m_retries.end() && !iter->second.last_password.empty()) { | ||
store_secret( | ||
get_remote_hostname(connection), | ||
iter->second.username, | ||
iter->second.last_password); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be wise to erase |
||
} | ||
} | ||
|
||
if(status != INF_XML_CONNECTION_OPENING) | ||
{ | ||
RetryMap::iterator iter = m_retries.find(connection); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
/* Gobby - GTK-based collaborative text editor | ||
* Copyright (C) 2024 Philipp Kern | ||
* | ||
* Permission to use, copy, modify, and/or distribute this software for any | ||
* purpose with or without fee is hereby granted, provided that the above | ||
* copyright notice and this permission notice appear in all copies. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
*/ | ||
|
||
#include "features.hpp" | ||
#include "secrets.hpp" | ||
|
||
#ifdef HAVE_LIBSECRET | ||
# include <libsecret/secret.h> | ||
#endif | ||
|
||
#include <iostream> | ||
#include <memory> | ||
#include <string> | ||
|
||
namespace Gobby { | ||
|
||
namespace { | ||
|
||
#ifdef HAVE_LIBSECRET | ||
const SecretSchema* get_schema() | ||
{ | ||
static const SecretSchema secret_schema = { | ||
"de.0x539.gobby.Password", SECRET_SCHEMA_NONE, | ||
{ | ||
{ "server", SECRET_SCHEMA_ATTRIBUTE_STRING }, | ||
{ "username", SECRET_SCHEMA_ATTRIBUTE_STRING }, | ||
{ "NULL", SECRET_SCHEMA_ATTRIBUTE_STRING }, | ||
} | ||
}; | ||
return &secret_schema; | ||
} | ||
|
||
extern "C" | ||
{ | ||
pkern marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
void on_password_stored(GObject* source, GAsyncResult* result, gpointer unused) | ||
{ | ||
GError* error = nullptr; | ||
secret_password_store_finish(result, &error); | ||
if (error != nullptr) | ||
{ | ||
std::cerr << "Failed to store secret: " << error->message << std::endl; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I remember correctly, most other notifications in Gobby use the status bar? I would either make this a class that can have the status bar as a member, so it can show the error there, or propagate the error back to the caller. |
||
g_error_free(error); | ||
} | ||
} | ||
|
||
void on_password_lookup(GObject* source, GAsyncResult* result, gpointer user_data) | ||
{ | ||
std::unique_ptr<LookupCallback> password_callback{reinterpret_cast<LookupCallback*>(user_data)}; | ||
GError *error = nullptr; | ||
gchar* password = secret_password_lookup_finish(result, &error); | ||
if (error != nullptr) | ||
{ | ||
std::cerr << "Failed to lookup secret: " << error->message << std::endl; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here about error handling |
||
g_error_free(error); | ||
(*password_callback)(std::nullopt); | ||
} | ||
else if (password == nullptr) | ||
{ | ||
(*password_callback)(std::nullopt); | ||
} | ||
else | ||
{ | ||
(*password_callback)(password); | ||
secret_password_free(password); | ||
} | ||
} | ||
|
||
} // extern "C" | ||
#endif | ||
|
||
} // namespace | ||
|
||
void store_secret(const std::string& server, const std::string& username, const std::string& password) | ||
{ | ||
#ifdef HAVE_LIBSECRET | ||
const std::string label = "Gobby password for \"" + username + "\" on \"" + server + "\""; | ||
secret_password_store(get_schema(), | ||
SECRET_COLLECTION_DEFAULT, | ||
label.c_str(), | ||
password.c_str(), | ||
/*cancellable=*/NULL, | ||
on_password_stored, | ||
/*user_data=*/NULL, | ||
"server", server.c_str(), | ||
"username", username.c_str(), | ||
NULL); | ||
#endif | ||
} | ||
|
||
void lookup_secret(const std::string& server, const std::string& username, LookupCallback password_callback) | ||
{ | ||
#ifdef HAVE_LIBSECRET | ||
auto callback = std::make_unique<LookupCallback>(password_callback); | ||
secret_password_lookup(get_schema(), | ||
/*cancellable=*/NULL, | ||
on_password_lookup, | ||
/*user_data=*/static_cast<void*>(callback.release()), | ||
"server", server.c_str(), | ||
"username", username.c_str(), | ||
NULL); | ||
#else | ||
password_callback(std::nullopt); | ||
#endif | ||
} | ||
|
||
} // namespace Gobby |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
/* Gobby - GTK-based collaborative text editor | ||
* Copyright (C) 2024 Philipp Kern | ||
* | ||
* Permission to use, copy, modify, and/or distribute this software for any | ||
* purpose with or without fee is hereby granted, provided that the above | ||
* copyright notice and this permission notice appear in all copies. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
*/ | ||
|
||
#ifndef _GOBBY_UTIL_SECRETS_HPP_ | ||
#define _GOBBY_UTIL_SECRETS_HPP_ | ||
|
||
#include <functional> | ||
#include <optional> | ||
#include <string> | ||
|
||
namespace Gobby | ||
{ | ||
|
||
using LookupCallback = std::function<void(std::optional<std::string> password)>; | ||
pkern marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// lookup_secret asynchronously attempts to lookup the password for the given | ||
// server and username combination in the keyring. Once the lookup attempt | ||
// completes, the callback is called. | ||
void lookup_secret(const std::string& server, const std::string& username, LookupCallback password_callback); | ||
|
||
// store_secret asynchronously attempts to store the password for the given server | ||
// and username in the keyring. | ||
void store_secret(const std::string& server, const std::string& username, const std::string& password); | ||
|
||
} // namespace Gobby | ||
|
||
#endif // _GOBBY_UTIL_SECRETS_HPP_ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest to move this into the anonymous namespace at the top of the file