mirror of
https://github.com/Murmele/Gittyup.git
synced 2024-09-11 02:35:41 +03:00
commit
ee7c780217
@ -26,7 +26,6 @@ void Setting::initialize(QMap<Id, QString> &keys) {
|
||||
keys[Id::TerminalName] = "terminal/name";
|
||||
keys[Id::TerminalPath] = "terminal/path";
|
||||
keys[Id::DontTranslate] = "translation/disable";
|
||||
keys[Id::StoreCredentials] = "credential/store";
|
||||
keys[Id::AllowSingleInstanceOnly] = "singleInstance";
|
||||
keys[Id::CheckForUpdatesAutomatically] = "update/check";
|
||||
keys[Id::InstallUpdatesAutomatically] = "update/download";
|
||||
|
@ -50,7 +50,6 @@ public:
|
||||
TerminalName,
|
||||
TerminalPath,
|
||||
DontTranslate,
|
||||
StoreCredentials,
|
||||
AllowSingleInstanceOnly,
|
||||
CheckForUpdatesAutomatically,
|
||||
InstallUpdatesAutomatically,
|
||||
|
@ -2,9 +2,9 @@ if(WIN32)
|
||||
set(CREDENTIAL_IMPL_FILE WinCred.cpp)
|
||||
endif()
|
||||
|
||||
add_library(cred Cache.cpp CredentialHelper.cpp GitCredential.cpp
|
||||
add_library(cred Cache.cpp Store.cpp CredentialHelper.cpp GitCredential.cpp
|
||||
${CREDENTIAL_IMPL_FILE})
|
||||
|
||||
target_link_libraries(cred conf Qt5::Core)
|
||||
target_link_libraries(cred conf git Qt5::Core)
|
||||
|
||||
set_target_properties(cred PROPERTIES AUTOMOC ON)
|
||||
|
@ -11,7 +11,9 @@
|
||||
#include "Cache.h"
|
||||
#include "GitCredential.h"
|
||||
#include "WinCred.h"
|
||||
#include "Store.h"
|
||||
#include "conf/Settings.h"
|
||||
#include "git/Config.h"
|
||||
#include <QLibrary>
|
||||
#include <QPointer>
|
||||
#include <QSettings>
|
||||
@ -22,27 +24,36 @@ namespace {
|
||||
|
||||
const QString kLogKey = "credential/log";
|
||||
|
||||
const QString cacheStoreName = "cache";
|
||||
const QString storeStoreName = "store";
|
||||
const QString osxKeyChainStoreName = "osxkeychain";
|
||||
const QString winCredStoreName = "wincred";
|
||||
const QString libSecretStoreName = "libsecret";
|
||||
const QString gnomeKeyringStoreName = "gnome-keyring";
|
||||
|
||||
} // namespace
|
||||
|
||||
CredentialHelper *CredentialHelper::instance() {
|
||||
static QPointer<CredentialHelper> instance;
|
||||
if (!instance) {
|
||||
if (Settings::instance()->value(Setting::Id::StoreCredentials).toBool()) {
|
||||
#if defined(Q_OS_MAC)
|
||||
instance = new GitCredential("osxkeychain");
|
||||
#elif defined(Q_OS_WIN)
|
||||
// The git wincred helper fails for some users.
|
||||
instance = new WinCred;
|
||||
#else
|
||||
QLibrary lib("secret-1", 0);
|
||||
if (lib.load()) {
|
||||
instance = new GitCredential("libsecret");
|
||||
} else {
|
||||
QLibrary lib("gnome-keyring", 0);
|
||||
if (lib.load())
|
||||
instance = new GitCredential("gnome-keyring");
|
||||
git::Config config = git::Config::global();
|
||||
auto helperName = config.value<QString>("credential.helper");
|
||||
if (isHelperValid(helperName)) {
|
||||
if (helperName == cacheStoreName) {
|
||||
instance = new Cache;
|
||||
} else if (helperName == storeStoreName) {
|
||||
auto path =
|
||||
QString::fromLocal8Bit(qgetenv("HOME") + "/.git-credentials");
|
||||
instance = new Store(path);
|
||||
}
|
||||
#if defined(Q_OS_WIN)
|
||||
else if (helperName == winCredStoreName) {
|
||||
instance = new WinCred;
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
instance = new GitCredential(helperName);
|
||||
}
|
||||
}
|
||||
|
||||
if (!instance)
|
||||
@ -52,6 +63,31 @@ CredentialHelper *CredentialHelper::instance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool CredentialHelper::isHelperValid(const QString &name) {
|
||||
return !name.isEmpty();
|
||||
}
|
||||
|
||||
QStringList CredentialHelper::getAvailableHelperNames() {
|
||||
QStringList list;
|
||||
list.append(cacheStoreName);
|
||||
list.append(storeStoreName);
|
||||
#if defined(Q_OS_MAC)
|
||||
list.append(osxKeyChainStoreName);
|
||||
#elif defined(Q_OS_WIN)
|
||||
list.append(winCredStoreName);
|
||||
#else
|
||||
QLibrary lib("secret-1", 0);
|
||||
if (lib.load()) {
|
||||
list.append(libSecretStoreName);
|
||||
}
|
||||
QLibrary lib2(gnomeKeyringStoreName, 0);
|
||||
if (lib2.load()) {
|
||||
list.append(gnomeKeyringStoreName);
|
||||
}
|
||||
#endif
|
||||
return list;
|
||||
}
|
||||
|
||||
bool CredentialHelper::isLoggingEnabled() {
|
||||
return QSettings().value(kLogKey).toBool();
|
||||
}
|
||||
|
@ -33,6 +33,9 @@ public:
|
||||
static bool isLoggingEnabled();
|
||||
static void setLoggingEnabled(bool enabled);
|
||||
|
||||
static QStringList getAvailableHelperNames();
|
||||
static bool isHelperValid(const QString &name);
|
||||
|
||||
protected:
|
||||
static void log(const QString &text);
|
||||
};
|
||||
|
111
src/cred/Store.cpp
Normal file
111
src/cred/Store.cpp
Normal file
@ -0,0 +1,111 @@
|
||||
//
|
||||
// Copyright (c) 2022, Gittyup Community
|
||||
//
|
||||
// This software is licensed under the MIT License. The LICENSE.md file
|
||||
// describes the conditions under which this software may be distributed.
|
||||
//
|
||||
// Author: Hessamoddin Hediehloo(H-4ND-H)
|
||||
//
|
||||
|
||||
#include "Store.h"
|
||||
#include <QUrl>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
|
||||
namespace {
|
||||
|
||||
QString host(const QString &url) {
|
||||
QString host = QUrl(url).host();
|
||||
if (!host.isEmpty())
|
||||
return host;
|
||||
|
||||
// Extract hostname from SSH URL.
|
||||
int end = url.indexOf(':');
|
||||
int begin = url.indexOf('@') + 1;
|
||||
return url.mid(begin, end - begin);
|
||||
}
|
||||
|
||||
QString protocol(const QString &url) {
|
||||
QString scheme = QUrl(url).scheme();
|
||||
return !scheme.isEmpty() ? scheme : "ssh";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Store::Store(const QString &path) { mPath = path; }
|
||||
|
||||
QMap<QString, QMap<QString, QMap<QString, QString>>> Store::readCredFile() {
|
||||
QMap<QString, QMap<QString, QMap<QString, QString>>> store;
|
||||
|
||||
QFile file(mPath);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
return store;
|
||||
|
||||
while (!file.atEnd()) {
|
||||
auto line = file.readLine();
|
||||
auto urlStr = QUrl::fromPercentEncoding(line);
|
||||
auto urlObj = QUrl::fromUserInput(urlStr);
|
||||
store[urlObj.scheme()][urlObj.host()][urlObj.userName()] =
|
||||
urlObj.password();
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
bool Store::extractUserPass(const QMap<QString, QString> &map,
|
||||
QString &username, QString &password) {
|
||||
if (map.isEmpty())
|
||||
return false;
|
||||
|
||||
if (username.isEmpty())
|
||||
username = map.keys().first();
|
||||
|
||||
if (!map.contains(username))
|
||||
return false;
|
||||
|
||||
password = map.value(username);
|
||||
|
||||
return !username.isEmpty() && !password.isEmpty();
|
||||
}
|
||||
|
||||
bool Store::get(const QString &url, QString &username, QString &password) {
|
||||
|
||||
auto store = readCredFile();
|
||||
const QMap<QString, QString> &map = store[protocol(url)][host(url)];
|
||||
return extractUserPass(map, username, password);
|
||||
}
|
||||
|
||||
bool Store::store(const QString &url, const QString &username,
|
||||
const QString &password) {
|
||||
auto store = readCredFile();
|
||||
store[protocol(url)][host(url)][username] = password;
|
||||
|
||||
QFile file(mPath);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text))
|
||||
return false;
|
||||
|
||||
foreach (const auto &protocolKey, store.keys()) {
|
||||
auto protocol = store[protocolKey];
|
||||
foreach (const auto &hostKey, protocol.keys()) {
|
||||
auto host = protocol[hostKey];
|
||||
foreach (const auto &usernameKey, host.keys()) {
|
||||
QUrl temp;
|
||||
temp.setScheme(protocolKey);
|
||||
temp.setHost(hostKey);
|
||||
temp.setUserName(usernameKey);
|
||||
temp.setPassword(host[usernameKey]);
|
||||
auto encoded = QUrl::toPercentEncoding(temp.toString(), "@:/");
|
||||
QTextStream fout(&file);
|
||||
fout << encoded << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QString Store::command() const { return ""; }
|
34
src/cred/Store.h
Normal file
34
src/cred/Store.h
Normal file
@ -0,0 +1,34 @@
|
||||
//
|
||||
// Copyright (c) 2022, Gittyup Community
|
||||
//
|
||||
// This software is licensed under the MIT License. The LICENSE.md file
|
||||
// describes the conditions under which this software may be distributed.
|
||||
//
|
||||
// Author: Hessamoddin Hediehloo(H-4ND-H)
|
||||
//
|
||||
|
||||
#ifndef STORE_H
|
||||
#define STORE_H
|
||||
|
||||
#include "CredentialHelper.h"
|
||||
#include <QMap>
|
||||
|
||||
class Store : public CredentialHelper {
|
||||
public:
|
||||
Store(const QString &path);
|
||||
|
||||
bool get(const QString &url, QString &username, QString &password) override;
|
||||
|
||||
bool store(const QString &url, const QString &username,
|
||||
const QString &password) override;
|
||||
|
||||
private:
|
||||
QString command() const;
|
||||
QMap<QString, QMap<QString, QMap<QString, QString>>> readCredFile();
|
||||
bool extractUserPass(const QMap<QString, QString> &map, QString &username,
|
||||
QString &password);
|
||||
|
||||
QString mPath;
|
||||
};
|
||||
|
||||
#endif
|
@ -109,6 +109,8 @@ public:
|
||||
mStoreCredentials =
|
||||
new QCheckBox(tr("Store credentials in secure storage"), this);
|
||||
|
||||
mAvailableStores = new QComboBox(this);
|
||||
|
||||
QLabel *privacy = new QLabel(tr("<a href='view'>View privacy policy</a>"));
|
||||
connect(privacy, &QLabel::linkActivated,
|
||||
[] { AboutDialog::openSharedInstance(AboutDialog::Privacy); });
|
||||
@ -122,6 +124,7 @@ public:
|
||||
form->addRow(QString(), mAutoPrune);
|
||||
form->addRow(tr("Language:"), mNoTranslation);
|
||||
form->addRow(tr("Credentials:"), mStoreCredentials);
|
||||
form->addRow(tr("Credential store type:"), mAvailableStores);
|
||||
form->addRow(QString(), privacy);
|
||||
|
||||
mSingleInstance =
|
||||
@ -181,11 +184,27 @@ public:
|
||||
Settings::instance()->setValue(Setting::Id::DontTranslate, checked);
|
||||
});
|
||||
|
||||
connect(mStoreCredentials, &QCheckBox::toggled, [](bool checked) {
|
||||
Settings::instance()->setValue(Setting::Id::StoreCredentials, checked);
|
||||
connect(mStoreCredentials, &QCheckBox::toggled, [this](bool checked) {
|
||||
git::Config config = git::Config::global();
|
||||
mAvailableStores->setEnabled(checked);
|
||||
if (checked) {
|
||||
auto store = mAvailableStores->currentText();
|
||||
config.setValue("credential.helper", store);
|
||||
} else {
|
||||
config.remove("credential.helper");
|
||||
}
|
||||
|
||||
delete CredentialHelper::instance();
|
||||
});
|
||||
|
||||
connect(mAvailableStores, &QComboBox::currentTextChanged,
|
||||
[](const QString &text) {
|
||||
git::Config config = git::Config::global();
|
||||
config.setValue("credential.helper", text);
|
||||
|
||||
delete CredentialHelper::instance();
|
||||
});
|
||||
|
||||
connect(mSingleInstance, &QCheckBox::toggled, [](bool checked) {
|
||||
Settings::instance()->setValue(Setting::Id::AllowSingleInstanceOnly,
|
||||
checked);
|
||||
@ -198,6 +217,7 @@ public:
|
||||
mEmail->setText(config.value<QString>("user.email"));
|
||||
|
||||
Settings *settings = Settings::instance();
|
||||
|
||||
mFetch->setChecked(
|
||||
settings->value(Setting::Id::FetchAutomatically).toBool());
|
||||
mFetchMinutes->setValue(
|
||||
@ -213,8 +233,17 @@ public:
|
||||
|
||||
mNoTranslation->setChecked(
|
||||
settings->value(Setting::Id::DontTranslate).toBool());
|
||||
mStoreCredentials->setChecked(
|
||||
settings->value(Setting::Id::StoreCredentials).toBool());
|
||||
|
||||
auto currentHelper = config.value<QString>("credential.helper");
|
||||
auto checked = CredentialHelper::isHelperValid(currentHelper);
|
||||
mStoreCredentials->setChecked(checked);
|
||||
|
||||
auto availableHelpers = CredentialHelper::getAvailableHelperNames();
|
||||
foreach (auto helper, availableHelpers) {
|
||||
mAvailableStores->addItem(helper);
|
||||
}
|
||||
mAvailableStores->setCurrentText(currentHelper);
|
||||
mAvailableStores->setEditable(true);
|
||||
|
||||
mSingleInstance->setChecked(
|
||||
settings->value(Setting::Id::AllowSingleInstanceOnly).toBool());
|
||||
@ -231,6 +260,7 @@ private:
|
||||
QCheckBox *mAutoPrune;
|
||||
QCheckBox *mNoTranslation;
|
||||
QCheckBox *mStoreCredentials;
|
||||
QComboBox *mAvailableStores;
|
||||
QCheckBox *mSingleInstance;
|
||||
};
|
||||
|
||||
|
@ -109,6 +109,7 @@ test(NAME Submodule)
|
||||
test(NAME referencelist)
|
||||
test(NAME amend)
|
||||
test(NAME SshConfig)
|
||||
test(NAME store)
|
||||
test(NAME fileContextMenu NO_WIN32_OFFSCREEN)
|
||||
test(NAME Setting)
|
||||
test(NAME commitMessageTemplate)
|
||||
|
@ -10,7 +10,7 @@
|
||||
#include "Test.h"
|
||||
#include "git/Config.h"
|
||||
#include "ui/RepoView.h"
|
||||
//#include <JlCompress.h>
|
||||
// #include <JlCompress.h>
|
||||
#include <exception>
|
||||
#include <QFileInfo>
|
||||
#include "zip.h"
|
||||
|
120
test/store.cpp
Normal file
120
test/store.cpp
Normal file
@ -0,0 +1,120 @@
|
||||
#include "Test.h"
|
||||
|
||||
#include "cred/Store.h"
|
||||
|
||||
class TestStore : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
QTemporaryDir mTempDir;
|
||||
|
||||
QString getTestFilePath();
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void readUserPassTestCase();
|
||||
void wrongProtocolTestCase();
|
||||
void wrongUrlTestCase();
|
||||
void wrongUsernameTestCase();
|
||||
void saveUserPassTestCase();
|
||||
void wrongFilePathTestCase();
|
||||
void cleanupTestCase();
|
||||
};
|
||||
|
||||
QString TestStore::getTestFilePath() {
|
||||
return mTempDir.path() + "/fakeCred.txt";
|
||||
}
|
||||
|
||||
void TestStore::initTestCase() {
|
||||
QFile file(getTestFilePath());
|
||||
if (file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
|
||||
QTextStream fout(&file);
|
||||
fout << "http://joneDoe:secretPassword@192.168.1.1\n";
|
||||
fout << "https://janeDoe:securePassword@192.168.2.2:3000\n";
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
void TestStore::readUserPassTestCase() {
|
||||
Store store(getTestFilePath());
|
||||
|
||||
auto url = "https://192.168.2.2:3000/h-4nd-h/fake.git";
|
||||
QString username;
|
||||
QString password;
|
||||
auto result = store.get(url, username, password);
|
||||
|
||||
QVERIFY(result);
|
||||
QCOMPARE(username, "janeDoe");
|
||||
QCOMPARE(password, "securePassword");
|
||||
}
|
||||
|
||||
void TestStore::wrongProtocolTestCase() {
|
||||
Store store(getTestFilePath());
|
||||
|
||||
auto url = "https://192.168.1.1/h-4nd-h/fake.git";
|
||||
QString username;
|
||||
QString password;
|
||||
auto result = store.get(url, username, password);
|
||||
|
||||
QVERIFY(!result);
|
||||
}
|
||||
|
||||
void TestStore::wrongUrlTestCase() {
|
||||
Store store(getTestFilePath());
|
||||
|
||||
auto url = "http://192.168.1.2/h-4nd-h/fake.git";
|
||||
QString username;
|
||||
QString password;
|
||||
auto result = store.get(url, username, password);
|
||||
|
||||
QVERIFY(!result);
|
||||
}
|
||||
|
||||
void TestStore::wrongUsernameTestCase() {
|
||||
Store store(getTestFilePath());
|
||||
|
||||
auto url = "http://192.168.1.1/h-4nd-h/fake.git";
|
||||
QString username = "janeDoe";
|
||||
QString password;
|
||||
auto result = store.get(url, username, password);
|
||||
|
||||
QVERIFY(!result);
|
||||
}
|
||||
|
||||
void TestStore::saveUserPassTestCase() {
|
||||
Store store(getTestFilePath());
|
||||
|
||||
auto url = "http://192.168.1.2/h-4nd-h/fake.git";
|
||||
QString username = "NewUser";
|
||||
QString password = "NewPassword";
|
||||
auto result = store.store(url, username, password);
|
||||
|
||||
QVERIFY(result);
|
||||
|
||||
QString readBackUsername;
|
||||
QString readBackpassword;
|
||||
auto result2 = store.get(url, readBackUsername, readBackpassword);
|
||||
|
||||
QVERIFY(result2);
|
||||
QCOMPARE(readBackUsername, "NewUser");
|
||||
QCOMPARE(readBackpassword, "NewPassword");
|
||||
}
|
||||
|
||||
void TestStore::wrongFilePathTestCase() {
|
||||
Store store("veryWrongPath");
|
||||
|
||||
auto url = "http://192.168.1.2/h-4nd-h/fake.git";
|
||||
QString username = "";
|
||||
QString password = "";
|
||||
auto result = store.get(url, username, password);
|
||||
|
||||
QVERIFY(!result);
|
||||
}
|
||||
|
||||
void TestStore::cleanupTestCase() {
|
||||
QFile file(getTestFilePath());
|
||||
file.remove();
|
||||
}
|
||||
|
||||
TEST_MAIN(TestStore)
|
||||
#include "store.moc"
|
Loading…
Reference in New Issue
Block a user