diff --git a/.gitignore b/.gitignore index 306aaec..9ecc5a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,14 @@ # Builds dirs -cmake-build-* -build +build* - -# IDE +# IDE & Devs tools .clickable .idea *.project *.workspace .codelite +*.kdev4 +*swp # Third parties ouput dir third/local diff --git a/CMakeLists.txt b/CMakeLists.txt index babc50e..21c23c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,10 @@ cmake_minimum_required(VERSION 3.5.1) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +option(TEST_RUNNER "Run Test" OFF) +if(${TEST_RUNNER}) + add_definitions(-DTEST_RUNNER) +endif() execute_process( COMMAND dpkg-architecture -qDEB_HOST_ARCH @@ -16,6 +20,7 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE ) + find_package(Qt5Core) find_package(Qt5Qml) find_package(Qt5Quick) @@ -36,7 +41,13 @@ set(DESKTOP_FILE_NAME ${PROJECT_NAME}.desktop) add_executable(${PROJECT_NAME} main.cpp) -qt5_use_modules(${PROJECT_NAME} Gui Qml Quick) +if(${TEST_RUNNER}) + find_package(Qt5QuickTest) + qt5_use_modules(${PROJECT_NAME} Gui Qml Quick QuickTest) +else() + qt5_use_modules(${PROJECT_NAME} Gui Qml Quick) +endif() + install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}) configure_file(manifest.json.in ${CMAKE_CURRENT_BINARY_DIR}/manifest.json) @@ -45,12 +56,15 @@ install(FILES ${PROJECT_NAME}.apparmor DESTINATION ${DATA_DIR}) install(DIRECTORY qml DESTINATION ${DATA_DIR}) install(DIRECTORY assets DESTINATION ${DATA_DIR}) file(GLOB_RECURSE BIN_FILES - "third/local/bin/${ARCH_TRIPLET}/*") + "third/local/${ARCH_TRIPLET}/bin/*") install( FILES ${BIN_FILES} PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE DESTINATION ${BIN_DIR} ) +if(${TEST_RUNNER}) + install(DIRECTORY tests DESTINATION ${DATA_DIR}) +endif() # Translations file(GLOB_RECURSE I18N_SRC_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/po qml/*.qml qml/*.js) @@ -77,3 +91,4 @@ add_subdirectory(po) add_subdirectory(plugins) add_custom_target(${PROJECT_NAME}_FILES ALL SOURCES ${PROJECT_SRC_FILES}) + diff --git a/FEATURES.MD b/FEATURES.MD new file mode 100644 index 0000000..379c5cc --- /dev/null +++ b/FEATURES.MD @@ -0,0 +1,79 @@ +# Features + +## Pass + +- [ ] pass init [--path=subfolder,-p subfolder] gpg-id... + Initialize new password storage and use gpg-id for encryption. + Selectively reencrypt existing passwords using new gpg-id. + - [ ] Interactive dialog to init a new password storage + - [ ] Support subfolder + - [ ] Support reencrypt +--- +- [x] pass [ls] [subfolder] + List passwords. + - [x] UI allowing to navigate through the password store showing the available passwords +--- +- [ ] pass find pass-names... +List passwords that match pass-names + - [ ] Search bar allowing searchs by pass-names +--- +- [ ] pass [show] [--clip[=line-number],-c[line-number]] pass-name + Show existing password and optionally put it on the clipboard. + If put on the clipboard, it will be cleared in 45 seconds. + - [ ] Click on a pass show the password + - [ ] Add option to put the password to the clipboard + - [ ] Clearing clipboard after 45 secs + - [ ] Line number option ??? +--- +- [ ] pass grep search-string + Search for password files containing search-string when decrypted. + - [ ] TBD +--- +- [ ] pass insert [--echo,-e | --multiline,-m] [--force,-f] pass-name + Insert new password. Optionally, echo the password back to the console + during entry. Or, optionally, the entry may be multiline. Prompt before + overwriting existing password unless forced. + - [ ] TBD +--- +- [ ] pass edit pass-name + Insert a new password or edit an existing password using editor. + - [ ] TBD +--- +- [ ] pass generate [--no-symbols,-n] [--clip,-c] [--in-place,-i | --force,-f] pass-name [pass-length] + Generate a new password of pass-length (or 25 if unspecified) with optionally no symbols. + Optionally put it on the clipboard and clear board after 45 seconds. + Prompt before overwriting existing password unless forced. + Optionally replace only the first line of an existing file with a new password. + - [ ] TBD +--- +- [ ] pass rm [--recursive,-r] [--force,-f] pass-name + Remove existing password or directory, optionally forcefully. + - [ ] TBD +--- +- [ ] pass mv [--force,-f] old-path new-path + Renames or moves old-path to new-path, optionally forcefully, selectively reencrypting. + - [ ] TBD +--- +- [ ] pass cp [--force,-f] old-path new-path + Copies old-path to new-path, optionally forcefully, selectively reencrypting. + + - [ ] TBD +--- +- [ ] pass git git-command-args... + If the password store is a git repository, execute a git command + specified by git-command-args. + - [ ] TBD +--- +- [ ] pass help + Show this text. + + - [ ] TBD +--- +- [ ] pass version + Show version information. + - [ ] TBD +--- + +## Gpg + +TODO diff --git a/README.md b/README.md index 03fbce7..c136dbf 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,20 @@ For more options/details see the [clickable documentation](http://clickable.bhdo * ```clickable third_clean ``` : clean third parties * ```clickable style ``` : reformat the code (Required [astyle](astyle.sourceforge.ne) & [qmlfmt](https://github.com/jesperhh/qmlfmt) installed) +## Testing + +To switch to the tests build you need to add the following arguments to the build command : -DTEST_RUNNER=ON. + +To do so with clickable you need to use the following commands: +``` +export CLICKABLE_BUILD_ARGS='-DTEST_RUNNER=ON' +clickable +``` +To comeback to the app build unset the environnment variable : +``` +unset CLICKABLE_BUILD_ARGS +``` + # Contributing Any contributions are welcome using the github issue & pull request system. @@ -25,80 +39,8 @@ Please respect the code style format by running ```clickable style``` before com # Features -The goal is to be closest possible of the features offer by [ZX2C4’s pass command line application](https://www.passwordstore.org/): - ---- -- [ ] pass init [--path=subfolder,-p subfolder] gpg-id... - Initialize new password storage and use gpg-id for encryption. - Selectively reencrypt existing passwords using new gpg-id. - - [ ] Interactive dialog to init a new password storage - - [ ] Support subfolder - - [ ] Support reencrypt ---- -- [x] pass [ls] [subfolder] - List passwords. - - [x] UI allowing to navigate through the password store showing the available passwords ---- -- [ ] pass find pass-names... -List passwords that match pass-names - - [ ] Search bar allowing searchs by pass-names ---- -- [ ] pass [show] [--clip[=line-number],-c[line-number]] pass-name - Show existing password and optionally put it on the clipboard. - If put on the clipboard, it will be cleared in 45 seconds. - - [ ] Click on a pass show the password - - [ ] Add option to put the password to the clipboard - - [ ] Clearing clipboard after 45 secs - - [ ] Line number option ??? ---- -- [ ] pass grep search-string - Search for password files containing search-string when decrypted. - - [ ] TBD ---- -- [ ] pass insert [--echo,-e | --multiline,-m] [--force,-f] pass-name - Insert new password. Optionally, echo the password back to the console - during entry. Or, optionally, the entry may be multiline. Prompt before - overwriting existing password unless forced. - - [ ] TBD ---- -- [ ] pass edit pass-name - Insert a new password or edit an existing password using editor. - - [ ] TBD ---- -- [ ] pass generate [--no-symbols,-n] [--clip,-c] [--in-place,-i | --force,-f] pass-name [pass-length] - Generate a new password of pass-length (or 25 if unspecified) with optionally no symbols. - Optionally put it on the clipboard and clear board after 45 seconds. - Prompt before overwriting existing password unless forced. - Optionally replace only the first line of an existing file with a new password. - - [ ] TBD ---- -- [ ] pass rm [--recursive,-r] [--force,-f] pass-name - Remove existing password or directory, optionally forcefully. - - [ ] TBD ---- -- [ ] pass mv [--force,-f] old-path new-path - Renames or moves old-path to new-path, optionally forcefully, selectively reencrypting. - - [ ] TBD ---- -- [ ] pass cp [--force,-f] old-path new-path - Copies old-path to new-path, optionally forcefully, selectively reencrypting. - - - [ ] TBD ---- -- [ ] pass git git-command-args... - If the password store is a git repository, execute a git command - specified by git-command-args. - - [ ] TBD ---- -- [ ] pass help - Show this text. - - - [ ] TBD ---- -- [ ] pass version - Show version information. - - [ ] TBD ---- +The goal is to be closest possible of the features offer by [ZX2C4’s pass command line application](https://www.passwordstore.org/). +See to the FEATURES.MD file for detailed comparison. # License diff --git a/clickable.json b/clickable.json index c4eceeb..876ad2b 100644 --- a/clickable.json +++ b/clickable.json @@ -4,8 +4,8 @@ "scripts": { "third_build": "clickable run 'cd third && ./clean.sh && mkdir build && cd build && cmake .. && make'", "third_build_d": "clickable run 'cd third && ./clean.sh && mkdir build && cd build && cmake .. && make' --arch amd64 ", - "third_clean": "cd third && ./clean.sh", - "style": "astyle --options=.astylerc \"plugins/*.cpp,*.h\" && qmlfmt -w tests && qmlfmt -w qml" + "third_clean": "cd third && rm -rf local && ./clean.sh", + "style": "astyle --options=.astylerc 'plugins/*.cpp,*.h' && qmlfmt -w tests && qmlfmt -w qml" }, "dependencies_build": [ "texinfo", diff --git a/main.cpp b/main.cpp index c231f89..c052ff6 100644 --- a/main.cpp +++ b/main.cpp @@ -5,16 +5,27 @@ #include #include + +#ifdef TEST_RUNNER +#include +#endif + int main(int argc, char *argv[]) { - new QGuiApplication(argc, argv); - QGuiApplication::setApplicationName("utpass.qrouland"); - +#ifndef TEST_RUNNER qDebug() << "Starting app from main.cpp"; + new QGuiApplication(argc, argv); + QGuiApplication::setApplicationName("utpass.qrouland"); + auto *view = new QQuickView(); view->setSource(QUrl(QStringLiteral("qml/Main.qml"))); view->show(); - + return QGuiApplication::exec(); +#else + qDebug() << "Starting tests from main.cpp"; + return quick_test_main(argc, argv, "Tests", "tests/unit"); +#endif + } diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 40b39c2..d048f9b 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -1,3 +1,2 @@ -add_subdirectory(Gpg) add_subdirectory(Pass) diff --git a/plugins/Gpg/CMakeLists.txt b/plugins/Gpg/CMakeLists.txt deleted file mode 100644 index 18895bc..0000000 --- a/plugins/Gpg/CMakeLists.txt +++ /dev/null @@ -1,47 +0,0 @@ -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") -set(PLUGIN "Gpg") - -set( - SRC - plugin.cpp - gpg.cpp -) - -set(CMAKE_AUTOMOC ON) - -add_library(${PLUGIN} MODULE ${SRC}) -set_target_properties(${PLUGIN} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PLUGIN}) -qt5_use_modules(${PLUGIN} Qml Quick DBus) - -set(EXTERNAL_LIBS "${CMAKE_SOURCE_DIR}/third/local/${ARCH_TRIPLET}") -set(THIRD_PATH "${CMAKE_CURRENT_SOURCE_DIR}") - -INCLUDE_DIRECTORIES(${EXTERNAL_LIBS}/include) - -add_library(GpgError STATIC IMPORTED) -set_property(TARGET GpgError PROPERTY IMPORTED_LOCATION "${EXTERNAL_LIBS}/lib/libgpg-error.a") - -add_library(GpgAssuan STATIC IMPORTED) -set_property(TARGET GpgAssuan PROPERTY IMPORTED_LOCATION "${EXTERNAL_LIBS}/lib/libassuan.a") - -add_library(Gpgme STATIC IMPORTED) -set_property(TARGET Gpgme PROPERTY IMPORTED_LOCATION "${EXTERNAL_LIBS}/lib/libgpgme.a") - -add_library(Gpgmepp STATIC IMPORTED) -set_property(TARGET Gpgmepp PROPERTY IMPORTED_LOCATION "${EXTERNAL_LIBS}/lib/libgpgmepp.a") - -add_library(QGpgme STATIC IMPORTED) -set_property(TARGET QGpgme PROPERTY IMPORTED_LOCATION "${EXTERNAL_LIBS}/lib/libqgpgme.a") - -target_link_libraries(${PLUGIN} QGpgme Gpgmepp Gpgme GpgAssuan GpgError) - -execute_process( - COMMAND dpkg-architecture -qDEB_HOST_MULTIARCH - OUTPUT_VARIABLE ARCH_TRIPLET - OUTPUT_STRIP_TRAILING_WHITESPACE -) - -set(QT_IMPORTS_DIR "/lib/${ARCH_TRIPLET}") - -install(TARGETS ${PLUGIN} DESTINATION ${QT_IMPORTS_DIR}/${PLUGIN}/) -install(FILES qmldir DESTINATION ${QT_IMPORTS_DIR}/${PLUGIN}/) diff --git a/plugins/Gpg/gpg.h b/plugins/Gpg/gpg.h deleted file mode 100644 index b614212..0000000 --- a/plugins/Gpg/gpg.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef PASS_H -#define PASS_H - -#include - -class Gpg : public QObject -{ - Q_OBJECT - -public: - Gpg(); - ~Gpg() override = default; - - Q_INVOKABLE void listDir(); - Q_INVOKABLE QString getKeyId(QString uid); - Q_INVOKABLE QStringList getAllKeysId(); - Q_INVOKABLE bool importKey(QString path); - Q_INVOKABLE QString decrypt(QByteArray plainText); - Q_INVOKABLE QString decryptFile(QString path); - Q_INVOKABLE QByteArray encrypt(QString str); - Q_INVOKABLE bool encryptFile(QString str, QString path); -}; - -#endif diff --git a/plugins/Gpg/plugin.cpp b/plugins/Gpg/plugin.cpp deleted file mode 100644 index 936ce60..0000000 --- a/plugins/Gpg/plugin.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include - -#include "plugin.h" -#include "gpg.h" - -void GpgPlugin::registerTypes(const char *uri) -{ - //@uri Pass - qmlRegisterSingletonType(uri, 1, 0, "Gpg", [](QQmlEngine *, QJSEngine *) -> QObject * { return new Gpg; }); -} diff --git a/plugins/Gpg/plugin.h b/plugins/Gpg/plugin.h deleted file mode 100644 index a4d0ed6..0000000 --- a/plugins/Gpg/plugin.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef GPGPLUGIN_H -#define GPGPLUGIN_H - -#include - -class GpgPlugin : public QQmlExtensionPlugin -{ - Q_OBJECT - Q_PLUGIN_METADATA(IID - "org.qt-project.Qt.QQmlExtensionInterface") - -public: - void registerTypes(const char *uri); -}; - -#endif diff --git a/plugins/Gpg/qmldir b/plugins/Gpg/qmldir deleted file mode 100644 index da374a6..0000000 --- a/plugins/Gpg/qmldir +++ /dev/null @@ -1,2 +0,0 @@ -module Gpg -plugin Gpg diff --git a/plugins/Pass/CMakeLists.txt b/plugins/Pass/CMakeLists.txt index 3ab033a..10a4b8e 100644 --- a/plugins/Pass/CMakeLists.txt +++ b/plugins/Pass/CMakeLists.txt @@ -5,21 +5,48 @@ set( SRC plugin.cpp pass.cpp + gpg.cpp ) set(CMAKE_AUTOMOC ON) -add_library(${PLUGIN} MODULE ${SRC}) -set_target_properties(${PLUGIN} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PLUGIN}) -qt5_use_modules(${PLUGIN} Qml Quick DBus) - - execute_process( COMMAND dpkg-architecture -qDEB_HOST_MULTIARCH OUTPUT_VARIABLE ARCH_TRIPLET OUTPUT_STRIP_TRAILING_WHITESPACE ) +if(ARCH_TRIPLET STREQUAL "") + set(ARCH_TRIPLET x86_64-linux-gnu) +endif() + +add_library(${PLUGIN} MODULE ${SRC}) +set_target_properties(${PLUGIN} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PLUGIN}) +qt5_use_modules(${PLUGIN} Qml Quick DBus) + +set(EXTERNAL_LIBS "${CMAKE_SOURCE_DIR}/third/local/${ARCH_TRIPLET}") +set(THIRD_PATH "${CMAKE_CURRENT_SOURCE_DIR}") + +INCLUDE_DIRECTORIES(${EXTERNAL_LIBS}/include) + +add_library(GpgError STATIC IMPORTED) +set_property(TARGET GpgError PROPERTY IMPORTED_LOCATION "${EXTERNAL_LIBS}/lib/libgpg-error.a") + +add_library(GpgAssuan STATIC IMPORTED) +set_property(TARGET GpgAssuan PROPERTY IMPORTED_LOCATION "${EXTERNAL_LIBS}/lib/libassuan.a") + +add_library(Gpgme STATIC IMPORTED) +set_property(TARGET Gpgme PROPERTY IMPORTED_LOCATION "${EXTERNAL_LIBS}/lib/libgpgme.a") + +add_library(Gpgmepp STATIC IMPORTED) +set_property(TARGET Gpgmepp PROPERTY IMPORTED_LOCATION "${EXTERNAL_LIBS}/lib/libgpgmepp.a") + +add_library(QGpgme STATIC IMPORTED) +set_property(TARGET QGpgme PROPERTY IMPORTED_LOCATION "${EXTERNAL_LIBS}/lib/libqgpgme.a") + +target_link_libraries(${PLUGIN} QGpgme Gpgmepp Gpgme GpgAssuan GpgError) + + set(QT_IMPORTS_DIR "/lib/${ARCH_TRIPLET}") install(TARGETS ${PLUGIN} DESTINATION ${QT_IMPORTS_DIR}/${PLUGIN}/) diff --git a/plugins/Gpg/gpg.cpp b/plugins/Pass/gpg.cpp similarity index 51% rename from plugins/Gpg/gpg.cpp rename to plugins/Pass/gpg.cpp index 01dea9d..efbc299 100644 --- a/plugins/Gpg/gpg.cpp +++ b/plugins/Pass/gpg.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "gpg.h" #include "gpgme++/global.h" @@ -13,6 +14,7 @@ #include "qgpgme/importjob.h" #include "gpgme++/importresult.h" #include "qgpgme/decryptjob.h" +#include "qgpgme/encryptjob.h" using namespace GpgME; using namespace QGpgME; @@ -26,13 +28,7 @@ Gpg::Gpg() qFatal("GpgME init fail"); } - QString gnuhome = QStandardPaths::writableLocation( - QStandardPaths::AppDataLocation).append("/gpghome"); - QDir dir(gnuhome); - if (!dir.exists()) - dir.mkpath("."); - qputenv("GNUPGHOME", gnuhome.toStdString().c_str()); - qDebug() << "GNUPGHOME is :" << qgetenv("GNUPGHOME"); + setGpghome(); error = checkEngine(OpenPGP); if (error) { @@ -43,86 +39,99 @@ Gpg::Gpg() qDebug() << "GpgME Engine Version :" << engineInfo(OpenPGP).version(); } -void Gpg::listDir() +bool Gpg::setGpghome(QString path) { - qDebug() << "hello world!"; + QFileInfo file(path); + if (file.isFile()) { + qWarning() << "Not a directory GNUPGHOME not change"; + return false; + } + if (file.isDir() and !file.isWritable()) { + qWarning() << "Not a writable directory GNUPGHOME not change"; + return false; + } + QDir dir(path); + if (!dir.exists()) { + dir.mkpath("."); + } + m_gpghome = dir; + qputenv("GNUPGHOME", m_gpghome.absolutePath().toStdString().c_str()); + qDebug() << "GNUPGHOME is :" << qgetenv("GNUPGHOME"); } QString Gpg::decrypt(const QByteArray cipherText) { - /*auto decJob = openpgp()->decryptJob(); - auto ctx = DecryptJob::context(decJob);*/ + /* + auto decJob = openpgp()->decryptJob(); + auto ctx = DecryptJob::context(decJob); - /* TODO - * TestPassphraseProvider provider; + + TestPassphraseProvider provider; ctx->setPassphraseProvider(&provider); - ctx->setPinentryMode(Context::PinentryLoopback);*/ + ctx->setPinentryMode(Context::PinentryLoopback); - /*QByteArray plainText; + QByteArray plainText; decJob->exec(cipherText, plainText); return QString::fromUtf8(plainText);*/ } -QString Gpg::decryptFile(QString path) +QString Gpg::decryptFromFile(QString path) { - /*QFile file(path); + QFile file(path); if (!file.open(QIODevice::ReadOnly)) { - qErrnoWarning("Can't open the File"); + qWarning() << "Can't open the File"; return nullptr; } QByteArray plainText = file.readAll(); - return this->decrypt(plainText);*/ + return decrypt(plainText); } -QByteArray Gpg::encrypt(const QString str) +QByteArray Gpg::encrypt(QString str, QString uid, bool ascii_armor, bool text_mode) { - /*auto listjob = openpgp()->keyListJob(false, false, false); - std::vector keys; - auto keylistresult = listjob->exec(QStringList() << QStringLiteral("alfa@example.net"), false, keys); + qDebug() << "Encrypt to QByteArray"; + + auto keys = getKeys(uid); auto job = openpgp() - ->encryptJob( - true, //ASCII Armor - true //Textmode - ); + ->encryptJob( + ascii_armor, + text_mode + ); QByteArray cipherText; - auto result = job->exec(keys, QStringLiteral("Hello World").toUtf8(), Context::AlwaysTrust, cipherText); - return cipherText;*/ + //auto result = job->exec(keys, str.toUtf8(), Context::AlwaysTrust, cipherText); + return cipherText; } -bool Gpg::encryptFile(QString str, QString path) +bool Gpg::encryptToFile(QString str, QString path, const QString uid, bool ascii_armor, + bool text_mode) { - /*QFile file(path); + qDebug() << "Encrypt to file " << path; + QFile file(path); if (!file.open(QIODevice::WriteOnly)) { - qErrnoWarning("Can't open the File"); + qWarning() << "Can't open the file to write it" ; return false; } - file.write(Pass::encrypt(str)); - return true;*/ + file.write(encrypt(str, uid, ascii_armor, text_mode)); + return true; } -QString Gpg::getKeyId(QString uid) +std::vector Gpg::getKeys(QString pattern_uid, bool remote, bool include_sigs, bool validate) { - qDebug() << "Getting the key id " << uid; - auto *job = openpgp()->keyListJob(false, false, false); + qDebug() << "Getting the key " << pattern_uid; + auto *job = openpgp()->keyListJob(remote, include_sigs, validate); + std::vector keys; - auto result = job->exec(QStringList() << uid, false, keys); + auto result = job->exec(QStringList() << pattern_uid, false, keys); delete job; - if (keys.empty()) { - qDebug() << "No key found for" << uid; - return nullptr; - } - const QString kId = QLatin1String(keys.front().keyID()); - qDebug() << "Id key for " << uid << "is : " << kId; - return kId; + return keys; } -QStringList Gpg::getAllKeysId() +QStringList Gpg::getAllKeysId(bool remote, bool include_sigs, bool validate) { qDebug() << "Show all available key"; - auto job = openpgp()->keyListJob(false, false, false); + auto job = openpgp()->keyListJob(remote, remote, validate); std::vector keys; auto result = job->exec(QStringList(""), false, keys); delete job; @@ -140,7 +149,7 @@ QStringList Gpg::getAllKeysId() return r; } -bool Gpg::importKey(QString path) +bool Gpg::importKeyFromFile(QString path) { qDebug() << "Importing the key file" << path; QFile file(path); @@ -159,15 +168,5 @@ bool Gpg::importKey(QString path) qWarning() << "Import go wrong"; return false; } - - qDebug() << "Key imported" << result.numImported(); - qDebug() << "Key not imported" << result.notImported(); - qDebug() << "Key unchanged" << result.numUnchanged(); - qDebug() << "Result null" << result.isNull(); - qDebug() << "newUserIDs" << result.newUserIDs(); - - for (const auto &key : result.imports()) - qDebug() << "Key" << key.fingerprint(); - - return true; -} \ No newline at end of file + return result.imports().size() == 1; +} diff --git a/plugins/Pass/gpg.h b/plugins/Pass/gpg.h new file mode 100644 index 0000000..c7d4e86 --- /dev/null +++ b/plugins/Pass/gpg.h @@ -0,0 +1,38 @@ +#ifndef GPG_H +#define GPG_H + +#include +#include "gpgme++/key.h" + +class Gpg +{ +private: + Gpg(); + QDir m_gpghome; + +public: + ~Gpg(); + static std::shared_ptr instance() + { + static std::shared_ptr s{new Gpg}; + return s; + } + Gpg(Gpg const &) = delete; + void operator=(Gpg const &) = delete; + + + QString decrypt(QByteArray plainText); + QString decryptFromFile(QString path); + QByteArray encrypt(QString str, QString uid, bool ascii_armor = true, bool text_mode = true); + bool encryptToFile(QString str, QString path, const QString uid, bool ascii_armor = true, + bool text_mode = true); + bool importKeyFromFile(QString path); + std::vector getKeys(QString pattern_uid, bool remote = false, bool include_sigs = false, + bool validate = false); + QStringList getAllKeysId(bool remote = false, bool include_sigs = false, bool validate = false); + + bool setGpghome(QString path = QStandardPaths::writableLocation( + QStandardPaths::AppDataLocation).append("/gpghome")); +}; + +#endif diff --git a/plugins/Pass/pass.cpp b/plugins/Pass/pass.cpp index 424929f..56d4eee 100644 --- a/plugins/Pass/pass.cpp +++ b/plugins/Pass/pass.cpp @@ -4,17 +4,30 @@ #include #include "pass.h" +#include "gpg.h" -Pass::Pass(){ - pass_store = QStandardPaths::writableLocation( - QStandardPaths::AppDataLocation).append("/.password-store"); - QDir dir(pass_store); + +Pass::Pass() +{ + m_password_store = QStandardPaths::writableLocation( + QStandardPaths::AppDataLocation).append("/.password-store"); + QDir dir(m_password_store); if (!dir.exists()) dir.mkpath("."); - qDebug() << "Password Store is :" << pass_store; + qDebug() << "Password Store is :" << m_password_store; } -void Pass::speak() +bool Pass::gpgImportKeyFromFile(QString path) { - qDebug() << "Starting app from main.cpp"; + return Gpg::instance()->importKeyFromFile(path); +} + +QStringList Pass::gpgListAllKeys() +{ + return Gpg::instance()->getAllKeysId(); +} + +bool Pass::gpgSetGpghome(QString path) +{ + return Gpg::instance()->setGpghome(path); } diff --git a/plugins/Pass/pass.h b/plugins/Pass/pass.h index 5dd5732..5f707d5 100644 --- a/plugins/Pass/pass.h +++ b/plugins/Pass/pass.h @@ -2,17 +2,19 @@ #define PASS_H #include +#include class Pass : public QObject { Q_OBJECT - QString pass_store; + QString m_password_store; public: Pass(); ~Pass() override = default; - - Q_INVOKABLE void speak(); + Q_INVOKABLE bool gpgImportKeyFromFile(QString path); + Q_INVOKABLE QStringList gpgListAllKeys(); + Q_INVOKABLE bool gpgSetGpghome(QString path); }; #endif diff --git a/po/utpass.qrouland.pot b/po/utpass.qrouland.pot index c4ffb32..29977b9 100644 --- a/po/utpass.qrouland.pot +++ b/po/utpass.qrouland.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: utpass.qrouland\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-03-22 16:39+0000\n" +"POT-Creation-Date: 2019-03-23 19:50+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,10 +17,6 @@ msgstr "" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" -#: ../qml/pages/Settings.qml:8 ../qml/pages/headers/MainHeader.qml:49 -msgid "Settings" -msgstr "" - #: ../qml/pages/PasswordList.qml:16 msgid "Back" msgstr "" @@ -29,7 +25,21 @@ msgstr "" msgid "No password found in the current folder" msgstr "" -#: ../qml/pages/Info.qml:10 ../qml/pages/headers/MainHeader.qml:56 +#: ../qml/pages/Settings.qml:8 ../qml/pages/headers/MainHeader.qml:49 +msgid "Settings" +msgstr "" + +#: ../qml/pages/headers/MainHeader.qml:8 ../qml/pages/headers/StackHeader.qml:8 +#: UTPass.desktop.in.h:1 +msgid "UTPass" +msgstr "" + +#: ../qml/pages/headers/MainHeader.qml:23 +#: ../qml/pages/headers/MainHeader.qml:38 +msgid "Search" +msgstr "" + +#: ../qml/pages/headers/MainHeader.qml:56 ../qml/pages/Info.qml:10 msgid "Info" msgstr "" @@ -56,13 +66,3 @@ msgstr "" #: ../qml/pages/Info.qml:75 msgid "Released under the terms of the GNU GPL v3" msgstr "" - -#: ../qml/pages/headers/MainHeader.qml:8 ../qml/pages/headers/StackHeader.qml:8 -#: UTPass.desktop.in.h:1 -msgid "UTPass" -msgstr "" - -#: ../qml/pages/headers/MainHeader.qml:23 -#: ../qml/pages/headers/MainHeader.qml:38 -msgid "Search" -msgstr "" diff --git a/qml/Main.qml b/qml/Main.qml index 5d24f0f..feab6f2 100644 --- a/qml/Main.qml +++ b/qml/Main.qml @@ -1,7 +1,6 @@ import QtQuick 2.4 import QtQuick.Layouts 1.1 import Ubuntu.Components 1.3 -import Gpg 1.0 import "components" @@ -23,7 +22,6 @@ MainView { "pages/PasswordList.qml"))) } Component.onCompleted: { - Gpg.importKey("password-store/public.key") - Gpg.getAllKeysId() + } } diff --git a/qml/models/Manifest.qml b/qml/models/Manifest.qml index bdfb882..059b2b0 100644 --- a/qml/models/Manifest.qml +++ b/qml/models/Manifest.qml @@ -2,30 +2,30 @@ pragma Singleton import QtQuick 2.4 Item { - id: manifest - property string name : "" - property string description : "" - property string architecture : "" - property string title : "" - property string version : "" - property string maintainer : "" - property string framework : "" - + id: manifest + property string name: "" + property string description: "" + property string architecture: "" + property string title: "" + property string version: "" + property string maintainer: "" + property string framework: "" + Component.onCompleted: { - var xhr = new XMLHttpRequest; - xhr.open("GET", "../../manifest.json"); - xhr.onreadystatechange = function() { - if (xhr.readyState == XMLHttpRequest.DONE) { - var mJson = JSON.parse(xhr.responseText); - manifest.name = mJson.name - manifest.description = mJson.description - manifest.architecture = mJson.architecture - manifest.title = mJson.title - manifest.version = mJson.version - manifest.maintainer = mJson.maintainer - manifest.framework = mJson.framework - } - }; - xhr.send(); - } + var xhr = new XMLHttpRequest + xhr.open("GET", "../../manifest.json") + xhr.onreadystatechange = function () { + if (xhr.readyState == XMLHttpRequest.DONE) { + var mJson = JSON.parse(xhr.responseText) + manifest.name = mJson.name + manifest.description = mJson.description + manifest.architecture = mJson.architecture + manifest.title = mJson.title + manifest.version = mJson.version + manifest.maintainer = mJson.maintainer + manifest.framework = mJson.framework + } + } + xhr.send() + } } diff --git a/qml/pages/Info.qml b/qml/pages/Info.qml index 47b9b85..1cfeea8 100644 --- a/qml/pages/Info.qml +++ b/qml/pages/Info.qml @@ -19,16 +19,17 @@ Page { Flow { spacing: units.gu(3) anchors.fill: parent - Rectangle { + Rectangle { width: parent.width height: units.gu(1) } - Text { + Text { horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter + verticalAlignment: Text.AlignVCenter width: parent.width height: units.gu(4) - text: i18n.tr('%1').arg(Manifest.title) + text: i18n.tr('%1').arg( + Manifest.title) } Rectangle { width: parent.width @@ -40,40 +41,44 @@ Page { anchors.horizontalCenter: parent.horizontalCenter } } - Text { + Text { horizontalAlignment: Text.AlignHCenter width: parent.width height: units.gu(6) - text: i18n.tr('Maintainer
%1
').arg(Manifest.maintainer) + text: i18n.tr('Maintainer
%1
').arg( + Manifest.maintainer) } Text { horizontalAlignment: Text.AlignHCenter width: parent.width height: units.gu(6) - text: i18n.tr('Version
%1

%2@%3').arg(Manifest.version).arg(Manifest.framework).arg(Manifest.architecture) + text: i18n.tr( + 'Version
%1

%2@%3').arg( + Manifest.version).arg(Manifest.framework).arg( + Manifest.architecture) } - } - Flow { - spacing: 2 + Flow { + spacing: 2 anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.left: parent.left - Link { + anchors.right: parent.right + anchors.left: parent.left + Link { url: "https://github.com/QRouland/UTPass/issues" text: i18n.tr('Suggest improvement(s) or report a bug(s)') } - Link { + Link { url: "https://github.com/QRouland/UTPass" text: i18n.tr('Access to the source code') } - Text { - width: parent.width + Text { + width: parent.width height: units.gu(3) - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - text: i18n.tr('Released under the terms of the GNU GPL v3') + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + text: i18n.tr( + 'Released under the terms of the GNU GPL v3') } - } + } } } diff --git a/tests/unit/assets/not.key b/tests/unit/assets/not.key new file mode 100644 index 0000000..8a04eff --- /dev/null +++ b/tests/unit/assets/not.key @@ -0,0 +1 @@ +This not a key file diff --git a/tests/unit/assets/public1.key b/tests/unit/assets/public1.key new file mode 100644 index 0000000..30d6554 --- /dev/null +++ b/tests/unit/assets/public1.key @@ -0,0 +1,30 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFyNdOEBCADl2oWeYkmVBDWoWgZdkpbV5VRJJFATLsu5aHBuQO/C1mn2RlL2 +jIpIzI5mwviAw9RN0KnLdHvp3n3JkJPZ8tB3Sk9SD8qhr6ae2DbIpySMscYC9+Go +t0mrGyB3w+Y5etfZ/1dRNx6/vYaWYIG6bKfJettt/zLJcjpkIKcrN4OKyN2wXz3y +EiAiJvMntdgLslURl93RyNuVR6UaE4TchtDqRc2KvXAxrf6NUYd4KxvUgUd0TFPs +s3SRs+cAcRmTzxv/c40sBw3z0B9rBB7T7oPgGUA6NhErvBwpF9MLN+6ucZ1HHLLH +dmCd7q2OT7wZ9L6zILmKvJcK13V4FyO9zOALABEBAAG0I1Rlc3QgKFRlc3QgZ3Bn +IGtleSkgPHRlc3RAdGVzdC5vcmc+iQFOBBMBCAA4FiEE6JXydyCXAQnkfMmTuV1W +R67EDnIFAlyNdOECGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQuV1WR67E +DnIchAgAvV5q4/Hktlu3RIgo8KkGksMOS5XhJrr6fZ8bUgqpwL3oEfZ3Se5aS7yN +5M7NT8foB2zK2moBICMYpxBxQoGjxFosv94FqX9+XMiRc2Di6MwLwKkWfu0HoPEi +e701iTo53r6K84TIKNrRsKyg6C/pRqNNwSp1YcvG11eUnG5teZMcb1xsMfx4O2/s +mcrySo0ZjAfYnh2poxf4yy9xTryPrDnaY/EFj+4uBMLH8jG3QQiAH1N6wHhi/vwj +FaaaBtRxcVU/obGDg2LHTVxItv81xzSbLf8JdIGOKjwFed+DjaoSlEzPnuaEZf/M +7mE6fiGbIhFgUlwEGomptZDC1fg3M7kBDQRcjXThAQgAmuSsbRoLfiSoij5CWiP4 +UUvhIEt5d4KkMRRvuWXkJo4FWs2tmNWIb1tiXuKhX+puLjP06LwfEyNT1jz75pgO +tSQ0Yz55Hn25CWOcyWF/iIoIjjw3WhQ0a59Ajk8tVdVrTEhlcQ+m7dH1igyMO1vv +iH1eTu+TXqWDF1+oYTZH0iTMYreCNbz2RcFHZQKdWK8GI1DE/qeKLHf+XYGTVQH4 +fRnGaX7T5DdnklHKVGi4iILOKn5aofTIg14roS9yfDMK6vmNr7BkzqAe9+WfYC0K +TU3hX1z5SrjnYnBb531MaaCotUEI3DbNeoNsuH3Hx0WLHR1Q55Hh+KAxhMUKTR4P +uwARAQABiQE2BBgBCAAgFiEE6JXydyCXAQnkfMmTuV1WR67EDnIFAlyNdOECGwwA +CgkQuV1WR67EDnKjKwgA3yexUAoTe9sDRKO710MSWhPAn3DZ8qMo8EqmNegG86PO +/mD6BPKo9503pqGGXYoFBcqsmFX07uvy0evCsqO15xuDWwOhNX5fm2LeSsNEkhhC +2wvJVQPdekj9KmOrRRRcr6DlR0Yl7+BJX1+zF8tYwtU4tiY+bCOVRoa1KvTXUwcy +sRTQ9xWguCP8Ai1GyZS0P8lEU0nCS2KrgU/XKQVW7o2OtBiywJbmVCDw15vIq3kN +akRrU5DvYCelUjjzgj+HC3MEE5fV4UsuFLKw3QMmekzFfa6OagRb/FYYZ5ZL+tGI +cf2W57AJOHpgYvxqrY5M1UfKLf8kCYPt3AG0XiD5mw== +=OHyE +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tests/unit/tst_gpg.qml b/tests/unit/tst_gpg.qml index 8f80ffd..df2aef6 100644 --- a/tests/unit/tst_gpg.qml +++ b/tests/unit/tst_gpg.qml @@ -1,10 +1,39 @@ import QtTest 1.0 import Ubuntu.Test 1.0 -import Gpg 1.0 +import Pass 1.0 UbuntuTestCase { - name: "GpgTests" - function test_empty_gnuhome() { - Gpg::getListIds().empty() - } + name: "GpgTests" + function initTestCase() { + Pass.gpgSetGpghome("tests/tmp/gnuhome") + } + + function test_empty_gnuhome() { + var listKeys = Pass.gpgListAllKeys() + verify(listKeys.length == 0) + } + + function test_import_key_form_file_data() { + return [{ + "tag": "public1.key", + "path": "tests/unit/assets/public1.key", + "answer": true + }, { + "tag": "private1.key", + "path": "tests/unit/assets/private1.key", + "answer": true + }, { + "tag": "bad path", + "path": "this/is/a/bad/path", + "answer": false + }, { + "tag": "bad key", + "path": "tests/unit/assets/not.key", + "answer": false + }] + } + + function test_import_key_form_file(data) { + compare(Pass.gpgImportKeyFromFile(data.path), data.answer) + } } diff --git a/third/CMakeLists.txt b/third/CMakeLists.txt index 688d16f..ec6b8e3 100644 --- a/third/CMakeLists.txt +++ b/third/CMakeLists.txt @@ -62,4 +62,4 @@ ExternalProject_Add( BUILD_IN_SOURCE 1 BUILD_COMMAND make INSTALL_COMMAND make install -) \ No newline at end of file +)