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

// 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;
}