// Copyright (C) 2024 Simon Quigley // // 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 . #include "authentication.h" #include "utils.h" #include #include #include #include #include #include #include #include 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(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(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; }