You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
269 lines
9.6 KiB
269 lines
9.6 KiB
// Copyright (C) 2024 Simon Quigley <tsimonq2@ubuntu.com>
|
|
//
|
|
// 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 <https://www.gnu.org/licenses/>.
|
|
|
|
#include "authentication.h"
|
|
#include "utils.h"
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <filesystem>
|
|
#include <regex>
|
|
#include <sstream>
|
|
#include <nlohmann/json.hpp>
|
|
#include <libsecret/secret.h>
|
|
#include <glib.h>
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
const std::string AUTH_PLAINTEXT_CREDENTIALS_FILE =
|
|
std::string(std::getenv("HOME")) + "/.local/share/python_keyring/keyring_pass.cfg";
|
|
|
|
static const SecretSchema* get_schema(void) {
|
|
static const SecretSchema* schema = secret_schema_new(
|
|
"org.launchpad.lib.Secret",
|
|
SECRET_SCHEMA_NONE,
|
|
"credentials", SECRET_SCHEMA_ATTRIBUTE_STRING,
|
|
"key", SECRET_SCHEMA_ATTRIBUTE_STRING,
|
|
NULL
|
|
);
|
|
return schema;
|
|
}
|
|
|
|
bool read_plaintext_credentials_impl(std::string& consumer_key,
|
|
std::string& consumer_secret,
|
|
std::string& oauth_token,
|
|
std::string& oauth_token_secret)
|
|
{
|
|
std::ifstream file(AUTH_PLAINTEXT_CREDENTIALS_FILE);
|
|
if (!file.is_open()) {
|
|
std::cerr << "Plaintext credentials file not found: " << AUTH_PLAINTEXT_CREDENTIALS_FILE << std::endl;
|
|
return false;
|
|
}
|
|
|
|
std::string line;
|
|
bool in_launchpadlib_section = false;
|
|
std::string encoded_credentials;
|
|
bool credentials_found = false;
|
|
std::string encoded_key;
|
|
|
|
while (std::getline(file, line)) {
|
|
line.erase(0, line.find_first_not_of(" \t\r\n"));
|
|
line.erase(line.find_last_not_of(" \t\r\n") + 1);
|
|
|
|
if (line.empty()) continue;
|
|
|
|
if (line.front() == '[' && line.back() == ']') {
|
|
std::string section = line.substr(1, line.size() - 2);
|
|
in_launchpadlib_section = (section == "launchpadlib");
|
|
continue;
|
|
}
|
|
|
|
if (in_launchpadlib_section) {
|
|
size_t eq_pos = line.find('=');
|
|
if (eq_pos != std::string::npos) {
|
|
encoded_key = line.substr(0, eq_pos);
|
|
encoded_credentials.clear();
|
|
while (std::getline(file, line)) {
|
|
if (line.find(" ") == 0) {
|
|
encoded_credentials += line.substr(8);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
credentials_found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
file.close();
|
|
|
|
if (!credentials_found || encoded_credentials.empty()) {
|
|
std::cerr << "No credentials found in plaintext file." << std::endl;
|
|
return false;
|
|
}
|
|
|
|
std::string decoded_credentials = base64_decode(encoded_credentials);
|
|
if (decoded_credentials.empty()) {
|
|
std::cerr << "Failed to decode Base64 credentials." << std::endl;
|
|
return false;
|
|
}
|
|
|
|
std::istringstream iss(decoded_credentials);
|
|
std::string token_line;
|
|
while (std::getline(iss, token_line)) {
|
|
size_t pos = token_line.find('=');
|
|
if (pos != std::string::npos) {
|
|
std::string key = token_line.substr(0, pos);
|
|
std::string value = token_line.substr(pos + 1);
|
|
if (key == "oauth_token") {
|
|
oauth_token = value;
|
|
} else if (key == "oauth_token_secret") {
|
|
oauth_token_secret = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string decoded_key = decode_service_identifier(encoded_key);
|
|
std::regex rgx("system-wide: ([^@]+)@https://api.launchpad.net/");
|
|
std::smatch matches;
|
|
if (std::regex_search(decoded_key, matches, rgx)) {
|
|
consumer_key = matches[1];
|
|
} else {
|
|
std::cerr << "Failed to parse consumer_key from encoded key." << std::endl;
|
|
return false;
|
|
}
|
|
|
|
consumer_secret = "&";
|
|
|
|
if (oauth_token.empty() || oauth_token_secret.empty()) {
|
|
std::cerr << "Incomplete credentials in plaintext file." << std::endl;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool create_plaintext_credentials_impl(const std::string& consumer_key,
|
|
const std::string& consumer_secret_input,
|
|
const std::string& oauth_token,
|
|
const std::string& oauth_token_secret)
|
|
{
|
|
fs::path dir = fs::path(AUTH_PLAINTEXT_CREDENTIALS_FILE).parent_path();
|
|
std::error_code ec;
|
|
if (!fs::exists(dir)) {
|
|
if (!fs::create_directories(dir, ec)) {
|
|
std::cerr << "Failed to create directory: " << dir << " Error: " << ec.message() << std::endl;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
std::string service_identifier = "system-wide: " + consumer_key + "@https://api.launchpad.net/";
|
|
std::string encoded_key = encode_service_identifier(service_identifier);
|
|
|
|
std::string credentials = "oauth_token=" + oauth_token + "\n" +
|
|
"oauth_token_secret=" + oauth_token_secret + "\n";
|
|
|
|
std::string encoded_credentials = base64_encode(reinterpret_cast<const unsigned char*>(credentials.c_str()), credentials.length());
|
|
|
|
std::ofstream file_out(AUTH_PLAINTEXT_CREDENTIALS_FILE, std::ios::trunc);
|
|
if (!file_out.is_open()) {
|
|
std::cerr << "Failed to open plaintext credentials file for writing: " << AUTH_PLAINTEXT_CREDENTIALS_FILE << std::endl;
|
|
return false;
|
|
}
|
|
|
|
file_out << "[launchpadlib]\n";
|
|
file_out << encoded_key << " = \n";
|
|
|
|
size_t pos = 0;
|
|
size_t line_length = 80;
|
|
while (pos < encoded_credentials.size()) {
|
|
file_out << " " << encoded_credentials.substr(pos, line_length) << "\n";
|
|
pos += line_length;
|
|
}
|
|
|
|
file_out.close();
|
|
std::cout << "Credentials saved to " << AUTH_PLAINTEXT_CREDENTIALS_FILE << std::endl;
|
|
return true;
|
|
}
|
|
|
|
bool read_gnome_keyring_impl(std::string& consumer_key,
|
|
std::string& consumer_secret,
|
|
std::string& oauth_token,
|
|
std::string& oauth_token_secret)
|
|
{
|
|
std::string actual_consumer_key = "ubuntu (lugito-ci)";
|
|
std::string service_identifier = "system-wide: " + actual_consumer_key + "@https://api.launchpad.net/";
|
|
std::string encoded_key = encode_service_identifier(service_identifier);
|
|
|
|
std::string serialized_credentials = get_secret("org.launchpad.lib.Secret", "credentials", "credentials").value_or("");
|
|
if (serialized_credentials.empty()) {
|
|
std::cerr << "No credentials found in GNOME keyring." << std::endl;
|
|
return false;
|
|
}
|
|
|
|
std::string decoded_credentials = base64_decode(serialized_credentials);
|
|
if (decoded_credentials.empty()) {
|
|
std::cerr << "Failed to decode Base64 credentials from keyring." << std::endl;
|
|
return false;
|
|
}
|
|
|
|
std::istringstream iss(decoded_credentials);
|
|
std::string token_line;
|
|
while (std::getline(iss, token_line)) {
|
|
size_t pos = token_line.find('=');
|
|
if (pos != std::string::npos) {
|
|
std::string key = token_line.substr(0, pos);
|
|
std::string value = token_line.substr(pos + 1);
|
|
if (key == "oauth_token") {
|
|
oauth_token = value;
|
|
} else if (key == "oauth_token_secret") {
|
|
oauth_token_secret = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string decoded_key = decode_service_identifier(encoded_key);
|
|
std::regex rgx("system-wide: ([^@]+)@https://api.launchpad.net/");
|
|
std::smatch matches;
|
|
if (std::regex_search(decoded_key, matches, rgx)) {
|
|
consumer_key = matches[1];
|
|
} else {
|
|
std::cerr << "Failed to parse consumer_key from encoded key." << std::endl;
|
|
return false;
|
|
}
|
|
|
|
consumer_secret = "&";
|
|
|
|
if (oauth_token.empty() || oauth_token_secret.empty()) {
|
|
std::cerr << "Incomplete credentials in GNOME keyring." << std::endl;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool create_keyring_credentials_impl(const std::string& consumer_key,
|
|
const std::string& consumer_secret,
|
|
const std::string& oauth_token,
|
|
const std::string& oauth_token_secret)
|
|
{
|
|
std::string service_identifier = "system-wide: " + consumer_key + "@https://api.launchpad.net/";
|
|
std::string encoded_key = encode_service_identifier(service_identifier);
|
|
|
|
std::string credentials = "oauth_token=" + oauth_token + "\n" +
|
|
"oauth_token_secret=" + oauth_token_secret + "\n";
|
|
std::string encoded_credentials = base64_encode(reinterpret_cast<const unsigned char*>(credentials.c_str()), credentials.length());
|
|
|
|
const SecretSchema* schema = get_schema();
|
|
|
|
GError* error = nullptr;
|
|
if (!secret_password_store_sync(
|
|
schema,
|
|
"credentials",
|
|
"launchpadlib",
|
|
encoded_credentials.c_str(),
|
|
NULL,
|
|
&error,
|
|
"credentials", "credentials",
|
|
NULL)) {
|
|
std::cerr << "Error storing credentials in keyring: " << error->message << std::endl;
|
|
g_error_free(error);
|
|
return false;
|
|
}
|
|
|
|
std::cout << "Credentials stored in GNOME keyring successfully." << std::endl;
|
|
return true;
|
|
}
|