1
0
mirror of https://github.com/QRouland/UTPass.git synced 2025-07-03 02:32:28 +00:00

57 Commits

Author SHA1 Message Date
20aff3a404 Some bugs fix 2025-01-20 15:03:34 +01:00
ebfc6f500d Add delete password store feature 2025-01-20 14:46:54 +01:00
0eb8920856 Fix delete gpg key 2025-01-20 11:24:38 +01:00
c0757da47b Refactor cloning feature and ui 2025-01-17 10:40:54 +01:00
beaad58af2 Add docsting for pass plugin 2025-01-15 23:40:35 +01:00
e589abd10c Refactor gpg for more clean job handling 2025-01-15 23:15:00 +01:00
00116aea8c Fix am issue with page stack titles when navigationg the password list 2025-01-14 14:02:13 +01:00
fd3ab95b27 Style 2025-01-14 13:05:09 +01:00
200964246e Improve gpg key infos 2025-01-14 12:20:55 +01:00
0e5df76787 Harmonize naming 2025-01-14 09:52:49 +01:00
8ec593becc Update UI Color with recommandation and fix the issues with dark theme 2025-01-13 20:51:29 +01:00
b022e30a89 Fix Cmake issue 2025-01-13 18:26:48 +01:00
063f66e99a Some cleanup 2025-01-13 18:11:16 +01:00
6ac11e2da7 Add initial support for http git clone with authentification 2025-01-13 17:59:08 +01:00
46145241fc Setup tests 2025-01-13 10:51:27 +01:00
399173b776 Fix style command 2025-01-10 15:28:42 +01:00
365e530efc Update repo and issues links 2025-01-10 15:01:38 +01:00
3aa99791b8 Use tmp dir during clone to keep orignal passstore in case of error 2025-01-10 14:41:04 +01:00
80a5055b78 Some minors fix 2025-01-10 13:58:46 +01:00
5e5a092da1 Initial git clone feature 2025-01-10 13:48:38 +01:00
d33932be6d Setup git libs for Pass plugin 2025-01-07 22:08:46 +01:00
7418894456 Fix import shared library libqgpgme 2025-01-07 22:07:43 +01:00
63d53caad5 Update Readme wiki links 2025-01-07 20:46:15 +01:00
62c19f8ff9 Create FUNDING.yml 2025-01-07 16:44:14 +00:00
37c481ece1 Update build : use prebuild binaries + Add libgit2 2025-01-07 14:41:48 +01:00
c6d7a025ff Fix loading password before Pass plugin init 2023-06-19 14:58:09 -04:00
cec4e7dabc Update to configuration to clickable 7 2023-06-17 21:53:54 -04:00
a9b979b011 It can be useful for people that are not really into gpg/pass to know
how to export the specficic files needed for UTPass to work properly.
2020-08-03 10:25:24 +02:00
42efea67d8 Add git plugin TG-41 closed 2019-10-07 18:18:49 +02:00
1818d3d7e8 Add libgit2 as third dependencies TG-40 2019-10-07 18:18:49 +02:00
df5321644a bump version 2019-10-07 18:18:49 +02:00
774019f4d0 Bump version 0.0.2 2019-10-07 18:12:13 +02:00
a1b87a7b45 Translated using Weblate (French)
Currently translated at 100.0% (40 of 40 strings)

Translation: UTPass/UTPass
Translate-URL: https://translate-ut.org/projects/utpass/utpass/fr/
2019-10-07 18:07:10 +02:00
5d92b767a7 Translated using Weblate (French)
Currently translated at 100.0% (40 of 40 strings)

Translation: UTPass/UTPass
Translate-URL: https://translate-ut.org/projects/utpass/utpass/fr/
2019-10-07 18:07:10 +02:00
6a419c5a05 Translated using Weblate (French)
Currently translated at 100.0% (40 of 40 strings)

Translation: UTPass/UTPass
Translate-URL: https://translate-ut.org/projects/utpass/utpass/fr/
2019-10-07 18:07:10 +02:00
5bb5d36630 Translated using Weblate (French)
Currently translated at 100.0% (40 of 40 strings)

Translation: UTPass/UTPass
Translate-URL: https://translate-ut.org/projects/utpass/utpass/fr/
2019-10-07 18:07:10 +02:00
3264c26358 Translated using Weblate (French)
Currently translated at 100.0% (40 of 40 strings)

Translation: UTPass/UTPass
Translate-URL: https://translate-ut.org/projects/utpass/utpass/fr/
2019-10-07 18:07:10 +02:00
d53a012565 Update headers translation files 2019-10-07 18:07:10 +02:00
49c992aa8d Translated using Weblate (Catalan)
Currently translated at 100.0% (40 of 40 strings)

Translation: UTPass/UTPass
Translate-URL: https://translate-ut.org/projects/utpass/utpass/ca/
2019-10-07 18:07:10 +02:00
123df71a5b Translated using Weblate (Spanish)
Currently translated at 100.0% (40 of 40 strings)

Translation: UTPass/UTPass
Translate-URL: https://translate-ut.org/projects/utpass/utpass/es/
2019-10-07 18:07:10 +02:00
fab14a395e Translated using Weblate (French)
Currently translated at 100.0% (40 of 40 strings)

Translation: UTPass/UTPass
Translate-URL: https://translate-ut.org/projects/utpass/utpass/fr/
2019-10-07 18:07:10 +02:00
cd3dc273b0 Translated using Weblate (French)
Currently translated at 100.0% (40 of 40 strings)

Translation: UTPass/UTPass
Translate-URL: https://translate-ut.org/projects/utpass/utpass/fr/
2019-10-07 18:07:10 +02:00
30727fde23 Translated using Weblate (Spanish)
Currently translated at 92.5% (37 of 40 strings)

Translation: UTPass/UTPass
Translate-URL: https://translate-ut.org/projects/utpass/utpass/es/
2019-10-07 18:07:10 +02:00
a6113f26ec Translated using Weblate (Spanish)
Currently translated at 92.5% (37 of 40 strings)

Translation: UTPass/UTPass
Translate-URL: https://translate-ut.org/projects/utpass/utpass/es/
2019-10-07 18:07:10 +02:00
09a58ee24b Translated using Weblate (Spanish)
Currently translated at 85.0% (34 of 40 strings)

Translation: UTPass/UTPass
Translate-URL: https://translate-ut.org/projects/utpass/utpass/es/
2019-10-07 18:07:10 +02:00
a722e7c261 Translated using Weblate (Spanish)
Currently translated at 85.0% (34 of 40 strings)

Translation: UTPass/UTPass
Translate-URL: https://translate-ut.org/projects/utpass/utpass/es/
2019-10-07 18:07:09 +02:00
17a533a2ef Translated using Weblate (Spanish)
Currently translated at 77.5% (31 of 40 strings)

Translation: UTPass/UTPass
Translate-URL: https://translate-ut.org/projects/utpass/utpass/es/
2019-10-07 18:07:09 +02:00
98ae4cc3d9 Translated using Weblate (Spanish)
Currently translated at 77.5% (31 of 40 strings)

Translation: UTPass/UTPass
Translate-URL: https://translate-ut.org/projects/utpass/utpass/es/
2019-10-07 18:07:09 +02:00
b48d9381f2 Translated using Weblate (Spanish)
Currently translated at 65.0% (26 of 40 strings)

Translation: UTPass/UTPass
Translate-URL: https://translate-ut.org/projects/utpass/utpass/es/
2019-10-07 18:07:09 +02:00
378e19e205 Translated using Weblate (Spanish)
Currently translated at 65.0% (26 of 40 strings)

Translation: UTPass/UTPass
Translate-URL: https://translate-ut.org/projects/utpass/utpass/es/
2019-10-07 18:07:09 +02:00
2e98d4356b Translated using Weblate (Spanish)
Currently translated at 50.0% (20 of 40 strings)

Translation: UTPass/UTPass
Translate-URL: https://translate-ut.org/projects/utpass/utpass/es/
2019-10-07 18:07:09 +02:00
7e4ef02f8c Translated using Weblate (Spanish)
Currently translated at 50.0% (20 of 40 strings)

Translation: UTPass/UTPass
Translate-URL: https://translate-ut.org/projects/utpass/utpass/es/
2019-10-07 18:07:09 +02:00
418b64a191 Translated using Weblate (Spanish)
Currently translated at 0.0% (0 of 40 strings)

Translation: UTPass/UTPass
Translate-URL: https://translate-ut.org/projects/utpass/utpass/es/
2019-10-07 18:07:09 +02:00
96e79d97c4 Added translation using Weblate (Catalan) 2019-10-07 18:07:09 +02:00
ace251c209 Added translation using Weblate (Spanish) 2019-10-07 18:07:09 +02:00
ae52834946 Merge branch 'master' of Anne17/UTPass into master 2019-09-22 20:17:15 +00:00
4f036e2d95 French translation added 2019-09-22 14:21:58 +00:00
89 changed files with 3490 additions and 1003 deletions

View File

@ -5,8 +5,7 @@ suffix=none
--align-reference=name --align-reference=name
--convert-tabs --convert-tabs
--attach-namespaces --attach-namespaces
--max-code-length=100 --max-code-length=120
--max-instatement-indent=120
--pad-header --pad-header
--pad-oper --pad-oper
--lineend=linux --lineend=linux

2
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,2 @@
ko_fi: qrouland # Replace with a single Ko-fi username

13
.gitignore vendored
View File

@ -1,5 +1,6 @@
# Builds dirs # Builds dirs
build* build*
.cache
# IDE & Devs tools # IDE & Devs tools
.clickable .clickable
@ -10,13 +11,7 @@ build*
*.kdev4 *.kdev4
*swp *swp
scripts scripts
CMakeLists.txt.*
# Third parties ouput dir # venv
libs/third/local venv
# desktop
desktop
# Test (Not Ready yet !)
tests

30
.gitmodules vendored
View File

@ -1,30 +0,0 @@
[submodule "third/gpgme"]
path = libs/gpg/gpgme
url = https://github.com/gpg/gpgme
[submodule "third/libassuan"]
path = libs/gpg/libassuan
url = https://github.com/gpg/libassuan
[submodule "third/libgpg-error"]
path = libs/gpg/libgpg-error
url = https://github.com/gpg/libgpg-error
[submodule "third/gnupg"]
path = libs/gpg/gnupg
url = https://github.com/gpg/gnupg
[submodule "libs/utils/quazip"]
path = libs/quazip
url = https://github.com/stachenov/quazip
[submodule "libs/gpg/gpgme"]
path = libs/gpg/gpgme
url = https://github.com/gpg/gpgme
[submodule "libs/gpg/libassuan"]
path = libs/gpg/libassuan
url = https://github.com/gpg/libassuan
[submodule "libs/gpg/libgpg-error"]
path = libs/gpg/libgpg-error
url = https://github.com/gpg/libgpg-error
[submodule "libs/gpg/gnupg"]
path = libs/gpg/gnupg
url = https://github.com/gpg/gnupg
[submodule "libs/quazip"]
path = libs/quazip
url = https://github.com/stachenov/quazip

View File

@ -3,6 +3,10 @@ cmake_minimum_required(VERSION 3.5.1)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release")
endif()
execute_process( execute_process(
COMMAND dpkg-architecture -qDEB_HOST_ARCH COMMAND dpkg-architecture -qDEB_HOST_ARCH
OUTPUT_VARIABLE CLICK_ARCH OUTPUT_VARIABLE CLICK_ARCH
@ -28,11 +32,22 @@ set(PROJECT_NAME "UTPass")
set(FULL_PROJECT_NAME "utpass.qrouland") set(FULL_PROJECT_NAME "utpass.qrouland")
set(CMAKE_INSTALL_PREFIX /) set(CMAKE_INSTALL_PREFIX /)
set(DATA_DIR /) set(DATA_DIR /)
set(BIN_DIR ${DATA_DIR}lib/bin)
set(DESKTOP_FILE_NAME ${PROJECT_NAME}.desktop) set(DESKTOP_FILE_NAME ${PROJECT_NAME}.desktop)
if(NOT TESTS_PATH)
set(TESTS_PATH "./tests")
endif()
configure_file(main.in.cpp main.cpp)
add_executable(${PROJECT_NAME} main.cpp) add_executable(${PROJECT_NAME} main.cpp)
qt5_use_modules(${PROJECT_NAME} Gui Qml Quick) qt5_use_modules(${PROJECT_NAME} Gui Qml Quick QuickTest)
if(TESTS_RUNNER)
qt5_use_modules(${PROJECT_NAME} QuickTest)
endif()
install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}) install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX})
@ -41,20 +56,13 @@ configure_file(${CMAKE_CURRENT_BINARY_DIR}/manifest.json ${CMAKE_CURRENT_BINARY_
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/manifest.json DESTINATION ${CMAKE_INSTALL_PREFIX}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/manifest.json DESTINATION ${CMAKE_INSTALL_PREFIX})
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/manifest_.json DESTINATION ${DATA_DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/manifest_.json DESTINATION ${DATA_DIR})
#install(DIRECTORY desktop DESTINATION ${DATA_DIR})
install(FILES ${PROJECT_NAME}.apparmor DESTINATION ${DATA_DIR}) install(FILES ${PROJECT_NAME}.apparmor DESTINATION ${DATA_DIR})
install(FILES ${PROJECT_NAME}.contenthub DESTINATION ${DATA_DIR}) install(FILES ${PROJECT_NAME}.contenthub DESTINATION ${DATA_DIR})
install(FILES LICENSE DESTINATION ${DATA_DIR}) install(FILES LICENSE DESTINATION ${DATA_DIR})
install(DIRECTORY qml DESTINATION ${DATA_DIR}) install(DIRECTORY qml DESTINATION ${DATA_DIR})
install(DIRECTORY assets DESTINATION ${DATA_DIR}) install(DIRECTORY assets DESTINATION ${DATA_DIR})
file(GLOB_RECURSE BIN_FILES
"build/${ARCH_TRIPLET}/gpg/local/bin/*")
install(
FILES ${BIN_FILES}
PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
DESTINATION ${BIN_DIR}
)
# Translations # Translations
file(GLOB_RECURSE I18N_SRC_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/po qml/*.qml qml/*.js) file(GLOB_RECURSE I18N_SRC_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/po qml/*.qml qml/*.js)
@ -79,6 +87,9 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${DESKTOP_FILE_NAME} DESTINATION ${DAT
add_subdirectory(po) add_subdirectory(po)
add_subdirectory(plugins) add_subdirectory(plugins)
if(TESTS_RUNNER)
add_subdirectory(tests/plugins)
endif()
add_custom_target(${PROJECT_NAME}_FILES ALL SOURCES ${PROJECT_SRC_FILES}) add_custom_target(${PROJECT_NAME}_FILES ALL SOURCES ${PROJECT_SRC_FILES})

View File

@ -8,35 +8,36 @@ UTPass is avalaible on the [OpenStore](open-store.io)
[![OpenStore](https://open-store.io/badges/en_US.png)](https://open-store.io/app/utpass.qrouland) [![OpenStore](https://open-store.io/badges/en_US.png)](https://open-store.io/app/utpass.qrouland)
## Build & Tests
See [Build & Tests wiki page](https://taiga.rdrive.ovh/project/utpass/wiki/build-tests)
## Contributing & Issues
See [Contributing wiki page](https://taiga.rdrive.ovh/project/utpass/wiki/contributing)
## Features ## Features
The goal is to be closest possible of the features offer by [ZX2C4s pass command line application](https://www.passwordstore.org/). The goal is to be closest possible of the features offer by [ZX2C4s pass command line application](https://www.passwordstore.org/).
See [Features wiki page](https://taiga.rdrive.ovh/project/utpass/wiki/contributing) for details. See [Features wiki page](https://taiga.rdrive.ovh/project/utpass/wiki/contributing) for details.
## License ## Export/Import
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> Assuming that there are already passwords in another device using [ZX2C4s pass command line application](https://www.passwordstore.org/) and, therefore, that [gpg keys](https://gnupg.org/) have been previously generated for encryption purposes, these may be helpful commands:
This program is free software: you can redistribute it and/or modify Export gpg private keys in order to decrypt passwords:
it under the terms of the GNU General Public License as published by ```
the Free Software Foundation, either version 3 of the License, or gpg --output keys.gpg --export-secret-keys
(at your option) any later version. ```
This program is distributed in the hope that it will be useful, Export passwords, assuming they reside in *.password-store* folder:
but WITHOUT ANY WARRANTY; without even the implied warranty of ```
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the zip passwords.zip -r .password-store/
GNU General Public License for more details. ```
You should have received a copy of the GNU General Public License Both files have the correct format for UTPass to import them and work as intended. It is highly recommended to remove them after imported to **UTPass**.
along with this program. If not, see <http://www.gnu.org/licenses/>.
## Build & Tests
See [Build & Tests wiki page](https://github.com/QRouland/UTPass/wiki/Build-&-Tests)
## Contributing & Issues
See [Contributing wiki page](https://github.com/QRouland/UTPass/wiki/Contributing)
## Useful Links ## Useful Links
@ -45,4 +46,23 @@ Some useful links related to UTPass development :
* [Ubports](https://ubports.com/) : Ubuntu Touch Community * [Ubports](https://ubports.com/) : Ubuntu Touch Community
* [ZX2C4s pass command line application](https://www.passwordstore.org/) : the standard unix password manager. * [ZX2C4s pass command line application](https://www.passwordstore.org/) : the standard unix password manager.
* [Clickable](https://github.com/bhdouglass/clickable) : Compile, build, and deploy Ubuntu Touch click packages * [Clickable](https://github.com/bhdouglass/clickable) : Compile, build, and deploy Ubuntu Touch click packages
* [GnuPG](https://gnupg.org/): The GNU Privacy Guard
* [Gpgme](https://www.gnupg.org/software/gpgme/index.html) : GnuPG Made Easy (GPGME) is a library designed to make access to GnuPG easier for applications * [Gpgme](https://www.gnupg.org/software/gpgme/index.html) : GnuPG Made Easy (GPGME) is a library designed to make access to GnuPG easier for applications
## License
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
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 <http://www.gnu.org/licenses/>.

View File

@ -1,6 +1,7 @@
{ {
"policy_groups": [ "policy_groups": [
"content_exchange" "content_exchange",
"networking"
], ],
"policy_version": 16.04 "policy_version": 20.04
} }

View File

@ -4,4 +4,4 @@ Exec=UTPass
Icon=assets/logo.svg Icon=assets/logo.svg
Terminal=false Terminal=false
Type=Application Type=Application
X-Ubuntu-Touch=true X-Lomiri-Touch=true

View File

@ -1,22 +0,0 @@
{
"template": "cmake",
"kill": "UTPass",
"scripts": {
"style": "echo 'Astyle :' && astyle --options=.astylerc main.cpp && astyle --options=.astylerc --recursive 'plugins/*.cpp,*.h' && echo 'QmlFmt :' && qmlfmt -l tests && qmlfmt -w tests && qmlfmt -l qml && qmlfmt -w qml"
},
"libraries": {
"gpg": {
"template": "cmake",
"make_jobs": 4,
"dependencies_build": [
"texinfo",
"gpgsm",
"bison"
]
},
"quazip": {
"template": "cmake",
"make_jobs": 4
}
}
}

34
clickable.yaml Normal file
View File

@ -0,0 +1,34 @@
clickable_minimum_required: 8
builder: cmake
kill: UTPass
scripts:
style: >-
echo 'Running Astyle :' && astyle --options=.astylerc --recursive '*.cpp,*.h' --exclude=build && echo 'Running QmlFormat' && find . -name "*.qml" -exec qmlformat -i {} \; && echo 'Success'
dependencies_target:
- libgpgmepp-dev
- libgpgme-dev
- libgit2-dev
- libquazip5-dev
- gpg
install_lib:
- "libgpg-error.so.0.28.0"
- "libassuan.so*"
- "libgpgme.so*"
- "libgpgmepp.so*"
- "libqgpgme.so*"
- "libgit2.so*"
- "libquazip5.so*"
- "libmbedtls.so*"
- "libmbedx509.so*"
- "libmbedcrypto.so*"
- "libhttp_parser.so*"
- "libssh2.so*"
install_bin:
- "gpg"

View File

@ -1,68 +0,0 @@
cmake_minimum_required(VERSION 3.5.1)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
include(${CMAKE_ROOT}/Modules/ExternalProject.cmake)
execute_process(
COMMAND dpkg-architecture -qDEB_HOST_MULTIARCH
OUTPUT_VARIABLE ARCH_TRIPLET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
execute_process (
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/clean.sh ${ARCH_TRIPLET}
)
set(EXTERNAL_LIBS "${CMAKE_CURRENT_BINARY_DIR}/local/")
set(THIRD_PATH "${CMAKE_CURRENT_SOURCE_DIR}")
set(LIBGPGERROR_PATH "${THIRD_PATH}/libgpg-error")
set(LIBASSUAN_PATH "${THIRD_PATH}/libassuan")
set(LIBGPGME_PATH "${THIRD_PATH}/gpgme")
set(GNUPG_PATH "${THIRD_PATH}/gnupg")
ExternalProject_Add(
LibGpgError
INSTALL_DIR ${EXTERNAL_LIBS}
DOWNLOAD_COMMAND ""
SOURCE_DIR ${LIBGPGERROR_PATH}
CONFIGURE_COMMAND <SOURCE_DIR>/autogen.sh && <SOURCE_DIR>/configure --prefix=${EXTERNAL_LIBS} --enable-static=yes --enable-shared=no --with-pic=yes --enable-maintainer-mode --host ${ARCH_TRIPLET} --disable-doc --disable-dependency-tracking
BUILD_IN_SOURCE 1
BUILD_COMMAND make
INSTALL_COMMAND make install
)
ExternalProject_Add(
LibGpgAssuan
DEPENDS LibGpgError
INSTALL_DIR ${EXTERNAL_LIBS}
DOWNLOAD_COMMAND ""
SOURCE_DIR ${LIBASSUAN_PATH}
CONFIGURE_COMMAND <SOURCE_DIR>/autogen.sh && <SOURCE_DIR>/configure --prefix=${EXTERNAL_LIBS} --enable-static=yes --enable-shared=no --with-pic=yes --enable-maintainer-mode --with-libgpg-error-prefix=${EXTERNAL_LIBS} --host ${ARCH_TRIPLET} --disable-doc --disable-dependency-tracking
BUILD_IN_SOURCE 1
BUILD_COMMAND make
INSTALL_COMMAND make install
)
ExternalProject_Add(
LibGpgme
DEPENDS LibGpgError LibGpgAssuan
INSTALL_DIR ${EXTERNAL_LIBS}
DOWNLOAD_COMMAND ""
SOURCE_DIR ${LIBGPGME_PATH}
CONFIGURE_COMMAND <SOURCE_DIR>/autogen.sh && <SOURCE_DIR>/configure --enable-languages=cpp,qt --enable-static=yes --enable-shared=no --with-pic=yes --prefix=${EXTERNAL_LIBS} --enable-maintainer-mode --with-libgpg-error-prefix=${EXTERNAL_LIBS} --with-libassuan-prefix=${EXTERNAL_LIBS} --host ${ARCH_TRIPLET} --disable-doc --disable-largefile --disable-dependency-tracking
BUILD_IN_SOURCE 1
BUILD_COMMAND make
INSTALL_COMMAND make install
)
ExternalProject_Add(
Gnupg
DEPENDS LibGpgError LibGpgAssuan
INSTALL_DIR ${EXTERNAL_LIBS}
DOWNLOAD_COMMAND ""
SOURCE_DIR ${GNUPG_PATH}
CONFIGURE_COMMAND <SOURCE_DIR>/autogen.sh && <SOURCE_DIR>/configure --prefix=${EXTERNAL_LIBS} --with-libgpg-error-prefix=${EXTERNAL_LIBS} --with-libassuan-prefix=${EXTERNAL_LIBS} --enable-maintainer-mode --with-libassuan-prefix=${EXTERNAL_LIBS} --host ${ARCH_TRIPLET} --disable-doc --disable-dependency-tracking
BUILD_IN_SOURCE 1
BUILD_COMMAND make
INSTALL_COMMAND make install
)

View File

@ -1,14 +0,0 @@
#!/bin/bash
SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
if [ ! -z "$1" ] && [ -d $SCRIPTPATH/../../build/$1/gpg ]; then
rm -rf $SCRIPTPATH/../../build/$1/gpg
fi
git submodule update --init --recursive
for LIB in $SCRIPTPATH/*/
do
echo $LIB
cd $LIB && git clean -xdf && git reset --hard HEAD
done

Submodule libs/gpg/gnupg deleted from 8ae6a246be

Submodule libs/gpg/gpgme deleted from ea11c2a13c

Submodule libs/quazip deleted from 4df6c7412e

View File

@ -6,6 +6,10 @@
#include <QtQml> #include <QtQml>
#ifdef TEST_RUNNER
#include <QtQuickTest/quicktest.h>
#endif
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
qDebug() << "Starting app from main.cpp"; qDebug() << "Starting app from main.cpp";
@ -13,6 +17,7 @@ int main(int argc, char *argv[])
QGuiApplication::setApplicationName("utpass.qrouland"); QGuiApplication::setApplicationName("utpass.qrouland");
#ifndef TEST_RUNNER
auto *view = new QQuickView(); auto *view = new QQuickView();
view->setSource(QUrl(QStringLiteral("qml/Main.qml"))); view->setSource(QUrl(QStringLiteral("qml/Main.qml")));
view->setResizeMode(QQuickView::SizeRootObjectToView); view->setResizeMode(QQuickView::SizeRootObjectToView);
@ -23,4 +28,7 @@ int main(int argc, char *argv[])
Q_ARG(QVariant, QVariant::fromValue(mainView)) Q_ARG(QVariant, QVariant::fromValue(mainView))
); );
return QGuiApplication::exec(); return QGuiApplication::exec();
#else
return quick_test_main(argc, argv, "@TESTS_PATH@", "@TESTS_PATH@");
#endif
} }

View File

@ -10,7 +10,7 @@
"content-hub": "UTPass.contenthub" "content-hub": "UTPass.contenthub"
} }
}, },
"version": "0.0.1", "version": "0.0.3-dev",
"maintainer": "Quentin Rouland <quentin@qrouland.com>", "maintainer": "Quentin Rouland <quentin@qrouland.com>",
"framework" : "ubuntu-sdk-16.04" "framework" : "ubuntu-sdk-20.04"
} }

View File

@ -1,2 +1,3 @@
add_subdirectory(Git)
add_subdirectory(Pass) add_subdirectory(Pass)
add_subdirectory(Utils) add_subdirectory(Utils)

View File

@ -0,0 +1,38 @@
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17")
set(PLUGIN "Git")
set(
SRC
plugin.cpp
git.cpp
utils.h
jobs/clonejob.cpp
jobs/gitjob.cpp
)
set(CMAKE_AUTOMOC ON)
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)
add_library(libgit2 SHARED IMPORTED)
set_property(TARGET libgit2 PROPERTY IMPORTED_LOCATION "/usr/lib/${ARCH_TRIPLET}/libgit2.so")
target_link_libraries(${PLUGIN} libgit2)
set(QT_IMPORTS_DIR "/lib/${ARCH_TRIPLET}")
install(TARGETS ${PLUGIN} DESTINATION ${QT_IMPORTS_DIR}/${PLUGIN}/)
install(FILES qmldir DESTINATION ${QT_IMPORTS_DIR}/${PLUGIN}/)

69
plugins/Git/git.cpp Normal file
View File

@ -0,0 +1,69 @@
#include <QUrl>
#include <QtCore/QDir>
#include <QDebug>
#include <QStandardPaths>
extern "C" {
#include <git2.h>
}
#include "git.h"
#include "utils.h"
#include "jobs/clonejob.h"
#include "jobs/gitjob.h"
Git::Git():
m_sem(std::unique_ptr<QSemaphore>(new QSemaphore(1)))
{
git_libgit2_init();
}
Git::~Git()
{
git_libgit2_shutdown();
}
bool Git::clone(QString url, QString path, cred_type mode)
{
if (!this->m_sem->tryAcquire(1, 500)) {
qWarning() << "Can acquire git semaphore a command is already running ";
return false;
}
auto v = overload {
[](const HTTP & x) { return "HTTP"; },
[](const HTTPUserPass & x) { return "HTTPAuth"; },
[](const SSHPass & x) { return "SSHAuth"; },
[](const SSHKey & x) { return "SSHKey"; },
};
qDebug() << "Creating clone Job " << url << " " << path << " " << std::visit(v, mode);
CloneJob *clone_job = new CloneJob(url, path, mode);
connect(clone_job, &CloneJob::resultReady, this, &Git::cloneResult);
connect(clone_job, &CloneJob::finished, clone_job, &QObject::deleteLater);
clone_job->start();
return true;
}
bool Git::cloneHttp(QString url, QString path)
{
qInfo() << "Call clone command Http " << url << " " << path;
HTTP mode = {};
return this->clone(url, path, mode);
}
bool Git::cloneHttpPass(QString url, QString path, QString pass)
{
qInfo() << "Call clone command HttpPass " << url << " " << path;
HTTPUserPass mode = { pass };
return this->clone(url, path, mode);
}
void Git::cloneResult(const bool err)
{
if (err) {
emit cloneFailed(); // TODO error message
} else {
emit cloneSucceed();
}
this->m_sem->release();
}

112
plugins/Git/git.h Normal file
View File

@ -0,0 +1,112 @@
#ifndef GIT_H
#define GIT_H
#include "jobs/gitjob.h"
#include <QUrl>
#include <QObject>
#include <QSemaphore>
#include <QtCore/QDir>
/**
* @class Git
* @brief A class that provides Git functionality for cloning and updating repositories.
*
* The `Git` class provides a set of methods to do Git operation on remote URLs.
*/
class Git : public QObject
{
Q_OBJECT
private slots:
/**
* @brief Slot that handles the result of a cloning operation.
*
* This slot is connected to the result of the cloning operation and is triggered when the cloning
* process finishes. It emits the appropriate signal based on whether the clone operation succeeded
* or failed.
*
* @param err A boolean indicating whether an error occurred during cloning. `true` if the clone failed,
* `false` if it succeeded.
*/
void cloneResult(const bool err);
signals:
/**
* @brief Signal emitted when the cloning operation succeeds.
*
* This signal is emitted when the Git repository is successfully cloned.
*/
void cloneSucceed();
/**
* @brief Signal emitted when the cloning operation fails.
*
* This signal is emitted when an error occurs during the cloning operation.
*/
void cloneFailed();
private:
std::unique_ptr<QSemaphore> m_sem; /**< Semaphore for managing concurrent operations. */
/**
* @brief Clones a repository from a specified URL.
*
* This private method initiates the cloning job. It sets up the repository cloning process based on
* the specified URL, destination path, and cloning mode (HTTP or SSH).
*
* @param url The URL of the Git repository to clone.
* @param path The destination path for the cloned repository.
* @param mode The cloning mode, such as HTTP or SSH (represented as `cred_type`).
* @return `true` if the cloning process was successful, `false` otherwise.
*/
bool clone(QString url, QString path, cred_type mode);
public:
/**
* @brief Constructor for the Git class.
*
* Initializes the `Git` class, setting up necessary resources such as the semaphore for concurrent operation management.
*/
Git();
/**
* @brief Destructor for the Git class.
*
* Cleans up any resources used by the `Git` class, ensuring that no ongoing operations or resources are left hanging.
*/
~Git() override;
/**
* @brief Clones a repository over HTTP.
*
* This method clones a Git repository from the specified HTTP URL and saves it to the given destination path.
* It is a wrapper around the private `clone()` method, specifying the HTTP cloning mode.
*
* @param url The HTTP URL of the Git repository to clone.
* @param path The destination path for the cloned repository.
* @return `true` if the clone operation was successful, `false` otherwise.
*/
Q_INVOKABLE bool cloneHttp(QString url, QString path);
/**
* @brief Clones a repository over HTTP with a password for authentication.
*
* This method clones a Git repository from the specified HTTP URL using the provided password for authentication,
* and saves it to the given destination path.
*
* @param url The HTTP URL of the Git repository to clone.
* @param path The destination path for the cloned repository.
* @param pass The password used for HTTP authentication.
* @return `true` if the clone operation was successful, `false` otherwise.
*/
Q_INVOKABLE bool cloneHttpPass(QString url, QString path, QString pass);
// Future SSH support methods:
// Q_INVOKABLE bool clone_ssh_pass(QString url, QString path, QString pass);
// Q_INVOKABLE bool clone_ssh_key(QString url, QString path, QString pub_key, QString priv_key, QString passphrase);
// Q_INVOKABLE bool update(QUrl url, QString path);
// ....
};
#endif

View File

@ -0,0 +1,80 @@
#include <QStandardPaths>
#include <QDir>
#include <QUrl>
#include <QDebug>
#include <QObject>
#include <type_traits>
extern "C" {
#include <git2.h>
}
#include "clonejob.h"
CloneJob::CloneJob(QString url, QString path, cred_type cred):
GitJob(cred),
m_url(url),
m_path(path)
{
this->setObjectName("CloneJob");
}
void CloneJob::run()
{
auto tmp_dir = this->cloneSetup();
auto err = this->clone(this->m_url, tmp_dir.absolutePath(), this->m_cred, this->credentialsCB);
if (!err) {
this->moveToDestination(tmp_dir, this->m_path);
}
this->cloneTearDown(tmp_dir);
emit resultReady(err); // TODO Clean error handling to return specifics errors for the ui
}
QDir CloneJob::cloneSetup()
{
QDir tmp_dir(QStandardPaths::writableLocation( QStandardPaths::CacheLocation).append("/clone"));
tmp_dir.removeRecursively();
qDebug() << "Temp dir path is " << tmp_dir.absolutePath();
return tmp_dir;
}
bool CloneJob::cloneTearDown(QDir tmp_dir)
{
return tmp_dir.removeRecursively();
}
bool CloneJob::moveToDestination(QDir tmp_dir, QString path)
{
qDebug() << "Removing password_store " << path;
QDir destination_dir(path);
destination_dir.removeRecursively();
qDebug() << "Moving cloned content to destination dir";
QDir dir;
qDebug() << tmp_dir.absolutePath() << " to " << destination_dir.absolutePath();
return dir.rename(tmp_dir.absolutePath(), destination_dir.absolutePath()); // TODO Better error handling
}
bool CloneJob::clone(QString url, QString path, cred_type cred, git_cred_acquire_cb cb)
{
git_repository *repo = NULL;
git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
opts.fetch_opts.callbacks.credentials = cb;
opts.fetch_opts.callbacks.payload = &cred;
int ret = git_clone(&repo, url.toLocal8Bit().data(), path.toLocal8Bit().data(), &opts);
if (ret != 0) {
qDebug() << git_error_last()->message;
}
if (repo) {
git_repository_free(repo);
}// TODO Better error handling
return ret != 0;
}

105
plugins/Git/jobs/clonejob.h Normal file
View File

@ -0,0 +1,105 @@
#ifndef CLONEJOB_H
#define CLONEJOB_H
#include <QDir>
extern "C" {
#include <git2.h>
}
#include "gitjob.h"
/**
* @class CloneJob
* @brief A class to handle cloning Git repositories in a separate thread.
*
*/
class CloneJob : public GitJob
{
Q_OBJECT
/**
* @brief The main function that performs the cloning operation.
*
* Handles the process of cloning a repository from the specified URL
* to the target path.
*/
void run() override;
signals:
/**
* @brief Signal emitted when the cloning operation is complete.
*
* This signal is emitted once the cloning operation finishes.
*
* @param err A boolean indicating whether an error occurred during cloning.
* `true` if an error occurred, `false` if the clone was successful.
*/
void resultReady(const bool err);
private:
QString m_url; ///< The URL of the Git repository to clone.
QString m_path; ///< The destination path for the cloned repository.
/**
* @brief Prepares the temporary directory for cloning.
*
* This method sets up the required directory structure for cloning a repository.
*
* @return A `QDir` object representing the prepared temporary directory.
*/
static QDir cloneSetup();
/**
* @brief Moves the cloned repository to the specified destination.
*
* After the repository has been cloned into a temporary directory, this method
* moves it to the final destination specified by the user.
*
* @param path The destination path to move the repository to.
* @param tmp_dir The temporary directory where the repository was cloned.
* @return `true` if the move was successful, `false` otherwise.
*/
static bool moveToDestination(QDir tmp_dir, QString path);
/**
* @brief Tears down the temporary directory after cloning.
*
* This method is called to clean up the temporary directory after the repository
* has been cloned and moved to the final destination. It removes the temporary
* directory and all its contents.
*
* @param tmp_dir The temporary directory to tear down.
* @return `true` if the teardown was successful, `false` otherwise.
*/
static bool cloneTearDown(QDir tmp_dir);
/**
* @brief Clones a repository from a specified URL.
*
* This method handles the actual cloning process by setting up a temporary
* directory, cloning the repository into it, and then moving it to the final
* destination. It uses the provided credentials to authenticate the cloning
* operation.
*
* @param url The URL of the Git repository to clone.
* @param path The destination path for the cloned repository.
* @param cred The credentials to use for the cloning operation.
* @param cb The callback function for acquiring credentials during cloning.
* @return `true` if the cloning process was successful, `false` otherwise.
*/
static bool clone(QString url, QString path, cred_type cred, git_cred_acquire_cb cb);
public:
/**
* @brief Constructor for the CloneJob class.
*
* Initializes the CloneJob with the specified repository URL, destination path,
* and credentials.
*
* @param url The URL of the Git repository to clone.
* @param path The destination path where the repository will be cloned.
* @param cred The credentials to be used for the cloning process.
*/
CloneJob(QString url, QString path, cred_type cred);
};
#endif // CLONEJOB_H

View File

@ -0,0 +1,53 @@
#include <QDebug>
#include "gitjob.h"
#include "../utils.h"
extern "C" {
#include <git2.h>
}
GitJob::GitJob(cred_type cred) :
m_cred(cred)
{
git_libgit2_init();
}
GitJob::~GitJob()
{
git_libgit2_shutdown();
}
int GitJob::credentialsCB(git_cred **out, const char *url, const char *username_from_url,
unsigned int allowed_types, void *payload)
{
cred_type *cred = (cred_type *)payload;
auto v = overload {
[](const HTTP & x)
{
qDebug() << "credentialsCB : HTTP ";
qWarning() << "credentialsCB : callback should never be call for HTTP ";
return (int) GIT_EUSER;
},
[&out, &username_from_url](const HTTPUserPass & x)
{
qDebug() << "credentialsCB : HTTPUserPass ";
if (!username_from_url) {
qWarning() << "credentials_cb : no username provided ";
return (int) GIT_EUSER;
}
return git_cred_userpass_plaintext_new(out, username_from_url, x.pass.toLocal8Bit().constData());
},
[](const SSHPass & x)
{
qWarning() << "credentials_cb : SSHAuth to be implemented ";
return (int) GIT_EUSER;
}, // TODO
[](const SSHKey & x)
{
qWarning() << "credentials_cb : SSHKey to be implemented ";
return (int) GIT_EUSER;
} // TODO
};
return std::visit(v, *cred);
}

82
plugins/Git/jobs/gitjob.h Normal file
View File

@ -0,0 +1,82 @@
#ifndef GITJOB_H
#define GITJOB_H
#include "qthread.h"
extern "C" {
#include <git2.h>
}
#include <variant>
// Forward declarations for the different credential types.
struct HTTP { };
struct HTTPUserPass {
QString pass; ///< Password for HTTP user authentication.
};
struct SSHPass { };
struct SSHKey { };
/**
* @brief Variant type to represent various types of credentials.
*
* This type is used to store one of the following credential types:
* - HTTP
* - HTTPUserPass
* - SSHPass
* - SSHKey
*/
typedef std::variant<HTTP, HTTPUserPass, SSHPass, SSHKey> cred_type;
/**
* @class GitJob
* @brief A class that manages Git-related tasks using libgit2.
*
* The GitJob class is used abstraction class to perform Git operations, such as cloning repositories,
* in a separate thread using libgit2 for interacting with Git repositories.
*/
class GitJob : public QThread
{
Q_OBJECT
protected:
cred_type m_cred; ///< The credentials used for Git operations.
/**
* @brief Callback function for handling Git credentials during cloning.
*
* This function is called by libgit2 to handle credentials for cloning a repository.
* The callback is invoked when Git needs to authenticate with a remote repository.
*
* @param out Pointer to the credentials object that will be populated by the callback.
* @param url The URL of the repository being cloned.
* @param username_from_url The username extracted from the URL (if applicable).
* @param allowed_types A bitmask of the allowed types of credentials that can be used.
* @param payload User-defined data passed to the callback. This is typically the instance
* of the class providing the callback, or other user-defined data.
*
* @return A status code indicating success (0) or failure (non-zero).
* @see git_cred
* @see git_cred_type
*/
static int credentialsCB(git_cred **out, const char *url, const char *username_from_url,
unsigned int allowed_types, void *payload);
public:
/**
* @brief Constructor for the GitJob class.
*
* Initializes the GitJob instance with the given credentials.
*
* @param cred The credentials to be used for the Git operation. This can be one of
* the following types: HTTP, HTTPUserPass, SSHPass, or SSHKey.
*/
GitJob(cred_type cred);
/**
* @brief Destructor for the GitJob class.
*
* Cleans up any resources used by the GitJob.
*/
~GitJob();
};
#endif // GITJOB_H

13
plugins/Git/plugin.cpp Normal file
View File

@ -0,0 +1,13 @@
#include <QtQml>
#include <cstring>
#include "plugin.h"
#include "git.h"
void GitPlugin::registerTypes(const char *uri)
{
//@uri Git
qmlRegisterSingletonType<Git>(uri, 1, 0, "Git", [](QQmlEngine *, QJSEngine *) -> QObject * { return new Git; });
}

16
plugins/Git/plugin.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef GITPLUGIN_H
#define GITPLUGIN_H
#include <QQmlExtensionPlugin>
class GitPlugin : public QQmlExtensionPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID
"org.qt-project.Qt.QQmlExtensionInterface")
public:
void registerTypes(const char *uri) override;
};
#endif

2
plugins/Git/qmldir Normal file
View File

@ -0,0 +1,2 @@
module Git
plugin Git

19
plugins/Git/utils.h Normal file
View File

@ -0,0 +1,19 @@
#ifndef UTILS_H
#define UTILS_H
/**
* @brief A utility structure for enabling function overloading with template-based classes.
* see : https://stackoverflow.com/a/64018031
*/
template<class... Ts>
struct overload : Ts... {
using Ts::operator()...;
};
/**
* @brief Deduction guide for the `overload` template.
* see : https://stackoverflow.com/a/64018031
*/
template<class... Ts>
overload(Ts...) -> overload<Ts...>;
#endif // UTILS_H

View File

@ -1,4 +1,4 @@
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17")
set(PLUGIN "Pass") set(PLUGIN "Pass")
set( set(
@ -8,6 +8,7 @@ set(
gpg.cpp gpg.cpp
passkeymodel.h passkeymodel.h
passphraseprovider.h passphraseprovider.h
jobs/rmjob.cpp
) )
set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOMOC ON)
@ -26,29 +27,26 @@ add_library(${PLUGIN} MODULE ${SRC})
set_target_properties(${PLUGIN} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PLUGIN}) set_target_properties(${PLUGIN} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PLUGIN})
qt5_use_modules(${PLUGIN} Qml Quick DBus) qt5_use_modules(${PLUGIN} Qml Quick DBus)
set(EXTERNAL_LIBS "${CMAKE_SOURCE_DIR}/build/${ARCH_TRIPLET}/gpg/local/")
INCLUDE_DIRECTORIES(${EXTERNAL_LIBS}/include) add_library(gpgerror SHARED IMPORTED)
set_property(TARGET gpgerror PROPERTY IMPORTED_LOCATION "/usr/lib/${ARCH_TRIPLET}/libgpg-error.so.0.28.0")
add_library(GpgError STATIC IMPORTED) add_library(libassuan SHARED IMPORTED)
set_property(TARGET GpgError PROPERTY IMPORTED_LOCATION "${EXTERNAL_LIBS}/lib/libgpg-error.a") set_property(TARGET libassuan PROPERTY IMPORTED_LOCATION "/usr/lib/${ARCH_TRIPLET}/libassuan.so")
add_library(GpgAssuan STATIC IMPORTED) add_library(libgpgme SHARED IMPORTED)
set_property(TARGET GpgAssuan PROPERTY IMPORTED_LOCATION "${EXTERNAL_LIBS}/lib/libassuan.a") set_property(TARGET libgpgme PROPERTY IMPORTED_LOCATION "/usr/lib/${ARCH_TRIPLET}/libgpgme.so")
add_library(Gpgme STATIC IMPORTED) add_library(libgpgmepp SHARED IMPORTED)
set_property(TARGET Gpgme PROPERTY IMPORTED_LOCATION "${EXTERNAL_LIBS}/lib/libgpgme.a") set_property(TARGET libgpgmepp PROPERTY IMPORTED_LOCATION "/usr/lib/${ARCH_TRIPLET}/libgpgmepp.so")
add_library(Gpgmepp STATIC IMPORTED) add_library(libqgpgme SHARED IMPORTED)
set_property(TARGET Gpgmepp PROPERTY IMPORTED_LOCATION "${EXTERNAL_LIBS}/lib/libgpgmepp.a") set_property(TARGET libqgpgme PROPERTY IMPORTED_LOCATION "/usr/lib/${ARCH_TRIPLET}/libqgpgme.so")
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) target_link_libraries(${PLUGIN} gpgerror libassuan libgpgme libgpgmepp libqgpgme)
set(QT_IMPORTS_DIR "/lib/${ARCH_TRIPLET}") set(QT_IMPORTS_DIR "/lib/${ARCH_TRIPLET}")
install(TARGETS ${PLUGIN} DESTINATION ${QT_IMPORTS_DIR}/${PLUGIN}/) install(TARGETS ${PLUGIN} DESTINATION ${QT_IMPORTS_DIR}/${PLUGIN}/)
install(FILES qmldir DESTINATION ${QT_IMPORTS_DIR}/${PLUGIN}/) install(FILES qmldir DESTINATION ${QT_IMPORTS_DIR}/${PLUGIN}/)

View File

@ -3,7 +3,7 @@
#include <QFile> #include <QFile>
#include <QDir> #include <QDir>
#include <QtCore/QStandardPaths> #include <QtCore/QStandardPaths>
#include <QProcess>
#include <gpgme.h> #include <gpgme.h>
#include <gpgme++/data.h> #include <gpgme++/data.h>
@ -23,25 +23,24 @@
#include <qgpgme/keylistjob.h> #include <qgpgme/keylistjob.h>
#include <qgpgme/changeownertrustjob.h> #include <qgpgme/changeownertrustjob.h>
#include "gpg.h" #include "gpg.h"
#include "pass.h"
#include "passphraseprovider.h" #include "passphraseprovider.h"
using namespace GpgME; using namespace GpgME;
using namespace QGpgME; using namespace QGpgME;
Gpg::Gpg() Gpg::Gpg(QObject* windows)
{ {
m_window = nullptr; this->m_passphrase_provider = new UTPassphraseProvider(windows);
initializeLibrary();
Gpg::initGpgConfig(); Gpg::initGpgConfig();
auto error = checkEngine(OpenPGP); auto error = checkEngine(OpenPGP);
if (error) { if (error) {
qDebug() << "Code Error : " << error.code(); qDebug() << "Code Error : " << error.code();
qDebug() << "Error str : " << error.asString(); qDebug() << "Error str : " << error.asString();
@ -53,6 +52,11 @@ Gpg::Gpg()
qDebug() << "GNUPG Home is :" << engineInfo(OpenPGP).homeDirectory(); qDebug() << "GNUPG Home is :" << engineInfo(OpenPGP).homeDirectory();
} }
Gpg::~Gpg()
{
delete this->m_passphrase_provider;
}
QString Gpg::initGpgHome() QString Gpg::initGpgHome()
{ {
@ -66,15 +70,33 @@ QString Gpg::initGpgHome()
} }
QString Gpg::findCommandPath(const QString &command)
{
// Retrieve the PATH environment variable
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
QString pathEnv = env.value("PATH");
// Split the PATH by colon
QStringList pathDirs = pathEnv.split(":", QString::SkipEmptyParts);
// Check each directory in the PATH
foreach (const QString &dir, pathDirs) {
QFileInfo fileInfo(QDir(dir).filePath(command));
// If the file exists and is executable, return the path
if (fileInfo.exists() && fileInfo.isExecutable()) {
return fileInfo.absoluteFilePath();
}
}
return QString::null;
}
QString Gpg::initGpgExec() QString Gpg::initGpgExec()
{ {
QString path = QDir::currentPath().append("/lib/bin/gpg"); QString path = findCommandPath("gpg");
QFileInfo file(path); if (path.isNull()) {
if (!file.isFile()) { qFatal("No valid gpg exec found !");
qFatal("GNUPGEXEC file not found !");
}
if (!file.isExecutable()) {
qFatal("GNUPGEXEC file not executable !");
} }
return path; return path;
} }
@ -82,53 +104,55 @@ QString Gpg::initGpgExec()
void Gpg::initGpgConfig() void Gpg::initGpgConfig()
{ {
auto home = initGpgHome(); initializeLibrary();
auto exec = initGpgExec(); gpgme_set_global_flag("disable-gpgconf", "1");
QString home = initGpgHome();
qDebug() << "Gpg home is " << home;
QString exec = initGpgExec();
qDebug() << "Gpg exec is " << exec;
QFile agentConf(home + QStringLiteral("/gpg-agent.conf")); QFile agentConf(home + QStringLiteral("/gpg-agent.conf"));
agentConf.remove(); agentConf.remove();
agentConf.open(QIODevice::WriteOnly); agentConf.open(QIODevice::WriteOnly);
agentConf.write("allow-loopback-pinentry"); agentConf.write("allow-loopback-pinentry\n");
agentConf.close(); agentConf.close();
gpgme_set_engine_info ( auto err = gpgme_set_engine_info (
GPGME_PROTOCOL_OpenPGP, GPGME_PROTOCOL_OpenPGP,
exec.toLocal8Bit().data(), exec.toLocal8Bit().data(),
home.toLocal8Bit().data() home.toLocal8Bit().data()
); );
if (err != GPG_ERR_NO_ERROR) {
qDebug() << "Error code : " << err;
qDebug() << "Error str : " << gpg_strerror(err);
qFatal("GPGME set engine info failed !");
}
} }
QPair<Error, QString> Gpg::decrypt(QByteArray cipherText) Error Gpg::decrypt(QByteArray cipher_text)
{ {
auto job = openpgp()->decryptJob(); auto job = openpgp()->decryptJob();
auto ctx = DecryptJob::context(job); auto ctx = DecryptJob::context(job);
auto provider = new UTPassphraseProvider; ctx->setPassphraseProvider(this->m_passphrase_provider);
ctx->setPassphraseProvider(provider);
ctx->setPinentryMode(Context::PinentryLoopback); ctx->setPinentryMode(Context::PinentryLoopback);
QByteArray plain_text; QObject::connect(job, &DecryptJob::result,
auto decResult = job->exec(cipherText, plain_text); this, &Gpg::decryptResultSlot);
delete job; return job->start(cipher_text);
if (decResult.error()) {
qWarning() << "something gone wrong on decrypt";
qDebug() << "Code Error : " << decResult.error().code();
qDebug() << "Error str : " << decResult.error().asString();
}
return QPair<Error, QString>(decResult.error(), QString::fromUtf8(plain_text));
} }
Error Gpg::decryptFromFile(QString path)
QPair<Error, QString> Gpg::decryptFromFile(QString path)
{ {
qDebug() << "Decrypt from " << path; qDebug() << "Decrypt from " << path;
QFile file(path); QFile file(path);
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly)) {
qWarning() << "Can't open the File"; qWarning() << "Can't open the File";
return QPair<Error, QString>(Error(), QString());; return Error();
} }
QByteArray cipherText = file.readAll(); QByteArray cipherText = file.readAll();
file.close(); file.close();
@ -136,77 +160,86 @@ QPair<Error, QString> Gpg::decryptFromFile(QString path)
} }
QPair<Error, QByteArray> Gpg::encrypt(QString str, QString uid, bool ascii_armor, bool text_mode)
void Gpg::decryptResultSlot(const GpgME::DecryptionResult &result, const QByteArray &plainText,
const QString &auditLogAsHtml, const GpgME::Error &auditLogError)
{ {
if (result.error()) {
qDebug() << "Encrypt to QByteArray"; qWarning() << "Something gone wrong on decrypt";
auto keys = getKeys(uid); qDebug() << "Code Error : " << result.error().code();
if (keys.first) { qDebug() << "Error str : " << result.error().asString();
return QPair<Error, QByteArray>(keys.first, QByteArray());
} }
qDebug() << "Cancelled : " << result.error().isCanceled();
auto job = std::unique_ptr<EncryptJob>(openpgp()->encryptJob(ascii_armor, text_mode)); emit decryptResult(result.error(), QString::fromUtf8(plainText));
QByteArray cipherText;
auto result = job->exec(keys.second, str.toUtf8(), Context::AlwaysTrust, cipherText);
qDebug() << "Encrypted to QByteArray";
return QPair<Error, QByteArray>(result.error(), cipherText);
} }
Error Gpg::encryptToFile(QString str, QString path, QString uid, bool ascii_armor, // QPair<Error, QByteArray> Gpg::encrypt(QString str, QString uid, bool ascii_armor, bool text_mode)
bool text_mode) // {
{
qDebug() << "Encrypting to file " << path; // qDebug() << "Encrypt to QByteArray";
QFile file(path); // auto keys = getKeys(uid);
if (!file.open(QIODevice::WriteOnly)) { // if (keys.first) {
qWarning() << "Can't open the file to write it" ; // return QPair<Error, QByteArray>(keys.first, QByteArray());
return Error(); // }
}
auto encrypt_ret = encrypt(str, uid, ascii_armor, text_mode); // auto job = std::unique_ptr<EncryptJob>(openpgp()->encryptJob(ascii_armor, text_mode));
if (encrypt_ret.first) {
file.write(encrypt_ret.second); // QByteArray cipherText;
} // auto result = job->exec(keys.second, str.toUtf8(), Context::AlwaysTrust, cipherText);
qDebug() << "Encrypting to file " << path;
return encrypt_ret.first; // qDebug() << "Encrypted to QByteArray";
} // return QPair<Error, QByteArray>(result.error(), cipherText);
// }
QPair<Error, std::vector< GpgME::Key >> Gpg::getAllKeys ( bool remote, const bool include_sigs, // Error Gpg::encryptToFile(QString str, QString path, QString uid, bool ascii_armor,
bool validate ) // bool text_mode)
// {
// qDebug() << "Encrypting to file " << path;
// QFile file(path);
// if (!file.open(QIODevice::WriteOnly)) {
// qWarning() << "Can't open the file to write it" ;
// return Error();
// }
// auto encrypt_ret = encrypt(str, uid, ascii_armor, text_mode);
// if (encrypt_ret.first) {
// file.write(encrypt_ret.second);
// }
// qDebug() << "Encrypting to file " << path;
// return encrypt_ret.first;
// }
Error Gpg::getAllKeys ( bool remote, const bool include_sigs,
bool validate )
{ {
return getKeys(QString(""), remote, include_sigs, validate); return getKeys(QString(""), remote, include_sigs, validate);
} }
QPair<Error, std::vector<Key>> Gpg::getKeys(QString pattern_uid, bool remote, bool include_sigs, Error Gpg::getKeys(QString pattern_uid, bool remote, bool include_sigs,
bool validate) bool validate)
{ {
qDebug() << "Getting the keys " << pattern_uid; qDebug() << "Getting the keys " << pattern_uid;
auto job = std::unique_ptr<KeyListJob>(openpgp()->keyListJob(remote, include_sigs, validate)); auto job = openpgp()->keyListJob(remote, include_sigs, validate);
std::vector<Key> keys; QObject::connect(job, &KeyListJob::result,
auto result = job->exec(QStringList() << pattern_uid, false, keys); this, &Gpg::getKeysJobResultSlot);
qDebug() << "Got the keys " << pattern_uid; return job->start(QStringList() << pattern_uid, false);
return QPair<Error, std::vector< Key >>(result.error(), keys);
} }
void Gpg::getKeysJobResultSlot(const GpgME::KeyListResult &result, const std::vector<GpgME::Key> &keys,
QPair<Error, Key> Gpg::getKey(QString uid, bool remote, bool include_sigs, bool validate) const QString &auditLogAsHtml, const GpgME::Error &auditLogError)
{ {
qDebug() << "Getting the key " << uid; if (result.error()) {
auto keys = getKeys(uid, remote, include_sigs, validate); qWarning() << "Something gone wrong on getKeys";
qDebug() << "Code Error : " << result.error().code();
if (keys.first or keys.second.size() != 1) { qDebug() << "Error str : " << result.error().asString();
qWarning() << "Bad id";
return QPair<Error, Key>(keys.first, Key::null);
} }
qDebug() << "Got the key " << uid; emit getKeysResult(result.error(), keys);
return QPair<Error, Key>(keys.first, keys.second.front());
} }
Error Gpg::importKeysFromFile(QString path) Error Gpg::importKeysFromFile(QString path)
{ {
qDebug() << "Importing the key file" << path; qDebug() << "Importing the key file" << path;
@ -216,55 +249,53 @@ Error Gpg::importKeysFromFile(QString path)
qWarning() << "Can't open the File"; qWarning() << "Can't open the File";
return Error(); return Error();
} }
auto data = file.readAll();
file.close();
auto job = openpgp()->importJob(); auto job = openpgp()->importJob();
auto ctx = ImportJob::context(job);
auto provider = new UTPassphraseProvider; QObject::connect(job, &ImportJob::result,
ctx->setPassphraseProvider(provider); this, &Gpg::importKeysFromFileSlot);
ctx->setPinentryMode(Context::PinentryLoopback);
auto result = job->exec(file.readAll());
return job->start(data);
}
void Gpg::importKeysFromFileSlot(const GpgME::ImportResult &result, const QString &auditLogAsHtml,
const GpgME::Error &auditLogError)
{
qDebug() << "numImported" << result.numImported(); qDebug() << "numImported" << result.numImported();
qDebug() << "numSecretKeysImported" << result.numSecretKeysImported(); qDebug() << "numSecretKeysImported" << result.numSecretKeysImported();
qDebug() << "numSecretKeysConsidered" << result.numSecretKeysConsidered(); qDebug() << "numSecretKeysConsidered" << result.numSecretKeysConsidered();
qDebug() << "numSecretKeysUnchanged" << result.numSecretKeysUnchanged(); qDebug() << "numSecretKeysUnchanged" << result.numSecretKeysUnchanged();
qDebug() << "numUnchanged" << result.numUnchanged(); qDebug() << "numUnchanged" << result.numUnchanged();
file.close();
delete job;
delete provider;
if (result.error()) { if (result.error()) {
qWarning() << "Import go wrong"; qWarning() << "Something gone wrong on decrypt";
qDebug() << "Code Error : " << result.error().code(); qDebug() << "Code Error : " << result.error().code();
qDebug() << "Error str : " << result.error().asString(); qDebug() << "Error str : " << result.error().asString();
} }
qDebug() << "Imported the key file" << path; emit importKeysFromFileResult(result.error());
return result.error();
} }
Error Gpg::deleteKeyId(QString uid) Error Gpg::deleteKey(const Key key)
{ {
qDebug() << "Deleting key id " << uid; auto job = openpgp()->deleteJob();
auto key = getKey(uid);
if (key.first) { QObject::connect(job, &DeleteJob::result,
return key.first; this, &Gpg::deleteKeySlot);
}
auto ctx = std::unique_ptr<Context>(Context::createForProtocol(OpenPGP)); return job->start(key, true);
auto err = ctx->deleteKey(key.second, true);
if (err) {
qWarning() << "Delete go wrong";
qDebug() << "Code Error : " << err.code();
qDebug() << "Error str : " << err.asString();
return err;
}
qDebug() << "Deleted key id" << uid;
return err;
} }
void Gpg::deleteKeySlot(const GpgME::Error &error, const QString &auditLogAsHtml, const GpgME::Error &auditLogError)
{
if (error) {
qWarning() << "Something gone wrong on deleteKey";
qDebug() << "Code Error : " << error.code();
qDebug() << "Error str : " << error.asString();
}
emit deleteKeyResult(error);
}

View File

@ -1,61 +1,227 @@
#ifndef GPG_H #ifndef GPG_H
#define GPG_H #define GPG_H
#include "passkeymodel.h"
#include "passphraseprovider.h"
#include <memory> #include <memory>
#include <QQuickWindow> #include <QQuickWindow>
#include <gpgme++/context.h> #include <gpgme++/context.h>
#include <qgpgme/changeownertrustjob.h> #include <qgpgme/changeownertrustjob.h>
#include <QSemaphore>
#include <gpgme.h>
#include <qgpgme/importjob.h>
#include <qgpgme/deletejob.h>
#include <qgpgme/decryptjob.h>
#include <qgpgme/encryptjob.h>
#include <qgpgme/protocol.h>
#include <qgpgme/keylistjob.h>
#include <qgpgme/changeownertrustjob.h>
using namespace GpgME; using namespace GpgME;
using namespace QGpgME;
class Gpg /**
* @class Gpg
* @brief A class for managing GPG key operations such as key import, decryption, and deletion.
*
* This class integrates with the GPGME (GnuPG Made Easy) library to provide functionalities
* for interacting with GPG keys, including decrypting messages, importing keys from files,
* listing keys, and deleting keys.
*/
class Gpg: public QObject
{ {
private: Q_OBJECT
Gpg(); Q_PROPERTY(UTPassphraseProvider* passphrase_provider READ passphrase_provider MEMBER m_passphrase_provider )
QObject *m_window;
private slots:
/**
* @brief Slot to handle the result of a decryption operation.
* @param result The result of the decryption operation.
* @param plain_text The decrypted text.
* @param auditLogAsHtml The HTML formatted audit log for the operation.
* @param auditLogError The error associated with the audit log, if any.
*/
void decryptResultSlot(
const DecryptionResult &result,
const QByteArray &plain_text,
const QString &auditLogAsHtml,
const Error &auditLogError
);
/**
* @brief Slot to handle the result of a key retrieval operation.
* @param result The result of the key retrieval operation.
* @param keys A vector of keys retrieved.
* @param auditLogAsHtml The HTML formatted audit log for the operation, if any.
* @param auditLogError The error associated with the audit log, if any.
*/
void getKeysJobResultSlot(
const GpgME::KeyListResult &result,
const std::vector<GpgME::Key> &keys,
const QString &auditLogAsHtml,
const GpgME::Error &auditLogError
);
/**
* @brief Slot to handle the result of a key import operation.
* @param result The result of the import operation.
* @param auditLogAsHtml The HTML formatted audit log for the operation, if any.
* @param auditLogError The error associated with the audit log, if any.
*/
void importKeysFromFileSlot(
const GpgME::ImportResult &result,
const QString &auditLogAsHtml,
const GpgME::Error &auditLogError
);
/**
* @brief Slot to handle the result of a key deletion operation.
* @param result The error result of the deletion operation.
* @param auditLogAsHtml The HTML formatted audit log for the operation, if any.
* @param auditLogError The error associated with the audit log, if any.
*/
void deleteKeySlot(
const GpgME::Error &result,
const QString &auditLogAsHtml,
const GpgME::Error &auditLogError
);
signals:
/**
* @brief Signal emitted when keys are imported from a file.
* @param err The error that occurred during the import operation, if any.
*/
void importKeysFromFileResult(Error err);
/**
* @brief Signal emitted when keys are retrieved.
* @param err The error that occurred during the retrieval operation, if any.
* @param keys A vector of keys retrieved.
*/
void getKeysResult(Error err, std::vector<GpgME::Key> keys);
/**
* @brief Signal emitted when a key is deleted.
* @param err The error that occurred during the deletion operation, if any.
*/
void deleteKeyResult(Error err);
/**
* @brief Signal emitted when a decryption operation completes.
* @param err The error that occurred during decryption, if any.
* @param plain_text The decrypted message.
*/
void decryptResult(Error err, QString plain_text);
private:
UTPassphraseProvider *m_passphrase_provider; /**< The passphrase provider used for authentication. */
/**
* @brief Finds the path of a command in the system's environment.
* @param command The name of the command to find.
* @return The full path to the command.
*/
QString findCommandPath(const QString &command);
/**
* @brief Initializes the GPG home directory.
* @return The path to the GPG home directory.
*/
QString initGpgHome(); QString initGpgHome();
/**
* @brief Initializes the GPG executable path.
* @return The path to the GPG executable.
*/
QString initGpgExec(); QString initGpgExec();
/**
* @brief Initializes the GPG configuration.
*/
void initGpgConfig(); void initGpgConfig();
/**
* @brief Retrieves a GPG key by UID.
* @param uid The UID of the key to retrieve.
* @param remote Whether to fetch the key from a remote keyserver (default: false).
* @param include_sigs Whether to include signatures (default: false).
* @param validate Whether to validate the key (default: false).
* @return The error result of the operation.
*/
Error getKey(QString uid, bool remote = false, bool include_sigs = false, bool validate = false);
public: public:
/**
* @brief Constructs a Gpg object and initializes necessary resources.
* @param window The window object for interacting with the user interface.
*/
Gpg(QObject* window);
/**
* @brief Destroys the Gpg object and cleans up resources.
*/
~Gpg(); ~Gpg();
static std::shared_ptr<Gpg> instance() /**
* @brief Gets the passphrase provider used for GPG authentication.
* @return The passphrase provider.
*/
UTPassphraseProvider *passphrase_provider() const
{ {
static std::shared_ptr<Gpg> s{new Gpg}; return m_passphrase_provider;
return s;
} }
Gpg(Gpg const &) = delete;
void operator=(Gpg const &) = delete;
void setWindow(QObject *window)
{
m_window = window;
};
QObject *getWindow()
{
return m_window;
};
QPair< Error, std::vector< Key > > getAllKeys(bool remote = false, bool include_sigs = {}, bool /**
validate = false); * @brief Imports GPG keys from a file.
QPair<Error, std::vector<Key>> getKeys( QString pattern_uid, bool remote = false, * @param path The path to the file containing the keys.
bool include_sigs = false, * @return The error result of the import operation.
bool validate = false); */
QPair<Error, Key> getKey( QString uid, bool remote = false, bool include_sigs = false, Error importKeysFromFile(const QString path);
bool validate = false);
QPair<Error, QString> decrypt( QByteArray cipherText); /**
QPair<Error, QString> decryptFromFile( QString path); * @brief Retrieves keys matching the provided UID pattern.
QPair<Error, QByteArray> encrypt( QString str, QString uid, bool ascii_armor = true, * @param pattern_uid The UID pattern to search for.
bool text_mode = true); * @param remote Whether to fetch the key from a remote keyserver (default: false).
Error encryptToFile( QString str, QString path, QString uid, bool ascii_armor = true, * @param include_sigs Whether to include signatures (default: false).
bool text_mode = true); * @param validate Whether to validate the key (default: false).
Error importKeysFromFile( QString path); * @return The error result of the operation.
Error deleteKeyId( QString uid); */
Error getKeys(const QString pattern_uid, bool remote = false, bool include_sigs = false, bool validate = false);
/**
* @brief Retrieves all keys from the GPG keyring.
* @param remote Whether to fetch the keys from a remote keyserver (default: false).
* @param include_sigs Whether to include signatures (default: false).
* @param validate Whether to validate the keys (default: false).
* @return The error result of the operation.
*/
Error getAllKeys(bool remote = false, bool include_sigs = false, bool validate = false);
/**
* @brief Deletes a specified GPG key.
* @param key The key to delete.
* @return The error result of the deletion operation.
*/
Error deleteKey(const Key key);
/**
* @brief Decrypts a given ciphertext.
* @param cipher_text The ciphertext to decrypt.
* @return The error result of the decryption operation.
*/
Error decrypt(const QByteArray cipher_text);
/**
* @brief Decrypts the contents of a file.
* @param path The path to the file to decrypt.
* @return The error result of the decryption operation.
*/
Error decryptFromFile(const QString path);
// Error encrypt (QString str, QString uid, bool ascii_armor = true, bool text_mode = true);
}; };
#endif #endif

View File

@ -0,0 +1,24 @@
#include "rmjob.h"
RmJob::RmJob(QString path):
m_path(path)
{
this->setObjectName("RmJob");
}
void RmJob::run()
{
auto info = QFileInfo(this->m_path);
if (info.isFile()) {
auto file = QFile(this->m_path);
file.remove();
emit resultReady(false);
} else if (info.isDir()) {
auto dir = QDir(this->m_path);
dir.removeRecursively();
emit resultReady(false);
} else {
emit resultReady(true);
}
}

46
plugins/Pass/jobs/rmjob.h Normal file
View File

@ -0,0 +1,46 @@
#ifndef RMJOB_H
#define RMJOB_H
#include <QThread>
#include <QDir>
/**
* @class RmJob
* @brief A class to handle removing recursively a path in a separate thread.
*
*/
class RmJob : public QThread
{
Q_OBJECT
/**
* @brief The main function that performs the rm operation.
*
* Handles the process of removing recursively a target path.
*/
void run() override;
signals:
/**
* @brief Signal emitted when the rm operation is complete.
*
* @param err A boolean indicating whether an error occurred during cloning.
* `true` if an error occurred, `false` if the clone was successful.
*/
void resultReady(const bool err);
private:
QString m_path; ///< The path to be removed.
public:
/**
* @brief Constructor for the RmJob class.
*
* Initializes the RmJob with the specified path to be removed.
*
* @param path Path to be remove.
*/
RmJob(QString path);
};
#endif // RMJOB_H

View File

@ -2,65 +2,166 @@
#include <QtCore/QStandardPaths> #include <QtCore/QStandardPaths>
#include <QtCore/QDir> #include <QtCore/QDir>
#include "jobs/rmjob.h"
#include "pass.h" #include "pass.h"
#include "gpg.h" #include "gpg.h"
#include "passphraseprovider.h"
#include "passkeymodel.h" #include "passkeymodel.h"
Pass::Pass(): m_password_store (QStandardPaths::writableLocation( Pass::Pass():
QStandardPaths::AppDataLocation).append("/.password-store")) m_password_store (QStandardPaths::writableLocation(
QStandardPaths::AppDataLocation).append("/.password-store")),
m_sem(std::unique_ptr<QSemaphore>(new QSemaphore(1))),
m_show_filename(QString())
{} {}
void Pass::init(QObject *window) void Pass::initialize(QObject *window)
{ {
if (!window) { if (!window) {
qFatal("window is invalid. Abording."); qFatal("window is invalid. Abording.");
} }
Gpg::instance()->setWindow(window);
this->m_gpg = std::unique_ptr<Gpg>(new Gpg(window));
QObject::connect(this, &Pass::responsePassphraseDialogPropagate, this->m_gpg->passphrase_provider(),
&UTPassphraseProvider::handleResponse);
QObject::connect(this->m_gpg.get(), &Gpg::importKeysFromFileResult, this, &Pass::importGPGKeyResult);
QObject::connect(this->m_gpg.get(), &Gpg::getKeysResult, this, &Pass::getAllGPGKeysResult);
QObject::connect(this->m_gpg.get(), &Gpg::deleteKeyResult, this, &Pass::deleteGPGKeyResult);
QObject::connect(this->m_gpg.get(), &Gpg::decryptResult, this, &Pass::showResult);
QDir dir(m_password_store); QDir dir(m_password_store);
if (!dir.exists()) if (!dir.exists()) {
dir.mkpath("."); dir.mkpath(".");
qDebug() << "Password Store is :" << m_password_store; }
qInfo() << "Password Store is :" << m_password_store;
} }
void Pass::decrypt(QUrl url) bool Pass::show(QUrl url)
{ {
qDebug() << "Start decrypting"; if (!this->m_sem->tryAcquire(1, 500)) {
auto decrypt_ret = Gpg::instance()->decryptFromFile(url.toLocalFile()); return false;
if (decrypt_ret.first) { }
qDebug() << "Decrypt Failed"; auto path = url.toLocalFile();
emit decryptFailed(); qInfo() << "Pass show " << path;
} else if (decrypt_ret.second.isNull()) { QFileInfo file_info(path);
qDebug() << "Decrypt Canceled"; this->m_show_filename = file_info.completeBaseName();
emit decryptCanceled(); return this->m_gpg->decryptFromFile(path);
}
void Pass::showResult(Error err, QString plain_text)
{
qDebug() << "Pass show Result";
if (err) {
qInfo() << "Pass show Failed";
emit showFailed(err.asString());
} else if (err.isCanceled()) {
qInfo() << "Pass show Cancelled";
emit showCancelled();
} else { } else {
qDebug() << "Decrypt OK"; qInfo() << "Pass show Succeed";
emit decrypted(decrypt_ret.second); emit showSucceed(this->m_show_filename, plain_text);
}
this->m_show_filename = QString();
this->m_sem->release(1);
}
bool Pass::deletePasswordStore()
{
qInfo() << "Pass delete Password Store";
auto job = new RmJob(this->password_store());
qDebug() << "Delete Password Store at " << this->password_store();
connect(job, &RmJob::resultReady, this, &Pass::deletePasswordStoreResult);
connect(job, &RmJob::finished, job, &QObject::deleteLater);
job->start();
return true;
}
void Pass::deletePasswordStoreResult(bool err)
{
qDebug() << "Pass delete Password StoreResult";
if (err) { //dir.removeRecursively()) {
qInfo() << "Pass delete Password Store Failed";
emit deletePasswordStoreFailed("failed to delete password store");
} else {
qInfo() << "Pass delete Password Store Succeed";
emit deletePasswordStoreSucceed();
} }
} }
bool Pass::gpgDeleteKeyId(QString id)
bool Pass::deleteGPGKey(PassKeyModel* key)
{ {
qDebug() << "Start deleting Key id " << id; if (!this->m_sem->tryAcquire(1, 500)) {
return !Gpg::instance()->deleteKeyId(id); return false;
}
qInfo() << "Delete Key " << key->uid();
return this->m_gpg->deleteKey(key->key());
} }
bool Pass::gpgImportKeyFromFile(QUrl url) void Pass::deleteGPGKeyResult(Error err)
{ {
qDebug() << "Start importing Key from " << url; qDebug() << "Delete Ke yResult";
return !Gpg::instance()->importKeysFromFile(url.toLocalFile()); if (err) {
qInfo() << "Delete Key Failed";
emit deleteGPGKeyFailed(err.asString());
} else {
qInfo() << "Delete Key Succeed";
emit deleteGPGKeySucceed();
}
this->m_sem->release(1);
} }
QVariant Pass::gpgGetAllKeysModel() bool Pass::importGPGKey(QUrl url)
{ {
return QVariant::fromValue(PassKeyModel::keysToPassKeyQObjectList( if (!this->m_sem->tryAcquire(1, 500)) {
Gpg::instance()->getAllKeys().second)); return false;
}
qInfo() << "Import GPG Key from " << url;
return this->m_gpg->importKeysFromFile(url.toLocalFile());
} }
QString Pass::getPasswordStore() void Pass::importGPGKeyResult(Error err)
{ {
return m_password_store; qDebug() << "Import GPG Key Result";
if (err) {
qInfo() << "Delete Key Failed";
emit importGPGKeyFailed(err.asString());
} else {
qInfo() << "Delete Key Succeed";
emit importGPGKeySucceed();
}
this->m_sem->release(1);
}
bool Pass::getAllGPGKeys()
{
if (!this->m_sem->tryAcquire(1, 500)) {
return false;
}
qInfo() << "Get GPG keys";
return this->m_gpg->getAllKeys();
}
void Pass::getAllGPGKeysResult(Error err, std::vector<GpgME::Key> keys_info)
{
qDebug() << "Get GPG keys Result";
if (err) {
qInfo() << "Get GPG Failed";
emit getAllGPGKeysFailed(err.asString());
} else {
qInfo() << "Get GPG Succeed";
emit getAllGPGKeysSucceed(QVariant::fromValue(PassKeyModel::keysToPassKey(keys_info)));
}
this->m_sem->release(1);
}
void Pass::responsePassphraseDialog(bool cancel, QString passphrase)
{
qDebug() << "Propagate responsePassphraseDialog";
emit responsePassphraseDialogPropagate(cancel, passphrase);
} }

View File

@ -4,29 +4,201 @@
#include <QObject> #include <QObject>
#include <QUrl> #include <QUrl>
#include <QVariant> #include <QVariant>
#include <gpgme++/context.h>
#include "gpg.h"
using namespace GpgME;
/**
* @class Pass
* @brief A class for managing password storage using GPG encryption.
*
* This class provides functionalities for interacting with password storage, including
* storing, showing, importing, and deleting passwords securely using GPG encryption.
*/
class Pass : public QObject class Pass : public QObject
{ {
Q_OBJECT Q_OBJECT
QString m_password_store; Q_PROPERTY(QString password_store READ password_store MEMBER m_password_store CONSTANT)
private slots:
/**
* @brief Slot to handle the result of a GPG decryption operation (to show password).
* @param err The error that occurred during the operation.
* @param plain_text The decrypted plain text (password).
*/
void showResult(Error err, QString plain_text);
/**
* @brief Slot to handle the result of a GPG key deletion operation.
* @param err The error that occurred during the operation.
*/
void deleteGPGKeyResult(Error err);
/**
* @brief Slot to handle the result of a GPG key import operation.
* @param err The error that occurred during the operation.
*/
void importGPGKeyResult(Error err);
/**
* @brief Slot to handle the result of retrieving all GPG keys.
* @param err The error that occurred during the operation.
* @param keys_info The list of GPG keys retrieved.
*/
void getAllGPGKeysResult(Error err, std::vector<GpgME::Key> keys_info);
/**
* @brief Slot to handle the result of a delete Password Store operation.
* @param err True if an error occurred during the operation.
*/
void deletePasswordStoreResult(bool err);
signals: signals:
void decrypted(QString text); // GPG-related signals
void decryptCanceled(); /**
void decryptFailed(); * @brief Emitted when a GPG key is successfully deleted.
*/
void deleteGPGKeySucceed();
/**
* @brief Emitted when a GPG key deletion fails.
* @param message The error message describing the failure.
*/
void deleteGPGKeyFailed(QString message);
/**
* @brief Emitted when a GPG key is successfully imported.
*/
void importGPGKeySucceed();
/**
* @brief Emitted when a GPG key import fails.
* @param message The error message describing the failure.
*/
void importGPGKeyFailed(QString message);
/**
* @brief Emitted when all GPG keys are successfully retrieved.
* @param keys_info The list of retrieved keys.
*/
void getAllGPGKeysSucceed(QVariant keys_info);
/**
* @brief Emitted when retrieving GPG keys fails.
* @param message The error message describing the failure.
*/
void getAllGPGKeysFailed(QString message);
// Pass-related signals
/**
* @brief Emitted to propagate passphrase dialog response.
* @param cancel Whether the dialog was cancelled.
* @param passphrase The passphrase entered, if not cancelled.
*/
void responsePassphraseDialogPropagate(bool cancel, QString passphrase);
/**
* @brief Emitted when a password is successfully retrieved and shown.
* @param name The name of the password (e.g., service).
* @param text The password text.
*/
void showSucceed(QString name, QString text);
/**
* @brief Emitted when showing a password fails.
* @param message The error message describing the failure.
*/
void showFailed(QString message);
/**
* @brief Emitted hen showing a password cancelled.
*/
void showCancelled();
/**
* @brief Emitted when the password store is successfully deleted.
*/
void deletePasswordStoreSucceed();
/**
* @brief Emitted when deleting the password store fails.
* @param message The error message describing the failure.
*/
void deletePasswordStoreFailed(QString message);
private:
QString m_password_store; /**< The path to the password store. */
std::unique_ptr<Gpg> m_gpg; /**< The GPG instance used for encryption/decryption. */
std::unique_ptr<QSemaphore> m_sem; /**< Semaphore for managing concurrent operations. */
QString m_show_filename; /**< The filename associated with the password to show. */
public: public:
/**
* @brief Constructs the Pass object and initializes necessary resources.
*/
Pass(); Pass();
~Pass() override = default;
Q_INVOKABLE void init(QObject *window); /**
Q_INVOKABLE QString getPasswordStore(); * @brief Gets the path to the password store.
Q_INVOKABLE void decrypt(QUrl url); * @return The path to the password store.
Q_INVOKABLE bool gpgDeleteKeyId(QString id); */
Q_INVOKABLE bool gpgImportKeyFromFile(QUrl url); QString password_store() const
Q_INVOKABLE QVariant gpgGetAllKeysModel(); {
return m_password_store;
};
/**
* @brief Initializes the Pass object with the given window.
* @param window The QObject window to interact with.
*/
Q_INVOKABLE void initialize(QObject *window);
// GPG-related methods
/**
* @brief Launch the job to delete the specified GPG key.
* @param key The PassKeyModel to delete.
* @return True if the job was start successfully, false otherwise.
*/
Q_INVOKABLE bool deleteGPGKey(PassKeyModel* key);
/**
* @brief Launch the job to import a GPG key from the given URL.
* @param url The URL to import the GPG key from.
* @return True if the job was start was successfully, false otherwise.
*/
Q_INVOKABLE bool importGPGKey(QUrl url);
/**
* @brief Launch the to retrieve all GPG keys.
* @return True if the job was start was successfully, false otherwise.
*/
Q_INVOKABLE bool getAllGPGKeys();
/**
* @brief Return the response from the passphrase dialog.
* @param cancel Whether the dialog was cancelled.
* @param passphrase The passphrase entered, if not cancelled.
*/
Q_INVOKABLE void responsePassphraseDialog(bool cancel, QString passphrase);
// Password store-related methods
/**
* @brief Launch the job to shows the password associated with the specified URL.
* @param url The URL pointing to the password store entry.
* @return True if the job was start successfully, false otherwise.
*/
Q_INVOKABLE bool show(QUrl url);
/**
* @brief Launch the job to delete the password store.
* @return True if if the job was start successfully, false otherwise.
*/
Q_INVOKABLE bool deletePasswordStore();
}; };
#endif #endif

View File

@ -6,69 +6,148 @@
using namespace GpgME; using namespace GpgME;
/**
* @class UserIdModel
* @brief A model representing a user ID associated with a GPG key.
*
* This class encapsulates the user ID information (UID) for a GPG key, providing access
* to the UID's identifier, name, and email.
*/
class UserIdModel : public QObject
{
Q_OBJECT
Q_PROPERTY(QString uid READ uid CONSTANT)
Q_PROPERTY(QString name READ name CONSTANT)
Q_PROPERTY(QString email READ email CONSTANT)
private:
UserID m_user_id; /**< The GPG UserID associated with the model. */
public:
/**
* @brief Constructs a UserIdModel for the given UserID.
* @param key The GPG UserID to model.
*/
UserIdModel(UserID key) : m_user_id(key) {}
/**
* @brief Gets the unique identifier (UID) for this user ID.
* @return The UID as a QString.
*/
QString uid() const
{
return QString::fromUtf8(m_user_id.id());
};
/**
* @brief Gets the name associated with this user ID.
* @return The name as a QString.
*/
QString name() const
{
return QString::fromUtf8(m_user_id.name());
};
/**
* @brief Gets the email associated with this user ID.
* @return The email as a QString.
*/
QString email() const
{
return QString::fromUtf8(m_user_id.email());
};
};
/**
* @class PassKeyModel
* @brief A model representing a GPG key.
*
* This class encapsulates the properties of a GPG key, including its key ID, associated
* user IDs, secret key status, and expiration status. It is used as a model for managing
* GPG keys within an application, providing access to the key's data and its associated user IDs.
*/
class PassKeyModel : public QObject class PassKeyModel : public QObject
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QString uid READ uid WRITE setUid NOTIFY uidChanged MEMBER m_uid) Q_PROPERTY(Key key READ key MEMBER m_key CONSTANT)
Q_PROPERTY(bool secret READ secret WRITE setSecret NOTIFY secretChanged MEMBER m_secret) Q_PROPERTY(QString uid READ uid CONSTANT)
Q_PROPERTY(bool expired READ expired WRITE setExpired NOTIFY expiredChanged MEMBER m_expired) Q_PROPERTY(QList<QObject *> userIds READ userIds CONSTANT)
Q_PROPERTY(bool isSecret READ isSecret CONSTANT)
Q_PROPERTY(bool isExpired READ isExpired CONSTANT)
QString m_uid; private:
bool m_secret; Key m_key; /**< The GPG key associated with the model. */
bool m_expired;
public: public:
PassKeyModel(QString uid, bool secret, bool expired): /**
m_uid(uid), * @brief Constructs a PassKeyModel for the given GPG key.
m_secret(secret), * @param key The GPG key to model.
m_expired(expired) */
{}; PassKeyModel(Key key) : m_key(key) {}
PassKeyModel(Key key): /**
PassKeyModel(QString::fromUtf8(key.keyID()), key.hasSecret(), key.isExpired()) * @brief Converts a vector of GPG keys into a list of PassKeyModel objects.
{}; * @param keys The vector of GPG keys to convert.
* @return A QList of PassKeyModel objects representing the keys.
static QList<QObject *> keysToPassKeyQObjectList(std::vector<Key> keys) */
static QList<QObject *> keysToPassKey(std::vector<Key> keys)
{ {
QList<QObject *> r; QList<QObject *> ret;
std::for_each(keys.begin(), keys.end(), [&r](Key k) { std::for_each(keys.begin(), keys.end(), [&ret](Key k) {
r.append(new PassKeyModel(k)); ret.append(new PassKeyModel(k));
}); });
return r; return ret;
}; };
/**
* @brief Gets the GPG key associated with this model.
* @return The GPG key.
*/
Key key() const
{
return m_key;
};
/**
* @brief Gets the unique identifier (UID) for this GPG key.
* @return The UID as a QString.
*/
QString uid() const QString uid() const
{ {
return m_uid; return QString::fromUtf8(m_key.keyID());
};
bool secret() const
{
return m_secret;
};
bool expired() const
{
return m_expired;
}; };
void setUid(QString uid) /**
* @brief Gets the list of user IDs associated with this GPG key.
* @return A list of UserIdModel objects representing the user IDs.
*/
QList<QObject *> userIds() const
{ {
m_uid = uid; auto user_ids = m_key.userIDs();
emit uidChanged(uid); QList<QObject *> ret;
} std::for_each(user_ids.begin(), user_ids.end(), [&ret](UserID k) {
void setSecret(bool secret) ret.append(new UserIdModel(k));
{ });
m_secret = secret; return ret;
emit secretChanged(secret); };
}
void setExpired(bool expired)
{
m_expired = expired;
emit expiredChanged(expired);
}
signals: /**
void uidChanged(QString); * @brief Checks if the GPG key is a secret key.
void secretChanged(bool); * @return True if the key is a secret key, false otherwise.
void expiredChanged(bool); */
bool isSecret() const
{
return m_key.hasSecret();
};
/**
* @brief Checks if the GPG key is expired.
* @return True if the key is expired, false otherwise.
*/
bool isExpired() const
{
return m_key.isExpired();
};
}; };
#endif #endif

View File

@ -1,62 +1,111 @@
#ifndef UTPASSPHRASEPROVIDER_H #ifndef UTPASSPHRASEPROVIDER_H
#define UTPASSPHRASEPROVIDER_H #define UTPASSPHRASEPROVIDER_H
#include <QDebug>
#include <stdio.h> #include <stdio.h>
#include <QObject> #include <QObject>
#include <QQmlProperty> #include <QQmlProperty>
#include <QEventLoop> #include <QEventLoop>
#include <QSemaphore> #include <QSemaphore>
#include <gpgme++/interfaces/passphraseprovider.h> #include <gpgme++/interfaces/passphraseprovider.h>
#include "passphraseprovider.h"
#include "gpg.h" #include "gpg.h"
/**
* @class UTPassphraseProvider
* @brief A passphrase provider for GPG operations that interacts with a QML dialog.
*
* This class implements the `PassphraseProvider` interface from GPGME and is responsible for
* obtaining passphrases for GPG operations.
*/
class UTPassphraseProvider : public QObject, public PassphraseProvider class UTPassphraseProvider : public QObject, public PassphraseProvider
{ {
Q_OBJECT Q_OBJECT
private:
std::unique_ptr<QEventLoop> m_loop;
std::unique_ptr<QSemaphore> m_sem;
char *m_passphrase;
bool m_canceled;
public slots: public slots:
void handleResponse(bool canceled, QString p) /**
* @brief Slot to handle the user's response from the passphrase dialog.
*
* This method processes the response from the passphrase dialog. If the user provides a passphrase,
* it is stored; if the operation is canceled, a flag is set.
*
* @param canceled Whether the user canceled the passphrase entry.
* @param passphrase The passphrase entered by the user.
*/
void handleResponse(bool canceled, QString passphrase)
{ {
qDebug() << "call handleResponse";
if (!canceled) if (!canceled)
gpgrt_asprintf(&m_passphrase, "%s", p.toUtf8().constData()); gpgrt_asprintf(&m_passphrase, "%s", passphrase.toUtf8().constData());
else else
m_canceled = true; m_canceled = true;
m_loop->quit(); emit unlockEventLoop();
}; };
signals:
/**
* @brief Signal to unlock the event loop.
*
* This signal is emitted when the passphrase has been entered or the operation has been canceled,
* unlocking the event loop waiting for the response.
*/
void unlockEventLoop();
private:
std::unique_ptr<QSemaphore> m_sem; /**< Semaphore for managing access. */
char *m_passphrase; /**< The passphrase provided by the user. */
bool m_canceled; /**< Flag indicating whether the passphrase operation was canceled. */
QObject *m_window; /**< The window object that triggers the QML dialog. */
public: public:
UTPassphraseProvider(): /**
m_loop(std::unique_ptr<QEventLoop>(new QEventLoop)), * @brief Constructs a UTPassphraseProvider.
m_sem(std::unique_ptr<QSemaphore>(new QSemaphore(1))), *
m_passphrase(nullptr), * Initializes the semaphore, passphrase, and canceled flag. Sets the window object that will
m_canceled(false) * trigger the passphrase dialog.
{} *
* @param window The QObject representing the window that interacts with QML.
*/
UTPassphraseProvider(QObject* window)
: m_sem(std::make_unique<QSemaphore>(1)),
m_passphrase(nullptr),
m_canceled(false),
m_window(window)
{
qDebug() << "Initialize UTPassphraseProvider";
}
char *getPassphrase( const char *useridHint, /**
const char *description, * @brief Implements the PassphraseProvider's `getPassphrase` method.
bool previousWasBad, *
bool &canceled ) Q_DECL_OVERRIDE { * This method is called by GPGME to retrieve the passphrase needed for GPG operations. It triggers
if (!m_sem->tryAcquire(1, 3000)) * a QML dialog for the user to input their passphrase. The method waits for the response and returns
* the passphrase if successful, or null if canceled.
*
* @param useridHint A hint for the user ID to which the passphrase corresponds.
* @param description A description of the passphrase request.
* @param previousWasBad Flag indicating whether the previous passphrase attempt was incorrect.
* @param canceled Reference to a boolean flag that will be set if the operation is canceled.
*
* @return The passphrase as a `char *` or `nullptr` if canceled.
*/
char *getPassphrase(const char *useridHint,
const char *description,
bool previousWasBad,
bool &canceled) Q_DECL_OVERRIDE {
qDebug() << "Call the getPassphrase";
if (!this->m_sem->tryAcquire(1, 500))
{ {
qWarning() << "Cannot acquire UTPassphraseProvider semaphore."; qWarning() << "Cannot acquire UTPassphraseProvider semaphore.";
canceled = true; canceled = true;
return nullptr; return nullptr;
} }
m_passphrase = nullptr; this->m_passphrase = nullptr;
m_canceled = false; this->m_canceled = false;
qDebug() << "Call the QML Dialog Passphrase Provider"; qDebug() << "Call the QML Dialog Passphrase Provider";
QMetaObject::invokeMethod( QMetaObject::invokeMethod(
Gpg::instance()->getWindow(), "callPassphraseDialog", this->m_window, "callPassphraseDialog",
Q_ARG(QVariant, useridHint), Q_ARG(QVariant, useridHint),
Q_ARG(QVariant, description), Q_ARG(QVariant, description),
Q_ARG(QVariant, previousWasBad) Q_ARG(QVariant, previousWasBad)
@ -64,25 +113,24 @@ public:
qDebug() << "Waiting for response"; qDebug() << "Waiting for response";
QObject::connect( QEventLoop loop;
Gpg::instance()->getWindow(), SIGNAL(responsePassphraseDialog(bool, QString)), QObject::connect(this, &UTPassphraseProvider::unlockEventLoop, &loop, &QEventLoop::quit);
this, SLOT(handleResponse(bool, QString)) loop.exec();
);
m_loop->exec();
qDebug() << "Prepare Returns"; qDebug() << "Prepare Returns";
char *ret; char *ret;
gpgrt_asprintf(&ret, "%s", m_passphrase); gpgrt_asprintf(&ret, "%s", m_passphrase);
canceled = m_canceled; canceled = this->m_canceled;
qDebug() << "Clean"; qDebug() << "Clean";
if (m_passphrase) if (this->m_passphrase)
{ {
free(m_passphrase); free(m_passphrase);
} }
m_canceled = false; this->m_canceled = false;
m_sem->release(1); this->m_sem->release(1);
return ret; return ret;
}; };
}; };
#endif #endif

View File

@ -1,4 +1,4 @@
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17")
set(PLUGIN "Utils") set(PLUGIN "Utils")
set( set(
@ -23,15 +23,10 @@ add_library(${PLUGIN} MODULE ${SRC})
set_target_properties(${PLUGIN} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PLUGIN}) set_target_properties(${PLUGIN} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PLUGIN})
qt5_use_modules(${PLUGIN} Qml Quick DBus) qt5_use_modules(${PLUGIN} Qml Quick DBus)
set(EXTERNAL_LIBS "${CMAKE_SOURCE_DIR}/build/${ARCH_TRIPLET}/quazip/install/") add_library(libquazip5 SHARED IMPORTED)
set_property(TARGET libquazip5 PROPERTY IMPORTED_LOCATION "/usr/lib/${ARCH_TRIPLET}/libquazip5.so")
INCLUDE_DIRECTORIES(${EXTERNAL_LIBS}/include)
add_library(quazip STATIC IMPORTED)
set_property(TARGET quazip PROPERTY IMPORTED_LOCATION "${EXTERNAL_LIBS}/lib/libquazip5.a")
target_link_libraries(${PLUGIN} quazip)
target_link_libraries(${PLUGIN} libquazip5)
set(QT_IMPORTS_DIR "/lib/${ARCH_TRIPLET}") set(QT_IMPORTS_DIR "/lib/${ARCH_TRIPLET}")

View File

@ -7,8 +7,6 @@
#include "utils.h" #include "utils.h"
Utils::Utils() {};
bool Utils::unzip(QUrl zip_url, QString dir_out_path) bool Utils::unzip(QUrl zip_url, QString dir_out_path)
{ {
auto tmp_dir_path = QStandardPaths::writableLocation( auto tmp_dir_path = QStandardPaths::writableLocation(
@ -31,7 +29,7 @@ bool Utils::unzip(QUrl zip_url, QString dir_out_path)
qDebug() << "Guessing if it should remove a single root folder"; qDebug() << "Guessing if it should remove a single root folder";
QStringList files_in_tmp_dir = tmp_dir.entryList(QDir::AllEntries | QDir::Hidden | QStringList files_in_tmp_dir = tmp_dir.entryList(QDir::AllEntries | QDir::Hidden |
QDir::NoDotAndDotDot); QDir::NoDotAndDotDot);
auto dir_import_path = auto dir_import_path =
files_in_tmp_dir.length() == 1 ? files_in_tmp_dir.length() == 1 ?
@ -60,3 +58,10 @@ bool Utils::rmDir(QUrl dir_url)
QDir dir(dir_url.toLocalFile()); QDir dir(dir_url.toLocalFile());
return dir.removeRecursively(); return dir.removeRecursively();
} }
QString Utils::manifestPath()
{
auto path = QDir(QDir::currentPath()).filePath("manifest_.json");
qDebug() << "Manifest path : " << path;
return path;
}

View File

@ -5,17 +5,70 @@
#include <QUrl> #include <QUrl>
#include <QQuickWindow> #include <QQuickWindow>
/**
* @class Utils
* @brief A utility class that provides helper functions for file and directory operations.
*
* The `Utils` class contains various helper methods such as for managing files and directories, including unzipping files
* and removing files or directories.
*/
class Utils : public QObject class Utils : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
Utils(); /**
* @brief Default constructor for the Utils class.
*/
Utils() = default;
/**
* @brief Default destructor for the Utils class.
*/
~Utils() override = default; ~Utils() override = default;
/**
* @brief Unzips a ZIP file to the specified output directory.
*
* This method extracts the contents of a ZIP file from the specified URL and saves them to the provided
* output directory path.
*
* @param zip_url The URL of the ZIP file to unzip.
* @param dir_out The output directory where the contents of the ZIP file should be extracted.
* @return `true` if the unzipping operation was successful, `false` otherwise.
*/
Q_INVOKABLE bool unzip(QUrl zip_url, QString dir_out); Q_INVOKABLE bool unzip(QUrl zip_url, QString dir_out);
/**
* @brief Removes a file at the specified URL.
*
* This method deletes a file at the specified URL.
*
* @param file_url The URL of the file to delete.
* @return `true` if the file was successfully removed, `false` otherwise.
*/
Q_INVOKABLE bool rmFile(QUrl file_url); Q_INVOKABLE bool rmFile(QUrl file_url);
/**
* @brief Removes a directory at the specified URL.
*
* This method deletes a directory at the specified URL, along with its contents.
*
* @param dir_url The URL of the directory to remove.
* @return `true` if the directory was successfully removed, `false` otherwise.
*/
Q_INVOKABLE bool rmDir(QUrl dir_url); Q_INVOKABLE bool rmDir(QUrl dir_url);
/**
* @brief Retrieves the path to the manifest data.
*
* This function returns the full path to the manifest file used by the application.
*
* @return A QString containing the manifest file path.
*/
Q_INVOKABLE QString manifestPath();
}; };
#endif #endif

190
po/ca.po Normal file
View File

@ -0,0 +1,190 @@
# UTPass
# Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
# This file is distributed under the same license as the utpass.qrouland package.
# Joan CiberSheep <cibersheep@gmail.com>, 2019.
msgid ""
msgstr ""
"Project-Id-Version: utpass.qrouland\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-09-21 14:01+0000\n"
"PO-Revision-Date: 2019-09-30 08:53+0000\n"
"Last-Translator: Joan CiberSheep <cibersheep@gmail.com>\n"
"Language-Team: Catalan <https://translate-ut.org/projects/utpass/utpass/ca/>"
"\n"
"Language: ca\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 3.8\n"
#: ../qml/components/FileDir.qml:71
msgid "Decryption failed !"
msgstr "Ha fallat la desencriptació"
#: ../qml/dialogs/DoubleValidationDialog.qml:28
#: ../qml/dialogs/PassphraseDialog.qml:29
#: ../qml/dialogs/SimpleValidationDialog.qml:19
msgid "Ok"
msgstr "D'acord"
#: ../qml/dialogs/DoubleValidationDialog.qml:44
#: ../qml/dialogs/PassphraseDialog.qml:41
#: ../qml/dialogs/SimpleValidationDialog.qml:30
msgid "Cancel"
msgstr "Cancel·la"
#: ../qml/dialogs/ErrorDialog.qml:12
msgid "Error !"
msgstr "Error"
#: ../qml/dialogs/ErrorDialog.qml:15 ../qml/dialogs/SuccessDialog.qml:15
msgid "OK"
msgstr "D'acord"
#: ../qml/dialogs/PassphraseDialog.qml:7
msgid "Authentication required"
msgstr "Es requereix autenticació"
#: ../qml/dialogs/PassphraseDialog.qml:8
msgid "Enter passphrase:"
msgstr "Entreu la frase de pas:"
#: ../qml/dialogs/PassphraseDialog.qml:20
msgid "passphrase"
msgstr "frase de pas"
#: ../qml/dialogs/SuccessDialog.qml:12
msgid "Success !"
msgstr "Èxit"
#: ../qml/pages/headers/MainHeader.qml:8 ../qml/pages/headers/StackHeader.qml:8
#: UTPass.desktop.in.h:1
msgid "UTPass"
msgstr "UTPass"
#: ../qml/pages/headers/MainHeader.qml:23
msgid "Search"
msgstr "Cerca"
#: ../qml/pages/headers/MainHeader.qml:51 ../qml/pages/settings/Settings.qml:14
msgid "Settings"
msgstr "Paràmetres"
#: ../qml/pages/headers/MainHeader.qml:58 ../qml/pages/Info.qml:11
msgid "Info"
msgstr "Informació"
#: ../qml/pages/Info.qml:50
msgid "<b>Version</b>"
msgstr "<b>Versió</b>"
#: ../qml/pages/Info.qml:68
msgid "<b>Maintainer</>"
msgstr "<b>Manteniment</>"
#: ../qml/pages/Info.qml:90
msgid "Suggest improvement(s) or report a bug(s)"
msgstr "Suggeriu millores o informeu d'un error"
#: ../qml/pages/Info.qml:94
msgid "Access to the source code"
msgstr "Accediu al codi font"
#: ../qml/pages/Info.qml:101
msgid "Released under the terms of the GNU GPL v3"
msgstr "Publicada sota els termes de la GNU GPL v3"
#: ../qml/pages/PasswordList.qml:23
msgid "Back"
msgstr "Enrere"
#: ../qml/pages/PasswordList.qml:43
msgid ""
"No password found<br>You can import a password store zip in the settings"
msgstr ""
"No s'ha trobat cap contrasenya<br>Podeu un importar un zip de contrasenyes a "
"la configuració"
#: ../qml/pages/settings/ImportKeyFile.qml:17
msgid "GPG Key Import"
msgstr "Importa clau GPG"
#: ../qml/pages/settings/ImportKeyFile.qml:69
msgid "Key import failed !"
msgstr "Ha fallat la importació de la clau"
#: ../qml/pages/settings/ImportKeyFile.qml:76
msgid "Key successfully imported !"
msgstr "S'ha importat la clau amb èxit"
#: ../qml/pages/settings/ImportZip.qml:17
msgid "Zip Password Store Import"
msgstr "Importa zip de contrasenyes"
#: ../qml/pages/settings/ImportZip.qml:72
msgid ""
"Importing a new zip will delete<br>any existing password store!<br>Continue ?"
msgstr ""
"A l'Importar un nou zip s'eliminarà<br>qualsevol contrasenya desada "
"anteriorment<br>Voleu continuar ?"
#: ../qml/pages/settings/ImportZip.qml:82
msgid "Password store import failed !"
msgstr "Ha fallat la importació de contrasenyes"
#: ../qml/pages/settings/ImportZip.qml:89
msgid "Password store sucessfully imported !"
msgstr "S'han importat les contrasenyes amb èxit"
#: ../qml/pages/settings/InfoKeys.qml:16
msgid "Info Keys"
msgstr "Informació de la clau"
#: ../qml/pages/settings/InfoKeys.qml:44
msgid "Key id : %1"
msgstr "Identificació de la clau: %1"
#: ../qml/pages/settings/InfoKeys.qml:49
msgid "Delete this key"
msgstr "Elimina aquesta clau"
#: ../qml/pages/settings/InfoKeys.qml:68
msgid "You're are about to delete<br>%1<br>Continue ?"
msgstr "Esteu a punt d'eliminar<br>%1<br>Voleu continuar?"
#: ../qml/pages/settings/InfoKeys.qml:71
msgid "%1<br>will be definitively removed.<br>Continue ?"
msgstr "%1<br>s'eliminarà definitivament.<br>Voleu continuar?"
#: ../qml/pages/settings/InfoKeys.qml:87
msgid "Key removal failed !"
msgstr "Ha fallat l'eliminació de la clau"
#: ../qml/pages/settings/InfoKeys.qml:94
msgid "Key successfully deleted !"
msgstr "S'ha eliminat la clau amb èxit"
#: ../qml/pages/settings/Settings.qml:28
msgid "GPG"
msgstr "GPG"
#: ../qml/pages/settings/Settings.qml:32
msgid "Import a GPG key file"
msgstr "Importa un fitxer de clau GPG"
#: ../qml/pages/settings/Settings.qml:36
msgid "Show GPG keys"
msgstr "Mostra les claus GPG"
#: ../qml/pages/settings/Settings.qml:43
msgid "Password Store"
msgstr "Contrasenyes desades"
#: ../qml/pages/settings/Settings.qml:47
msgid "Import a Password Store Zip"
msgstr "Importa un zip de contrasenyes"
#: ../qml/pages/settings/Settings.qml:56
msgid "Warning: importing delete any exiting Password Store"
msgstr "Alertau: s'eliminarà qualsevol contrasenya desada a l'importar"

193
po/es.po Normal file
View File

@ -0,0 +1,193 @@
# UTPass
# Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
# This file is distributed under the same license as the utpass.qrouland package.
# Advocatux <advocatux@airpost.net>, 2019.
# Reda <redxxiii@zaclys.net>, 2019.
msgid ""
msgstr ""
"Project-Id-Version: utpass.qrouland\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-09-21 14:01+0000\n"
"PO-Revision-Date: 2019-09-30 08:53+0000\n"
"Last-Translator: Advocatux <advocatux@airpost.net>\n"
"Language-Team: Spanish <https://translate-ut.org/projects/utpass/utpass/es/>"
"\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 3.8\n"
#: ../qml/components/FileDir.qml:71
msgid "Decryption failed !"
msgstr "¡Falló el descifrado!"
#: ../qml/dialogs/DoubleValidationDialog.qml:28
#: ../qml/dialogs/PassphraseDialog.qml:29
#: ../qml/dialogs/SimpleValidationDialog.qml:19
msgid "Ok"
msgstr "Aceptar"
#: ../qml/dialogs/DoubleValidationDialog.qml:44
#: ../qml/dialogs/PassphraseDialog.qml:41
#: ../qml/dialogs/SimpleValidationDialog.qml:30
msgid "Cancel"
msgstr "Cancelar"
#: ../qml/dialogs/ErrorDialog.qml:12
msgid "Error !"
msgstr "¡Error!"
#: ../qml/dialogs/ErrorDialog.qml:15 ../qml/dialogs/SuccessDialog.qml:15
msgid "OK"
msgstr "Aceptar"
#: ../qml/dialogs/PassphraseDialog.qml:7
msgid "Authentication required"
msgstr "Se requiere autentificación"
#: ../qml/dialogs/PassphraseDialog.qml:8
msgid "Enter passphrase:"
msgstr "Introducir contraseña:"
#: ../qml/dialogs/PassphraseDialog.qml:20
msgid "passphrase"
msgstr "contraseña"
#: ../qml/dialogs/SuccessDialog.qml:12
msgid "Success !"
msgstr "¡Correcto!"
#: ../qml/pages/headers/MainHeader.qml:8 ../qml/pages/headers/StackHeader.qml:8
#: UTPass.desktop.in.h:1
msgid "UTPass"
msgstr "UTPass"
#: ../qml/pages/headers/MainHeader.qml:23
msgid "Search"
msgstr "Buscar"
#: ../qml/pages/headers/MainHeader.qml:51 ../qml/pages/settings/Settings.qml:14
msgid "Settings"
msgstr "Configuración"
#: ../qml/pages/headers/MainHeader.qml:58 ../qml/pages/Info.qml:11
msgid "Info"
msgstr "Información"
#: ../qml/pages/Info.qml:50
msgid "<b>Version</b>"
msgstr "<b>Versión</b>"
#: ../qml/pages/Info.qml:68
msgid "<b>Maintainer</>"
msgstr "<b>Mantenedor</b>"
#: ../qml/pages/Info.qml:90
msgid "Suggest improvement(s) or report a bug(s)"
msgstr "Sugerir mejora(s) o reportar problema(s)"
#: ../qml/pages/Info.qml:94
msgid "Access to the source code"
msgstr "Acceso al código fuente"
#: ../qml/pages/Info.qml:101
msgid "Released under the terms of the GNU GPL v3"
msgstr "Publicado bajo los términos de la GNU GPL v3"
#: ../qml/pages/PasswordList.qml:23
msgid "Back"
msgstr "Atrás"
#: ../qml/pages/PasswordList.qml:43
msgid ""
"No password found<br>You can import a password store zip in the settings"
msgstr ""
"No se han encontrado contraseñas<br>Puede importar un archivo zip de "
"almacenamiento de contraseñas en la configuración"
#: ../qml/pages/settings/ImportKeyFile.qml:17
msgid "GPG Key Import"
msgstr "Importar clave GPG"
#: ../qml/pages/settings/ImportKeyFile.qml:69
msgid "Key import failed !"
msgstr "¡Falló la importación de la clave!"
#: ../qml/pages/settings/ImportKeyFile.qml:76
msgid "Key successfully imported !"
msgstr "¡Clave importada correctamente!"
#: ../qml/pages/settings/ImportZip.qml:17
msgid "Zip Password Store Import"
msgstr "Importar zip de almacenamiento de contraseñas"
#: ../qml/pages/settings/ImportZip.qml:72
msgid ""
"Importing a new zip will delete<br>any existing password store!<br>Continue ?"
msgstr ""
"¡La importación de un nuevo zip borrará<br>cualquier contraseña "
"almacenada!<br>¿Continuar?"
#: ../qml/pages/settings/ImportZip.qml:82
msgid "Password store import failed !"
msgstr "¡Falló la importación del archivo de contraseñas!"
#: ../qml/pages/settings/ImportZip.qml:89
msgid "Password store sucessfully imported !"
msgstr ""
"¡La importación del archivo de contraseñas se ha realizado correctamente!"
#: ../qml/pages/settings/InfoKeys.qml:16
msgid "Info Keys"
msgstr "Información de las claves"
#: ../qml/pages/settings/InfoKeys.qml:44
msgid "Key id : %1"
msgstr "Identificador de clave: %1"
#: ../qml/pages/settings/InfoKeys.qml:49
msgid "Delete this key"
msgstr "Eliminar esta clave"
#: ../qml/pages/settings/InfoKeys.qml:68
msgid "You're are about to delete<br>%1<br>Continue ?"
msgstr "Está a punto de eliminar<br>%1<br>¿Continuar?"
#: ../qml/pages/settings/InfoKeys.qml:71
msgid "%1<br>will be definitively removed.<br>Continue ?"
msgstr "1%<br>será eliminada definitivamente.<br>¿Continuar?"
#: ../qml/pages/settings/InfoKeys.qml:87
msgid "Key removal failed !"
msgstr "¡Falló la eliminación de la clave!"
#: ../qml/pages/settings/InfoKeys.qml:94
msgid "Key successfully deleted !"
msgstr "¡La clave ha sido eliminada correctamente!"
#: ../qml/pages/settings/Settings.qml:28
msgid "GPG"
msgstr "GPG"
#: ../qml/pages/settings/Settings.qml:32
msgid "Import a GPG key file"
msgstr "Importar un archivo de clave GPG"
#: ../qml/pages/settings/Settings.qml:36
msgid "Show GPG keys"
msgstr "Mostrar las claves GPG"
#: ../qml/pages/settings/Settings.qml:43
msgid "Password Store"
msgstr "Archivo de contraseñas"
#: ../qml/pages/settings/Settings.qml:47
msgid "Import a Password Store Zip"
msgstr "Importar un archivo Zip de contraseñas"
#: ../qml/pages/settings/Settings.qml:56
msgid "Warning: importing delete any exiting Password Store"
msgstr ""
"Advertencia: Importar elimina cualquier archivo de contraseñas existente"

191
po/fr.po Normal file
View File

@ -0,0 +1,191 @@
# UTPass
# Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
# This file is distributed under the same license as the utpass.qrouland package.
# Anne17 <>anneonyme017@netcourrier.com>, 2019.
# Quentin Rouland <quentin@qrouland.com>, 2019.
# Reda <redxxiii@zaclys.net>, 2019.
msgid ""
msgstr ""
"Project-Id-Version: utpass.qrouland\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-09-21 14:01+0000\n"
"PO-Revision-Date: 2019-10-05 17:01+0000\n"
"Last-Translator: Reda <redxxiii@zaclys.net>\n"
"Language-Team: French <https://translate-ut.org/projects/utpass/utpass/fr/>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 3.8\n"
#: ../qml/components/FileDir.qml:71
msgid "Decryption failed !"
msgstr "Échec du déchiffrement !"
#: ../qml/dialogs/DoubleValidationDialog.qml:28
#: ../qml/dialogs/PassphraseDialog.qml:29
#: ../qml/dialogs/SimpleValidationDialog.qml:19
msgid "Ok"
msgstr "Valider"
#: ../qml/dialogs/DoubleValidationDialog.qml:44
#: ../qml/dialogs/PassphraseDialog.qml:41
#: ../qml/dialogs/SimpleValidationDialog.qml:30
msgid "Cancel"
msgstr "Annuler"
#: ../qml/dialogs/ErrorDialog.qml:12
msgid "Error !"
msgstr "Erreur !"
#: ../qml/dialogs/ErrorDialog.qml:15 ../qml/dialogs/SuccessDialog.qml:15
msgid "OK"
msgstr "Valider"
#: ../qml/dialogs/PassphraseDialog.qml:7
msgid "Authentication required"
msgstr "Authentification nécessaire"
#: ../qml/dialogs/PassphraseDialog.qml:8
msgid "Enter passphrase:"
msgstr "Saisissez votre mot de passe :"
#: ../qml/dialogs/PassphraseDialog.qml:20
msgid "passphrase"
msgstr "Mot de passe"
#: ../qml/dialogs/SuccessDialog.qml:12
msgid "Success !"
msgstr "Réussite !"
#: ../qml/pages/headers/MainHeader.qml:8 ../qml/pages/headers/StackHeader.qml:8
#: UTPass.desktop.in.h:1
msgid "UTPass"
msgstr "UTPass"
#: ../qml/pages/headers/MainHeader.qml:23
msgid "Search"
msgstr "Rechercher"
#: ../qml/pages/headers/MainHeader.qml:51 ../qml/pages/settings/Settings.qml:14
msgid "Settings"
msgstr "Paramètres"
#: ../qml/pages/headers/MainHeader.qml:58 ../qml/pages/Info.qml:11
msgid "Info"
msgstr "Infos"
#: ../qml/pages/Info.qml:50
msgid "<b>Version</b>"
msgstr "<b>Version</b>"
#: ../qml/pages/Info.qml:68
msgid "<b>Maintainer</>"
msgstr "<b>Mainteneur</>"
#: ../qml/pages/Info.qml:90
msgid "Suggest improvement(s) or report a bug(s)"
msgstr "Suggérer des améliorations ou signaler des problèmes"
#: ../qml/pages/Info.qml:94
msgid "Access to the source code"
msgstr "Accéder au code source"
#: ../qml/pages/Info.qml:101
msgid "Released under the terms of the GNU GPL v3"
msgstr "Distribuée sous les termes de la GNU GPL v3"
#: ../qml/pages/PasswordList.qml:23
msgid "Back"
msgstr "Retour"
#: ../qml/pages/PasswordList.qml:43
msgid ""
"No password found<br>You can import a password store zip in the settings"
msgstr ""
"Aucun mot de passe trouvé<br>Vous pouvez importer un fichier zip de stockage "
"de mots de passe dans les paramètres"
#: ../qml/pages/settings/ImportKeyFile.qml:17
msgid "GPG Key Import"
msgstr "Importation de clé GPG"
#: ../qml/pages/settings/ImportKeyFile.qml:69
msgid "Key import failed !"
msgstr "L'importation de la clé a échoué !"
#: ../qml/pages/settings/ImportKeyFile.qml:76
msgid "Key successfully imported !"
msgstr "Clé importée avec succès !"
#: ../qml/pages/settings/ImportZip.qml:17
msgid "Zip Password Store Import"
msgstr "Importation d'un fichier zip de mots de passe"
#: ../qml/pages/settings/ImportZip.qml:72
msgid ""
"Importing a new zip will delete<br>any existing password store!<br>Continue ?"
msgstr ""
"L'importation d'un nouveau zip supprimera<br>tout mot de passe stocké!<br>"
"Continuer ?"
#: ../qml/pages/settings/ImportZip.qml:82
msgid "Password store import failed !"
msgstr "L'importation du fichier de mots de passe a échoué !"
#: ../qml/pages/settings/ImportZip.qml:89
msgid "Password store sucessfully imported !"
msgstr "L'importation du fichier de mots de passe a réussi !"
#: ../qml/pages/settings/InfoKeys.qml:16
msgid "Info Keys"
msgstr "Informations sur les clés"
#: ../qml/pages/settings/InfoKeys.qml:44
msgid "Key id : %1"
msgstr "Identifiant de la clé : %1"
#: ../qml/pages/settings/InfoKeys.qml:49
msgid "Delete this key"
msgstr "Supprimer cette clé"
#: ../qml/pages/settings/InfoKeys.qml:68
msgid "You're are about to delete<br>%1<br>Continue ?"
msgstr "Vous êtes sur le point de supprimer<br>%1<br>Voulez-vous continuer ?"
#: ../qml/pages/settings/InfoKeys.qml:71
msgid "%1<br>will be definitively removed.<br>Continue ?"
msgstr "%1<br>sera définitivement supprimée.<br>Voulez-vous continuer ?"
#: ../qml/pages/settings/InfoKeys.qml:87
msgid "Key removal failed !"
msgstr "La suppression de clé a échoué !"
#: ../qml/pages/settings/InfoKeys.qml:94
msgid "Key successfully deleted !"
msgstr "La clé a été supprimée avec succès !"
#: ../qml/pages/settings/Settings.qml:28
msgid "GPG"
msgstr "GPG"
#: ../qml/pages/settings/Settings.qml:32
msgid "Import a GPG key file"
msgstr "Importer un fichier de clés GPG"
#: ../qml/pages/settings/Settings.qml:36
msgid "Show GPG keys"
msgstr "Afficher les clés GPG"
#: ../qml/pages/settings/Settings.qml:43
msgid "Password Store"
msgstr "Fichier de mots de passe"
#: ../qml/pages/settings/Settings.qml:47
msgid "Import a Password Store Zip"
msgstr "Importer un fichier Zip de mots de passe"
#: ../qml/pages/settings/Settings.qml:56
msgid "Warning: importing delete any exiting Password Store"
msgstr "Attention : l'importation supprime les fichiers précédents"

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: utpass.qrouland\n" "Project-Id-Version: utpass.qrouland\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-09-21 14:01+0000\n" "POT-Creation-Date: 2025-01-20 15:00+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -17,169 +17,240 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n" "Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: ../qml/components/FileDir.qml:71
msgid "Decryption failed !"
msgstr ""
#: ../qml/dialogs/DoubleValidationDialog.qml:28
#: ../qml/dialogs/PassphraseDialog.qml:29
#: ../qml/dialogs/SimpleValidationDialog.qml:19
msgid "Ok"
msgstr ""
#: ../qml/dialogs/DoubleValidationDialog.qml:44
#: ../qml/dialogs/PassphraseDialog.qml:41
#: ../qml/dialogs/SimpleValidationDialog.qml:30
msgid "Cancel"
msgstr ""
#: ../qml/dialogs/ErrorDialog.qml:12 #: ../qml/dialogs/ErrorDialog.qml:12
msgid "Error !" msgid "Error !"
msgstr "" msgstr ""
#: ../qml/dialogs/ErrorDialog.qml:15 ../qml/dialogs/SuccessDialog.qml:15 #: ../qml/dialogs/ErrorDialog.qml:16
msgid "OK" msgid "Close"
msgstr "" msgstr ""
#: ../qml/dialogs/PassphraseDialog.qml:7 #: ../qml/dialogs/PassphraseDialog.qml:15
msgid "Authentication required" msgid "Authentication required"
msgstr "" msgstr ""
#: ../qml/dialogs/PassphraseDialog.qml:8 #: ../qml/dialogs/PassphraseDialog.qml:16
msgid "Enter passphrase:" msgid "Enter passphrase:"
msgstr "" msgstr ""
#: ../qml/dialogs/PassphraseDialog.qml:20 #: ../qml/dialogs/PassphraseDialog.qml:21
msgid "passphrase" msgid "passphrase"
msgstr "" msgstr ""
#: ../qml/dialogs/PassphraseDialog.qml:29
#: ../qml/dialogs/SimpleValidationDialog.qml:9
msgid "Ok"
msgstr ""
#: ../qml/dialogs/PassphraseDialog.qml:41
#: ../qml/dialogs/SimpleValidationDialog.qml:34
msgid "Cancel"
msgstr ""
#: ../qml/dialogs/SuccessDialog.qml:12 #: ../qml/dialogs/SuccessDialog.qml:12
msgid "Success !" msgid "Success !"
msgstr "" msgstr ""
#: ../qml/pages/headers/MainHeader.qml:8 ../qml/pages/headers/StackHeader.qml:8 #: ../qml/dialogs/SuccessDialog.qml:16
#: UTPass.desktop.in.h:1 msgid "OK"
msgid "UTPass"
msgstr "" msgstr ""
#: ../qml/pages/headers/MainHeader.qml:23 #: ../qml/pages/Info.qml:62
msgid "Search"
msgstr ""
#: ../qml/pages/headers/MainHeader.qml:51 ../qml/pages/settings/Settings.qml:14
msgid "Settings"
msgstr ""
#: ../qml/pages/headers/MainHeader.qml:58 ../qml/pages/Info.qml:11
msgid "Info"
msgstr ""
#: ../qml/pages/Info.qml:50
msgid "<b>Version</b>" msgid "<b>Version</b>"
msgstr "" msgstr ""
#: ../qml/pages/Info.qml:68 #: ../qml/pages/Info.qml:83
msgid "<b>Maintainer</>" msgid "<b>Maintainer</>"
msgstr "" msgstr ""
#: ../qml/pages/Info.qml:90 #: ../qml/pages/Info.qml:110
msgid "Suggest improvement(s) or report a bug(s)" msgid "Suggest improvement(s) or report a bug(s)"
msgstr "" msgstr ""
#: ../qml/pages/Info.qml:94 #: ../qml/pages/Info.qml:115
msgid "Access to the source code" msgid "Access to the source code"
msgstr "" msgstr ""
#: ../qml/pages/Info.qml:101 #: ../qml/pages/Info.qml:123
msgid "Released under the terms of the GNU GPL v3" msgid "Released under the terms of the GNU GPL v3"
msgstr "" msgstr ""
#: ../qml/pages/PasswordList.qml:23 #: ../qml/pages/Info.qml:132 ../qml/pages/headers/MainHeader.qml:33
msgid "Info"
msgstr ""
#: ../qml/pages/PasswordList.qml:44
msgid "No password found"
msgstr ""
#: ../qml/pages/PasswordList.qml:55
msgid "You can import a password store by cloning or"
msgstr ""
#: ../qml/pages/PasswordList.qml:61
msgid "importing a password store zip in the settings"
msgstr ""
#: ../qml/pages/PasswordList.qml:95
msgid "Decryption failed !"
msgstr ""
#: ../qml/pages/PasswordList.qml:109
msgid "Back" msgid "Back"
msgstr "" msgstr ""
#: ../qml/pages/PasswordList.qml:43 #: ../qml/pages/PasswordList.qml:116 ../qml/pages/headers/MainHeader.qml:9
msgid "" #: ../qml/pages/headers/StackHeader.qml:9 UTPass.desktop.in.h:1
"No password found<br>You can import a password store zip in the settings" msgid "UTPass"
msgstr "" msgstr ""
#: ../qml/pages/settings/ImportKeyFile.qml:17 #: ../qml/pages/headers/MainHeader.qml:26 ../qml/pages/settings/Settings.qml:75
msgid "GPG Key Import" msgid "Settings"
msgstr "" msgstr ""
#: ../qml/pages/settings/ImportKeyFile.qml:69 #: ../qml/pages/headers/MainHeader.qml:57
msgid "Search"
msgstr ""
#: ../qml/pages/settings/DeleteRepo.qml:41
#: ../qml/pages/settings/Settings.qml:58
msgid "Delete Password Store"
msgstr ""
#: ../qml/pages/settings/DeleteRepo.qml:54
msgid "You're are about to delete<br>the current Password Store.<br>Continue ?"
msgstr ""
#: ../qml/pages/settings/DeleteRepo.qml:55
#: ../qml/pages/settings/ImportZip.qml:62
#: ../qml/pages/settings/InfoKeys.qml:140
#: ../qml/pages/settings/git/ImportGitClone.qml:55
msgid "Yes"
msgstr ""
#: ../qml/pages/settings/DeleteRepo.qml:68
msgid "Password Store removal failed !"
msgstr ""
#: ../qml/pages/settings/DeleteRepo.qml:77
msgid "Password Store deleted !"
msgstr ""
#: ../qml/pages/settings/DeleteRepo.qml:89
#: ../qml/pages/settings/InfoKeys.qml:182
msgid "Info Keys"
msgstr ""
#: ../qml/pages/settings/ImportKeyFile.qml:57
msgid "Key import failed !" msgid "Key import failed !"
msgstr "" msgstr ""
#: ../qml/pages/settings/ImportKeyFile.qml:76 #: ../qml/pages/settings/ImportKeyFile.qml:66
msgid "Key successfully imported !" msgid "Key successfully imported !"
msgstr "" msgstr ""
#: ../qml/pages/settings/ImportZip.qml:17 #: ../qml/pages/settings/ImportKeyFile.qml:77
msgid "Zip Password Store Import" msgid "GPG Key Import"
msgstr "" msgstr ""
#: ../qml/pages/settings/ImportZip.qml:72 #: ../qml/pages/settings/ImportZip.qml:61
msgid "" msgid ""
"Importing a new zip will delete<br>any existing password store!<br>Continue ?" "Importing a new zip will delete<br>any existing password store!<br>Continue ?"
msgstr "" msgstr ""
#: ../qml/pages/settings/ImportZip.qml:82 #: ../qml/pages/settings/ImportZip.qml:75
msgid "Password store import failed !" msgid "Password store import failed !"
msgstr "" msgstr ""
#: ../qml/pages/settings/ImportZip.qml:89 #: ../qml/pages/settings/ImportZip.qml:84
#: ../qml/pages/settings/git/ImportGitClone.qml:77
msgid "Password store sucessfully imported !" msgid "Password store sucessfully imported !"
msgstr "" msgstr ""
#: ../qml/pages/settings/InfoKeys.qml:16 #: ../qml/pages/settings/ImportZip.qml:96
msgid "Info Keys" msgid "Zip Password Store Import"
msgstr "" msgstr ""
#: ../qml/pages/settings/InfoKeys.qml:44 #: ../qml/pages/settings/InfoKeys.qml:56
msgid "Key id : %1" msgid "Key ID :"
msgstr "" msgstr ""
#: ../qml/pages/settings/InfoKeys.qml:49 #: ../qml/pages/settings/InfoKeys.qml:90
msgid "Users IDs : "
msgstr ""
#: ../qml/pages/settings/InfoKeys.qml:117
msgid "Delete this key" msgid "Delete this key"
msgstr "" msgstr ""
#: ../qml/pages/settings/InfoKeys.qml:68 #: ../qml/pages/settings/InfoKeys.qml:139
msgid "You're are about to delete<br>%1<br>Continue ?" msgid "You're are about to delete<br>%1.<br>Continue ?"
msgstr "" msgstr ""
#: ../qml/pages/settings/InfoKeys.qml:71 #: ../qml/pages/settings/InfoKeys.qml:153
msgid "%1<br>will be definitively removed.<br>Continue ?"
msgstr ""
#: ../qml/pages/settings/InfoKeys.qml:87
msgid "Key removal failed !" msgid "Key removal failed !"
msgstr "" msgstr ""
#: ../qml/pages/settings/InfoKeys.qml:94 #: ../qml/pages/settings/InfoKeys.qml:162
msgid "Key successfully deleted !" msgid "Key successfully deleted !"
msgstr "" msgstr ""
#: ../qml/pages/settings/Settings.qml:28 #: ../qml/pages/settings/InfoKeys.qml:174
msgid "An Error occured getting GPG keys !"
msgstr ""
#: ../qml/pages/settings/Settings.qml:23
msgid "GPG" msgid "GPG"
msgstr "" msgstr ""
#: ../qml/pages/settings/Settings.qml:32 #: ../qml/pages/settings/Settings.qml:29
msgid "Import a GPG key file" msgid "Import a GPG key file"
msgstr "" msgstr ""
#: ../qml/pages/settings/Settings.qml:36 #: ../qml/pages/settings/Settings.qml:34
msgid "Show GPG keys" msgid "Show GPG keys"
msgstr "" msgstr ""
#: ../qml/pages/settings/Settings.qml:43 #: ../qml/pages/settings/Settings.qml:42
msgid "Password Store" msgid "Password Store"
msgstr "" msgstr ""
#: ../qml/pages/settings/Settings.qml:47 #: ../qml/pages/settings/Settings.qml:48
msgid "Import a Password Store using Git"
msgstr ""
#: ../qml/pages/settings/Settings.qml:53
msgid "Import a Password Store Zip" msgid "Import a Password Store Zip"
msgstr "" msgstr ""
#: ../qml/pages/settings/Settings.qml:56 #: ../qml/pages/settings/Settings.qml:67
msgid "Warning: importing delete any exiting Password Store" msgid "Warning: importing delete any exiting Password Store"
msgstr "" msgstr ""
#: ../qml/pages/settings/git/GitCloneHttp.qml:16
#: ../qml/pages/settings/git/GitCloneHttpAuth.qml:16
msgid "Repo Url"
msgstr ""
#: ../qml/pages/settings/git/GitCloneHttp.qml:32
#: ../qml/pages/settings/git/GitCloneHttpAuth.qml:50
msgid "Clone"
msgstr ""
#: ../qml/pages/settings/git/GitCloneHttpAuth.qml:33
msgid "Password"
msgstr ""
#: ../qml/pages/settings/git/ImportGitClone.qml:54
msgid ""
"Importing a git repo will delete<br>any existing password store!"
"<br>Continue ?"
msgstr ""
#: ../qml/pages/settings/git/ImportGitClone.qml:68
msgid "An error occured during git clone !"
msgstr ""
#: ../qml/pages/settings/git/ImportGitClone.qml:89
msgid "Git Clone Import"
msgstr ""

View File

@ -1,50 +1,52 @@
import Lomiri.Components 1.3
import Lomiri.Components.Popups 1.3
import Pass 1.0
import QtQuick 2.4 import QtQuick 2.4
import QtQuick.Layouts 1.1 import QtQuick.Layouts 1.1
import Ubuntu.Components 1.3
import Ubuntu.Components.Popups 1.3
import Pass 1.0
import "dialogs" import "dialogs"
MainView { MainView {
//theme.name: "Lomiri.Components.Themes.SuruDark"
id: root id: root
objectName: "mainView"
applicationName: "utpass.qrouland"
automaticOrientation: false
width: units.gu(48)
height: units.gu(80)
signal responsePassphraseDialog(bool canceled, string passphrase)
function initPass(rootView) { function initPass(rootView) {
Pass.init(rootView) Pass.initialize(rootView);
pageStack.push(Qt.resolvedUrl("pages/PasswordList.qml"));
} }
function callPassphraseDialog(useridHint, description, previousWasBad) { function callPassphraseDialog(useridHint, description, previousWasBad) {
//TODO use parameters to impove passphrase dialog //TODO use parameters to impove passphrase dialog
var passphraseDialog = PopupUtils.open( var pop = PopupUtils.open(passphraseDialog);
Qt.resolvedUrl("dialogs/PassphraseDialog.qml")) pop.activateFocus();
passphraseDialog.activateFocus()
var validated = function (passphrase) {
responsePassphraseDialog(false, passphrase)
}
var canceled = function () {
responsePassphraseDialog(true, "")
}
passphraseDialog.validated.connect(validated)
passphraseDialog.canceled.connect(canceled)
} }
objectName: "mainView"
applicationName: "utpass.qrouland"
automaticOrientation: true
width: units.gu(45)
height: units.gu(75)
PageStack { PageStack {
id: pageStack id: pageStack
anchors.fill: parent
Component.onCompleted: { anchors.fill: parent
pageStack.push(Qt.resolvedUrl("pages/PasswordList.qml"))
}
} }
Component {
id: passphraseDialog
PassphraseDialog {
onValidated: {
console.info("valided");
Pass.responsePassphraseDialog(false, passphrase);
}
onCanceled: {
console.info("canceled");
Pass.responsePassphraseDialog(true, "");
}
}
}
} }

View File

@ -1,18 +1,18 @@
import QtQuick 2.4
import Ubuntu.Components 1.3
import "../styles" import "../styles"
import Lomiri.Components 1.3
import QtQuick 2.4
Item { Item {
id: copyText id: copyText
property string text
property string text
property bool commonBorder: true property bool commonBorder: true
property int lBorderwidth: 0 property int lBorderwidth: 0
property int rBorderwidth: 0 property int rBorderwidth: 0
property int tBorderwidth: 0 property int tBorderwidth: 0
property int bBorderwidth: 0 property int bBorderwidth: 0
property int commonBorderWidth: 0 property int commonBorderWidth: 0
property string borderColor: UbuntuColors.warmGrey property string borderColor: LomiriColors.warmGrey
width: parent.width width: parent.width
height: units.gu(6) height: units.gu(6)
@ -33,26 +33,27 @@ Item {
anchors.rightMargin: units.gu(2) anchors.rightMargin: units.gu(2)
height: units.gu(4) height: units.gu(4)
name: "edit-copy" name: "edit-copy"
color: UbuntuColors.orange color: LomiriColors.orange
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onPressed: { onPressed: {
parent.color = UbuntuColors.warmGrey parent.color = LomiriColors.warmGrey;
} }
onClicked: { onClicked: {
var mimeData = Clipboard.newData() var mimeData = Clipboard.newData();
mimeData.text = copyText.text mimeData.text = copyText.text;
Clipboard.push(mimeData) Clipboard.push(mimeData);
} }
onReleased: { onReleased: {
parent.color = theme.palette.normal.background parent.color = theme.palette.normal.background;
} }
} }
CustomBorder { CustomBorder {
id: cb id: cb
commonBorder: copyText.commonBorder commonBorder: copyText.commonBorder
lBorderwidth: copyText.lBorderwidth lBorderwidth: copyText.lBorderwidth
rBorderwidth: copyText.rBorderwidth rBorderwidth: copyText.rBorderwidth
@ -60,5 +61,7 @@ Item {
bBorderwidth: copyText.bBorderwidth bBorderwidth: copyText.bBorderwidth
borderColor: copyText.borderColor borderColor: copyText.borderColor
} }
} }
} }

View File

@ -1,32 +1,33 @@
import QtQuick 2.4
import Ubuntu.Components 1.3
import "../styles" import "../styles"
import Lomiri.Components 1.3
import QtQuick 2.4
Item { Item {
id: externalLink id: externalLink
property string url property string url
property string text property string text
property bool commonBorder: true property bool commonBorder: true
property int lBorderwidth: 0 property int lBorderwidth: 0
property int rBorderwidth: 0 property int rBorderwidth: 0
property int tBorderwidth: 0 property int tBorderwidth: 0
property int bBorderwidth: 0 property int bBorderwidth: 0
property int commonBorderWidth: 0 property int commonBorderWidth: 0
property string borderColor: UbuntuColors.warmGrey property string borderColor: LomiriColors.warmGrey
width: parent.width width: parent.width
height: units.gu(6) height: units.gu(6)
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: theme.palette.normal.background
Text { Text {
text: externalLink.text text: externalLink.text
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: units.gu(2) anchors.leftMargin: units.gu(2)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: theme.palette.normal.backgroundText
} }
Icon { Icon {
@ -36,18 +37,19 @@ Item {
width: units.gu(4) width: units.gu(4)
height: units.gu(4) height: units.gu(4)
name: "go-next" name: "go-next"
color: UbuntuColors.orange color: LomiriColors.orange
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
Qt.openUrlExternally(externalLink.url) Qt.openUrlExternally(externalLink.url);
} }
} }
CustomBorder { CustomBorder {
id: cb id: cb
commonBorder: externalLink.commonBorder commonBorder: externalLink.commonBorder
lBorderwidth: externalLink.lBorderwidth lBorderwidth: externalLink.lBorderwidth
rBorderwidth: externalLink.rBorderwidth rBorderwidth: externalLink.rBorderwidth
@ -55,5 +57,7 @@ Item {
bBorderwidth: externalLink.bBorderwidth bBorderwidth: externalLink.bBorderwidth
borderColor: externalLink.borderColor borderColor: externalLink.borderColor
} }
} }
} }

View File

@ -1,24 +1,24 @@
import QtQuick 2.4
import Ubuntu.Components 1.3
import Ubuntu.Components.Popups 1.3
import Pass 1.0
import "../styles"
import "../dialogs" import "../dialogs"
import "../styles"
import Lomiri.Components 1.3
import Lomiri.Components.Popups 1.3
import Lomiri.Components.Themes 1.3
import Pass 1.0
import QtQuick 2.4
Component { Component {
Rectangle { Rectangle {
id: fileDir
property string activePasswordName
anchors.right: parent.right anchors.right: parent.right
anchors.left: parent.left anchors.left: parent.left
height: units.gu(5) height: units.gu(5)
color: theme.palette.normal.background
Text { Text {
text: fileBaseName text: fileBaseName
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: units.gu(2) anchors.leftMargin: units.gu(2)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: theme.palette.normal.backgroundText
} }
Icon { Icon {
@ -27,31 +27,20 @@ Component {
anchors.rightMargin: units.gu(2) anchors.rightMargin: units.gu(2)
height: units.gu(4) height: units.gu(4)
name: fileIsDir ? "go-next" : "lock" name: fileIsDir ? "go-next" : "lock"
color: UbuntuColors.orange color: LomiriColors.orange
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
var path = folderModel.folder + "/" + fileName;
if (fileIsDir) { if (fileIsDir) {
folderModel.folder = folderModel.folder + "/" + fileName folderModel.folder = path;
backAction.visible = true backAction.visible = true;
passwordListHeader.title = fileName;
} else { } else {
fileDir.activePasswordName = fileBaseName console.debug("pass show %1".arg(path));
Pass.show(path);
Pass.onDecrypted.connect(function (text) {
pageStack.push(Qt.resolvedUrl("../pages/Password.qml"),
{
"plainText": text,
"title": fileDir.activePasswordName
})
})
Pass.onDecryptFailed.connect(function () {
PopupUtils.open(passwordPageDecryptError)
})
Pass.decrypt(folderModel.folder + "/" + fileName)
} }
} }
} }
@ -62,17 +51,9 @@ Component {
rBorderwidth: 0 rBorderwidth: 0
tBorderwidth: 0 tBorderwidth: 0
bBorderwidth: 1 bBorderwidth: 1
borderColor: UbuntuColors.warmGrey borderColor: LomiriColors.warmGrey
} }
Component {
id: passwordPageDecryptError
ErrorDialog {
textError: i18n.tr("Decryption failed !")
onDialogClosed: {
pageStack.pop()
}
}
}
} }
} }

View File

@ -1,35 +1,35 @@
import QtQuick 2.4
import Ubuntu.Components 1.3
import "../styles" import "../styles"
import Lomiri.Components 1.3
import QtQuick 2.4
Item { Item {
id: pageStackLink id: pageStackLink
property string page property string page
property var params: { property var params: {
} }
property string text property string text
property bool commonBorder: true property bool commonBorder: true
property int lBorderwidth: 0 property int lBorderwidth: 0
property int rBorderwidth: 0 property int rBorderwidth: 0
property int tBorderwidth: 0 property int tBorderwidth: 0
property int bBorderwidth: 0 property int bBorderwidth: 0
property int commonBorderWidth: 0 property int commonBorderWidth: 0
property string borderColor: UbuntuColors.warmGrey property string borderColor: LomiriColors.warmGrey
width: parent.width width: parent.width
height: units.gu(6) height: units.gu(6)
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: theme.palette.normal.background
Text { Text {
text: pageStackLink.text text: pageStackLink.text
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: units.gu(2) anchors.leftMargin: units.gu(2)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: theme.palette.normal.backgroundText
} }
Icon { Icon {
@ -39,18 +39,19 @@ Item {
width: units.gu(4) width: units.gu(4)
height: units.gu(4) height: units.gu(4)
name: "go-next" name: "go-next"
color: UbuntuColors.orange color: LomiriColors.orange
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
pageStack.push(page, params) pageStack.push(page, params);
} }
} }
CustomBorder { CustomBorder {
id: cb id: cb
commonBorder: pageStackLink.commonBorder commonBorder: pageStackLink.commonBorder
lBorderwidth: pageStackLink.lBorderwidth lBorderwidth: pageStackLink.lBorderwidth
rBorderwidth: pageStackLink.rBorderwidth rBorderwidth: pageStackLink.rBorderwidth
@ -58,5 +59,7 @@ Item {
bBorderwidth: pageStackLink.bBorderwidth bBorderwidth: pageStackLink.bBorderwidth
borderColor: pageStackLink.borderColor borderColor: pageStackLink.borderColor
} }
} }
} }

View File

@ -1,52 +0,0 @@
import QtQuick 2.4
import Ubuntu.Components 1.3
import Ubuntu.Components.Popups 1.3
Dialog {
id: doubleValidationDialog
property int nb_validation: 0
property string text1
property string text2
signal doubleValidated
signal canceled
Text {
visible: nb_validation == 0
horizontalAlignment: Text.AlignHCenter
text: text1
}
Text {
visible: nb_validation == 1
horizontalAlignment: Text.AlignHCenter
text: text2
}
Button {
text: i18n.tr("Ok")
color: UbuntuColors.green
onClicked: {
if (nb_validation == 1) {
nb_validation = 0
doubleValidated()
PopupUtils.close(doubleValidationDialog)
} else {
nb_validation += 1
}
}
}
Button {
id: cancelButton
text: i18n.tr("Cancel")
color: UbuntuColors.red
onClicked: {
nb_validation = 0
canceled()
PopupUtils.close(doubleValidationDialog)
}
}
}

View File

@ -1,22 +1,23 @@
import Lomiri.Components 1.3
import Lomiri.Components.Popups 1.3
import QtQuick 2.4 import QtQuick 2.4
import Ubuntu.Components 1.3
import Ubuntu.Components.Popups 1.3
Dialog { Dialog {
id: dialogSuccess id: dialog
property string textError property string textError
signal dialogClosed signal dialogClosed()
title: i18n.tr("Error !") title: i18n.tr("Error !")
text: textError text: textError
Button { Button {
text: i18n.tr("OK") text: i18n.tr("Close")
color: UbuntuColors.red onClicked: function() {
onClicked: function () { dialogClosed();
dialogClosed() PopupUtils.close(dialog);
PopupUtils.close(dialogSuccess)
} }
} }
} }

View File

@ -1,25 +1,25 @@
import Lomiri.Components 1.3
import Lomiri.Components.Popups 1.3
import QtQuick 2.4 import QtQuick 2.4
import Ubuntu.Components 1.3
import Ubuntu.Components.Popups 1.3
Dialog { Dialog {
id: passphraseProvider id: dialog
title: i18n.tr("Authentication required")
text: i18n.tr("Enter passphrase:")
signal validated(string passphrase) signal validated(string passphrase)
signal canceled signal canceled()
function activateFocus() { function activateFocus() {
passphraseField.forceActiveFocus() passphraseField.forceActiveFocus();
} }
title: i18n.tr("Authentication required")
text: i18n.tr("Enter passphrase:")
TextField { TextField {
id: passphraseField id: passphraseField
placeholderText: i18n.tr("passphrase") placeholderText: i18n.tr("passphrase")
echoMode: TextInput.Password echoMode: TextInput.Password
onAccepted: okButton.clicked(text) onAccepted: okButton.clicked(text)
} }
@ -27,24 +27,22 @@ Dialog {
id: okButton id: okButton
text: i18n.tr("Ok") text: i18n.tr("Ok")
color: UbuntuColors.green color: theme.palette.normal.positive
onClicked: { onClicked: {
validated(passphraseField.text) validated(passphraseField.text);
passphraseField.text = "" passphraseField.text = "";
PopupUtils.close(passphraseProvider) PopupUtils.close(dialog);
} }
} }
Button { Button {
id: cancelButton id: cancelButton
text: i18n.tr("Cancel") text: i18n.tr("Cancel")
color: UbuntuColors.red
onClicked: { onClicked: {
canceled() canceled();
PopupUtils.close(passphraseProvider) PopupUtils.close(dialog);
} }
} }
} }

View File

@ -1,37 +1,41 @@
import Lomiri.Components 1.3
import Lomiri.Components.Popups 1.3
import QtQuick 2.4 import QtQuick 2.4
import Ubuntu.Components 1.3
import Ubuntu.Components.Popups 1.3
Dialog { Dialog {
id: doubleValidationDialog id: dialog
property string text property string text
property string continueText: i18n.tr("Ok")
property color continueColor: theme.palette.normal.positive
signal validated signal validated()
signal canceled signal canceled()
Text { Text {
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: doubleValidationDialog.text text: dialog.text
} }
Button { Button {
text: i18n.tr("Ok") id: continueButton
color: UbuntuColors.green
text: dialog.continueText
color: dialog.continueColor
onClicked: { onClicked: {
validated() validated();
PopupUtils.close(doubleValidationDialog) PopupUtils.close(dialog);
} }
} }
Button { Button {
id: cancelButton id: cancelButton
text: i18n.tr("Cancel") text: i18n.tr("Cancel")
color: UbuntuColors.red
onClicked: { onClicked: {
canceled() canceled();
PopupUtils.close(doubleValidationDialog) PopupUtils.close(dialog);
} }
} }
} }

View File

@ -1,22 +1,23 @@
import Lomiri.Components 1.3
import Lomiri.Components.Popups 1.3
import QtQuick 2.4 import QtQuick 2.4
import Ubuntu.Components 1.3
import Ubuntu.Components.Popups 1.3
Dialog { Dialog {
id: dialogSuccess id: dialog
property string textSuccess property string textSuccess
signal dialogClosed signal dialogClosed()
title: i18n.tr("Success !") title: i18n.tr("Success !")
text: textSuccess text: textSuccess
Button { Button {
text: i18n.tr("OK") text: i18n.tr("OK")
color: UbuntuColors.green onClicked: function() {
onClicked: function () { dialogClosed();
dialogClosed() PopupUtils.close(dialog);
PopupUtils.close(dialogSuccess)
} }
} }
} }

View File

@ -1,14 +1,20 @@
import QtQuick 2.4
import Ubuntu.Components 1.3
import "headers"
import "../components" import "../components"
import Lomiri.Components 1.3
import QtQuick 2.4
import Utils 1.0
import "headers"
Page { Page {
id: infoPage id: infoPage
header: StackHeader { Component.onCompleted: {
id: infoHeader var xhr = new XMLHttpRequest();
title: i18n.tr('Info') xhr.open("GET", Utils.manifestPath(), false);
xhr.send();
var mJson = JSON.parse(xhr.responseText);
manifestTitle.text = "<b>" + mJson.title + "</b>";
manifestVersion.text = mJson.version + "<br>" + mJson.framework + "@" + mJson.architecture;
manifestMaintener.text = mJson.maintainer;
} }
Flow { Flow {
@ -20,27 +26,33 @@ Page {
Rectangle { Rectangle {
width: parent.width width: parent.width
height: units.gu(1) height: units.gu(1)
color: theme.palette.normal.background
} }
Text { Text {
id: manifestTitle id: manifestTitle
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
width: parent.width width: parent.width
height: units.gu(8) height: units.gu(8)
fontSizeMode: Text.Fit fontSizeMode: Text.Fit
font.pixelSize: 144 font.pixelSize: 144
color: theme.palette.normal.backgroundText
} }
Rectangle { Rectangle {
width: parent.width width: parent.width
height: units.gu(12) height: units.gu(12)
color: theme.palette.normal.background
Image { Image {
source: "../../assets/logo.svg" source: "../../assets/logo.svg"
width: units.gu(12) width: units.gu(12)
height: units.gu(12) height: units.gu(12)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
} }
Text { Text {
@ -50,15 +62,18 @@ Page {
text: i18n.tr("<b>Version</b>") text: i18n.tr("<b>Version</b>")
fontSizeMode: Text.Fit fontSizeMode: Text.Fit
font.pixelSize: 72 font.pixelSize: 72
color: theme.palette.normal.backgroundText
} }
Text { Text {
id: manifestVersion id: manifestVersion
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
width: parent.width width: parent.width
height: units.gu(4) height: units.gu(4)
fontSizeMode: Text.Fit fontSizeMode: Text.Fit
font.pixelSize: 72 font.pixelSize: 72
color: theme.palette.normal.backgroundText
} }
Text { Text {
@ -68,16 +83,20 @@ Page {
text: i18n.tr("<b>Maintainer</>") text: i18n.tr("<b>Maintainer</>")
fontSizeMode: Text.Fit fontSizeMode: Text.Fit
font.pixelSize: 72 font.pixelSize: 72
color: theme.palette.normal.backgroundText
} }
Text { Text {
id: manifestMaintener id: manifestMaintener
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
width: parent.width width: parent.width
height: units.gu(2) height: units.gu(2)
fontSizeMode: Text.Fit fontSizeMode: Text.Fit
font.pixelSize: 72 font.pixelSize: 72
color: theme.palette.normal.backgroundText
} }
} }
Flow { Flow {
@ -85,32 +104,32 @@ Page {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
anchors.left: parent.left anchors.left: parent.left
ExternalLink { ExternalLink {
url: "https://taiga.rdrive.ovh/project/utpass/issues" url: "https://github.com/QRouland/UTPass/issues"
text: i18n.tr("Suggest improvement(s) or report a bug(s)") text: i18n.tr("Suggest improvement(s) or report a bug(s)")
} }
ExternalLink { ExternalLink {
url: "https://git.rdrive.ovh/QRouland/UTPass" url: "https://github.com/QRouland/UTPass"
text: i18n.tr("Access to the source code") text: i18n.tr("Access to the source code")
} }
Text { Text {
width: parent.width width: parent.width
height: units.gu(2) height: units.gu(2)
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
text: i18n.tr("Released under the terms of the GNU GPL v3") text: i18n.tr("Released under the terms of the GNU GPL v3")
color: theme.palette.normal.backgroundText
} }
} }
Component.onCompleted: { header: StackHeader {
var xhr = new XMLHttpRequest() id: infoHeader
xhr.open("GET", "../../manifest_.json", false)
xhr.send()
var mJson = JSON.parse(xhr.responseText) title: i18n.tr('Info')
manifestTitle.text = "<b>" + mJson.title + "</b>"
manifestVersion.text = mJson.version + "<br>" + mJson.framework + "@" + mJson.architecture
manifestMaintener.text = mJson.maintainer
} }
} }

View File

@ -1,6 +1,6 @@
import Lomiri.Components 1.3
import Lomiri.Components.Popups 1.3
import QtQuick 2.4 import QtQuick 2.4
import Ubuntu.Components 1.3
import Ubuntu.Components.Popups 1.3
import "headers" import "headers"
Page { Page {
@ -10,38 +10,15 @@ Page {
property string plainText property string plainText
property var objects property var objects
header: PageHeader { Component.onCompleted: {
id: passwordPageHeader var text_split = passwordPage.plainText.split('\n');
width: parent.width var component = Qt.createComponent("../components/CopyText.qml");
height: units.gu(6) for (var i = 0; i < text_split.length; i++) {
title: passwordPage.title if (text_split[i]) {
var object = component.createObject(container);
contents: Item { object.text = text_split[i];
height: parent.height
width: parent.width
Label {
id: labelTitle
text: passwordPage.title
anchors.verticalCenter: parent.verticalCenter
} }
} }
leadingActionBar.height: units.gu(4)
leadingActionBar.actions: [
Action {
id: backAction
iconName: "back"
text: "Back"
onTriggered: {
passwordPage.plainText = ""
for (var object in objects) {
object.text = ""
object.destroy()
}
pageStack.pop()
}
}
]
} }
Rectangle { Rectangle {
@ -52,18 +29,49 @@ Page {
Flow { Flow {
id: container id: container
anchors.fill: parent anchors.fill: parent
} }
} }
Component.onCompleted: { header: PageHeader {
var text_split = passwordPage.plainText.split('\n') id: passwordPageHeader
var component = Qt.createComponent("../components/CopyText.qml")
for (var i = 0; i < text_split.length; i++) { width: parent.width
if (text_split[i]) { height: units.gu(6)
var object = component.createObject(container) title: passwordPage.title
object.text = text_split[i] leadingActionBar.height: units.gu(4)
leadingActionBar.actions: [
Action {
id: backAction
iconName: "back"
text: "Back"
onTriggered: {
passwordPage.plainText = null;
for (var object in objects) {
object.text = null;
object.destroy();
}
pageStack.pop();
}
} }
]
contents: Item {
height: parent.height
width: parent.width
Label {
id: labelTitle
text: passwordPage.title
anchors.verticalCenter: parent.verticalCenter
}
} }
} }
} }

View File

@ -1,8 +1,10 @@
import QtQuick 2.4
import Ubuntu.Components 1.3
import Qt.labs.folderlistmodel 2.1
import Pass 1.0
import "../components" import "../components"
import "../dialogs"
import Lomiri.Components 1.3
import Lomiri.Components.Popups 1.3
import Pass 1.0
import Qt.labs.folderlistmodel 2.1
import QtQuick 2.4
import "headers" import "headers"
Page { Page {
@ -11,40 +13,56 @@ Page {
property string passwordStorePath property string passwordStorePath
anchors.fill: parent anchors.fill: parent
Component.onCompleted: {
header: MainHeader { passwordStorePath = "file:" + Pass.password_store;
id: passwordListHeader Pass.onShowSucceed.connect(function(filename, text) {
pageStack.push(Qt.resolvedUrl("../pages/Password.qml"), {
leadingActionBar.height: units.gu(4) "plainText": text,
leadingActionBar.actions: [ "title": filename
Action { });
id: backAction });
iconName: "back" Pass.onShowFailed.connect(function(message) {
text: i18n.tr("Back") PopupUtils.open(passwordPageDecryptError);
visible: false });
onTriggered: {
folderModel.folder = folderModel.parentFolder
if (folderModel.rootFolder === folderModel.folder) {
backAction.visible = false
}
}
}
]
} }
Rectangle { Column {
anchors.top: passwordListHeader.bottom anchors.top: passwordListHeader.bottom
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: units.gu(2)
anchors.rightMargin: units.gu(2)
visible: folderModel.count == 0 visible: folderModel.count == 0
Rectangle {
width: parent.width
height: units.gu(1)
}
Text { Text {
text: i18n.tr( text: i18n.tr("No password found")
"No password found<br>You can import a password store zip in the settings") width: parent.width
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
} }
Rectangle {
width: parent.width
height: units.gu(1)
}
Text {
text: i18n.tr("You can import a password store by cloning or")
width: parent.width
horizontalAlignment: Text.AlignHCenter
}
Text {
text: i18n.tr("importing a password store zip in the settings")
width: parent.width
horizontalAlignment: Text.AlignHCenter
}
} }
ListView { ListView {
@ -53,20 +71,55 @@ Page {
anchors.right: parent.right anchors.right: parent.right
anchors.left: parent.left anchors.left: parent.left
spacing: 1 spacing: 1
visible: folderModel.count != 0
model: FolderListModel { model: FolderListModel {
id: folderModel id: folderModel
nameFilters: ["*.gpg"] nameFilters: ["*.gpg"]
rootFolder: passwordStorePath rootFolder: passwordStorePath
folder: passwordStorePath folder: passwordStorePath
showDirs: true showDirs: true
} }
delegate: FileDir { delegate: FileDir {
id: fileDelegate id: fileDelegate
} }
} }
Component.onCompleted: { Component {
passwordStorePath = "file:" + Pass.getPasswordStore() id: passwordPageDecryptError
ErrorDialog {
textError: i18n.tr("Decryption failed !")
}
} }
header: MainHeader {
id: passwordListHeader
leadingActionBar.height: units.gu(4)
leadingActionBar.actions: [
Action {
id: backAction
iconName: "back"
text: i18n.tr("Back")
visible: false
onTriggered: {
folderModel.folder = folderModel.parentFolder;
console.debug(folderModel.folder);
if (folderModel.rootFolder === folderModel.folder) {
backAction.visible = false;
passwordListHeader.title = i18n.tr("UTPass");
} else {
passwordListHeader.title = folderModel.folder;
}
}
}
]
}
} }

View File

@ -1,40 +1,15 @@
import Lomiri.Components 1.3
import QtQuick 2.4 import QtQuick 2.4
import Ubuntu.Components 1.3
PageHeader { PageHeader {
id: mainHeader id: mainHeader
width: parent.width width: parent.width
height: units.gu(6) height: units.gu(6)
title: i18n.tr("UTPass") title: i18n.tr("UTPass")
contents: Item {
height: parent.height
width: parent.width
Label {
id: labelTitle
text: mainHeader.title
anchors.verticalCenter: parent.verticalCenter
visible: true
}
TextField {
id: searchBar
anchors.right: parent.right
anchors.left: parent.left
placeholderText: i18n.tr("Search")
height: units.gu(4)
visible: false
anchors.verticalCenter: parent.verticalCenter
onFocusChanged: {
}
}
}
trailingActionBar.height: units.gu(4) trailingActionBar.height: units.gu(4)
trailingActionBar.numberOfSlots: 2 trailingActionBar.numberOfSlots: 2
trailingActionBar.actions: [ trailingActionBar.actions: [
/*Action { TODO /*Action { TODO
iconName: "search" iconName: "search"
text: i18n.tr("Search") text: i18n.tr("Search")
@ -50,15 +25,43 @@ PageHeader {
iconName: "settings" iconName: "settings"
text: i18n.tr("Settings") text: i18n.tr("Settings")
onTriggered: { onTriggered: {
pageStack.push(Qt.resolvedUrl("../settings/Settings.qml")) pageStack.push(Qt.resolvedUrl("../settings/Settings.qml"));
} }
}, },
Action { Action {
iconName: "info" iconName: "info"
text: i18n.tr("Info") text: i18n.tr("Info")
onTriggered: { onTriggered: {
pageStack.push(Qt.resolvedUrl("../Info.qml")) pageStack.push(Qt.resolvedUrl("../Info.qml"));
} }
} }
] ]
contents: Item {
height: parent.height
width: parent.width
Label {
id: labelTitle
text: mainHeader.title
anchors.verticalCenter: parent.verticalCenter
visible: true
}
TextField {
id: searchBar
anchors.right: parent.right
anchors.left: parent.left
placeholderText: i18n.tr("Search")
height: units.gu(4)
visible: false
anchors.verticalCenter: parent.verticalCenter
onFocusChanged: {
}
}
}
} }

View File

@ -1,31 +1,36 @@
import Lomiri.Components 1.3
import QtQuick 2.4 import QtQuick 2.4
import Ubuntu.Components 1.3
PageHeader { PageHeader {
id: stackHeader id: stackHeader
width: parent.width width: parent.width
height: units.gu(6) height: units.gu(6)
title: i18n.tr("UTPass") title: i18n.tr("UTPass")
contents: Item {
height: parent.height
width: parent.width
Label {
id: labelTitle
text: stackHeader.title
anchors.verticalCenter: parent.verticalCenter
}
}
leadingActionBar.height: units.gu(4) leadingActionBar.height: units.gu(4)
leadingActionBar.actions: [ leadingActionBar.actions: [
Action { Action {
id: backAction id: backAction
iconName: "back" iconName: "back"
text: "Back" text: "Back"
onTriggered: { onTriggered: {
pageStack.pop() pageStack.pop();
} }
} }
] ]
contents: Item {
height: parent.height
width: parent.width
Label {
id: labelTitle
text: stackHeader.title
anchors.verticalCenter: parent.verticalCenter
}
}
} }

View File

@ -0,0 +1,92 @@
import "../../components"
import "../../dialogs"
import "../headers"
import Lomiri.Components 1.3
import Lomiri.Components.Popups 1.3
import Pass 1.0
import QtQuick 2.4
Page {
id: deleteRepoPage
Component.onCompleted: {
Pass.deletePasswordStoreSucceed.connect(function(keys_info) {
PopupUtils.open(deleteRepoPagePageDeleteSuccess);
});
Pass.deletePasswordStoreFailed.connect(function(message) {
PopupUtils.open(deleteRepoPagePageDeleteError);
});
}
Column {
id: deleteRepoPageListView
anchors.top: deleteRepoPageHeader.bottom
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.left: parent.left
anchors.leftMargin: units.gu(2)
anchors.rightMargin: units.gu(2)
spacing: units.gu(1)
Rectangle {
width: parent.width
height: units.gu(1)
}
Button {
id: buttonDeleteKey
width: parent.width
text: i18n.tr("Delete Password Store")
color: theme.palette.normal.negative
onClicked: {
PopupUtils.open(deleteRepoPagePageDeleteValidation, deleteRepoPage);
}
}
}
Component {
id: deleteRepoPagePageDeleteValidation
SimpleValidationDialog {
text: i18n.tr("You're are about to delete<br>the current Password Store.<br>Continue ?")
continueText: i18n.tr("Yes")
continueColor: theme.palette.normal.negative
onValidated: {
var status = Pass.deletePasswordStore();
}
}
}
Component {
id: deleteRepoPagePageDeleteError
ErrorDialog {
textError: i18n.tr("Password Store removal failed !")
}
}
Component {
id: deleteRepoPagePageDeleteSuccess
SuccessDialog {
textSuccess: i18n.tr("Password Store deleted !")
onDialogClosed: {
pageStack.pop();
pageStack.pop();
}
}
}
header: StackHeader {
id: deleteRepoPageHeader
title: i18n.tr('Info Keys')
}
}

View File

@ -1,82 +1,80 @@
import QtQuick 2.4
import Ubuntu.Components 1.3
import Ubuntu.Content 1.3
import Ubuntu.Components.Popups 1.3
import Pass 1.0
import Utils 1.0
import "../headers"
import "../../dialogs" import "../../dialogs"
import "../headers"
import Lomiri.Components 1.3
import Lomiri.Components.Popups 1.3
import Lomiri.Content 1.3
import Pass 1.0
import QtQuick 2.4
import Utils 1.0
Page { Page {
id: importKeyFilePage id: importKeyFilePage
property var activeTransfer property var activeTransfer
header: StackHeader {
id: importKeyHeader
title: i18n.tr("GPG Key Import")
}
ContentPeerPicker { ContentPeerPicker {
anchors.top: importKeyHeader.bottom anchors.top: importKeyHeader.bottom
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.topMargin: importKeyFilePage.header.height anchors.topMargin: importKeyFilePage.header.height
width: parent.width width: parent.width
visible: parent.visible visible: parent.visible
showTitle: false showTitle: false
contentType: ContentType.Text contentType: ContentType.Text
handler: ContentHandler.Source handler: ContentHandler.Source
onPeerSelected: { onPeerSelected: {
peer.selectionType = ContentTransfer.Single peer.selectionType = ContentTransfer.Single;
importKeyFilePage.activeTransfer = peer.request() importKeyFilePage.activeTransfer = peer.request();
importKeyFilePage.activeTransfer.stateChanged.connect(function() {
importKeyFilePage.activeTransfer.stateChanged.connect(function () {
if (importKeyFilePage.activeTransfer.state === ContentTransfer.Charged) { if (importKeyFilePage.activeTransfer.state === ContentTransfer.Charged) {
console.log("Charged") console.log("Charged");
console.log(importKeyFilePage.activeTransfer.items[0].url) console.log(importKeyFilePage.activeTransfer.items[0].url);
var status = Pass.importGPGKey(importKeyFilePage.activeTransfer.items[0].url);
var status = Pass.gpgImportKeyFromFile( Utils.rmFile(importKeyFilePage.activeTransfer.items[0].url);
importKeyFilePage.activeTransfer.items[0].url) if (status)
PopupUtils.open(dialogImportKeyPageSucess);
Utils.rmFile(importKeyFilePage.activeTransfer.items[0].url) else
if (status) { PopupUtils.open(dialogImportKeyPageError);
PopupUtils.open(dialogImportKeyPageSucess) importKeyFilePage.activeTransfer = null;
} else {
PopupUtils.open(dialogImportKeyPageError)
}
importKeyFilePage.activeTransfer = null
} }
}) });
} }
onCancelPressed: { onCancelPressed: {
pageStack.pop() pageStack.pop();
} }
} }
ContentTransferHint { ContentTransferHint {
id: transferHint id: transferHint
anchors.fill: parent anchors.fill: parent
activeTransfer: importKeyFilePage.activeTransfer activeTransfer: importKeyFilePage.activeTransfer
} }
Component { Component {
id: dialogImportKeyPageError id: dialogImportKeyPageError
ErrorDialog { ErrorDialog {
textError: i18n.tr("Key import failed !") textError: i18n.tr("Key import failed !")
} }
} }
Component { Component {
id: dialogImportKeyPageSucess id: dialogImportKeyPageSucess
SuccessDialog { SuccessDialog {
textSuccess: i18n.tr("Key successfully imported !") textSuccess: i18n.tr("Key successfully imported !")
onDialogClosed: { onDialogClosed: {
pageStack.pop() pageStack.pop();
} }
} }
} }
header: StackHeader {
id: importKeyHeader
title: i18n.tr("GPG Key Import")
}
} }

View File

@ -1,99 +1,99 @@
import QtQuick 2.4
import Ubuntu.Components 1.3
import Ubuntu.Content 1.3
import Ubuntu.Components.Popups 1.3
import Pass 1.0
import Utils 1.0
import "../headers"
import "../../dialogs" import "../../dialogs"
import "../headers"
import Lomiri.Components 1.3
import Lomiri.Components.Popups 1.3
import Lomiri.Content 1.3
import Pass 1.0
import QtQuick 2.4
import Utils 1.0
Page { Page {
id: importZipPage id: importZipPage
property var activeTransfer property var activeTransfer
header: StackHeader { Component.onCompleted: {
id: importZipHeader PopupUtils.open(importZipPageImportValidation, importZipPage);
title: i18n.tr("Zip Password Store Import")
} }
ContentPeerPicker { ContentPeerPicker {
anchors.top: importZipHeader.bottom anchors.top: importZipHeader.bottom
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.topMargin: importZipPage.header.height anchors.topMargin: importZipPage.header.height
width: parent.width width: parent.width
visible: parent.visible visible: parent.visible
showTitle: false showTitle: false
contentType: ContentType.Text contentType: ContentType.Text
handler: ContentHandler.Source handler: ContentHandler.Source
onPeerSelected: { onPeerSelected: {
peer.selectionType = ContentTransfer.Single peer.selectionType = ContentTransfer.Single;
importZipPage.activeTransfer = peer.request() importZipPage.activeTransfer = peer.request();
importZipPage.activeTransfer.stateChanged.connect(function() {
importZipPage.activeTransfer.stateChanged.connect(function () {
if (importZipPage.activeTransfer.state === ContentTransfer.Charged) { if (importZipPage.activeTransfer.state === ContentTransfer.Charged) {
console.log("Charged") console.log("Charged");
console.log(importZipPage.activeTransfer.items[0].url) console.log(importZipPage.activeTransfer.items[0].url);
var status = Utils.unzip(importZipPage.activeTransfer.items[0].url, Pass.getPasswordStore());
var status = Utils.unzip( Utils.rmFile(importZipPage.activeTransfer.items[0].url);
importZipPage.activeTransfer.items[0].url, if (status)
Pass.getPasswordStore()) PopupUtils.open(dialogImportZipPageSuccess);
else
Utils.rmFile(importZipPage.activeTransfer.items[0].url) PopupUtils.open(dialogImportZipPageError);
importZipPage.activeTransfer = null;
if (status) {
PopupUtils.open(dialogImportZipPageSuccess)
} else {
PopupUtils.open(dialogImportZipPageError)
}
importZipPage.activeTransfer = null
} }
}) });
} }
onCancelPressed: { onCancelPressed: {
pageStack.pop() pageStack.pop();
} }
} }
ContentTransferHint { ContentTransferHint {
id: transferHint id: transferHint
anchors.fill: parent anchors.fill: parent
activeTransfer: importZipPage.activeTransfer activeTransfer: importZipPage.activeTransfer
} }
Component { Component {
id: importZipPageImportValidation id: importZipPageImportValidation
SimpleValidationDialog { SimpleValidationDialog {
text: i18n.tr( text: i18n.tr("Importing a new zip will delete<br>any existing password store!<br>Continue ?")
"Importing a new zip will delete<br>any existing password store!<br>Continue ?") continueText: i18n.tr("Yes")
continueColor: theme.palette.normal.negative
onCanceled: { onCanceled: {
pageStack.pop() pageStack.pop();
} }
} }
} }
Component { Component {
id: dialogImportZipPageError id: dialogImportZipPageError
ErrorDialog { ErrorDialog {
textError: i18n.tr("Password store import failed !") textError: i18n.tr("Password store import failed !")
} }
} }
Component { Component {
id: dialogImportZipPageSuccess id: dialogImportZipPageSuccess
SuccessDialog { SuccessDialog {
textSuccess: i18n.tr("Password store sucessfully imported !") textSuccess: i18n.tr("Password store sucessfully imported !")
onDialogClosed: { onDialogClosed: {
pageStack.pop() pageStack.pop();
pageStack.pop();
} }
} }
} }
Component.onCompleted: { header: StackHeader {
PopupUtils.open(importZipPageImportValidation, importZipPage) id: importZipHeader
title: i18n.tr("Zip Password Store Import")
} }
} }

View File

@ -1,29 +1,41 @@
import QtQuick 2.4
import Ubuntu.Components 1.3
import Ubuntu.Components.Popups 1.3
import Pass 1.0
import "../headers"
import "../../components" import "../../components"
import "../../dialogs" import "../../dialogs"
import "../headers"
import Lomiri.Components 1.3
import Lomiri.Components.Popups 1.3
import Pass 1.0
import QtQuick 2.4
Page { Page {
id: infoKeysPage id: infoKeysPage
property string currentKey property QtObject currentKey
header: StackHeader { Component.onCompleted: {
id: infoKeysHeader Pass.getAllGPGKeysSucceed.connect(function(keys_info) {
title: i18n.tr('Info Keys') infoKeysListView.model = keys_info;
});
Pass.getAllGPGKeysFailed.connect(function(message) {
PopupUtils.open(infoKeysPageGetAllError);
});
Pass.deleteGPGKeySucceed.connect(function(keys_info) {
PopupUtils.open(infoKeysPageDeleteSuccess);
});
Pass.deleteGPGKeyFailed.connect(function(message) {
PopupUtils.open(infoKeysPageDeleteError);
});
Pass.getAllGPGKeys();
} }
ListView { ListView {
id: infoKeysListView id: infoKeysListView
anchors.top: infoKeysHeader.bottom anchors.top: infoKeysHeader.bottom
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: units.gu(2)
model: Pass.gpgGetAllKeysModel() anchors.rightMargin: units.gu(2)
delegate: Grid { delegate: Grid {
columns: 1 columns: 1
@ -34,67 +46,140 @@ Page {
Rectangle { Rectangle {
width: parent.width width: parent.width
height: units.gu(1) height: units.gu(1)
color: theme.palette.normal.background
} }
Text { Text {
id: uidKey
width: parent.width width: parent.width
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
text: i18n.tr('Key id : %1').arg(model.modelData.uid) text: i18n.tr('Key ID :')
color: theme.palette.normal.backgroundText
}
Text {
width: parent.width
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: model.modelData.uid
color: theme.palette.normal.backgroundText
}
Rectangle {
width: parent.width
height: units.gu(1)
color: theme.palette.normal.background
}
ListModel {
id: userIdsModel
Component.onCompleted: {
for (var i = 0; i < model.modelData.userIds.length; ++i) {
userIdsModel.append({
"model": model.modelData.userIds[i]
});
}
}
}
Text {
width: parent.width
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: i18n.tr('Users IDs : ')
color: theme.palette.normal.backgroundText
}
Repeater {
model: userIdsModel
Text {
width: parent.width
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: modelData.uid
color: theme.palette.normal.backgroundText
}
}
Rectangle {
width: parent.width
height: units.gu(1)
color: theme.palette.normal.background
} }
Button { Button {
id: buttonDeleteKey id: buttonDeleteKey
width: parent.width
text: i18n.tr("Delete this key") text: i18n.tr("Delete this key")
color: UbuntuColors.red color: theme.palette.normal.negative
onClicked: { onClicked: {
infoKeysPage.currentKey = model.modelData.uid infoKeysPage.currentKey = model.modelData;
PopupUtils.open(infoKeysPageDeleteValidation, infoKeysPage) PopupUtils.open(infoKeysPageDeleteValidation, infoKeysPage);
} }
} }
Rectangle { Rectangle {
width: parent.width width: parent.width
height: units.gu(1) height: units.gu(1)
color: theme.palette.normal.background
} }
} }
} }
Component { Component {
id: infoKeysPageDeleteValidation id: infoKeysPageDeleteValidation
DoubleValidationDialog {
text1: i18n.tr( SimpleValidationDialog {
"You're are about to delete<br>%1<br>Continue ?").arg( text: i18n.tr("You're are about to delete<br>%1.<br>Continue ?").arg(infoKeysPage.currentKey.uid)
infoKeysPage.currentKey) continueText: i18n.tr("Yes")
text2: i18n.tr( continueColor: theme.palette.normal.negative
"%1<br>will be definitively removed.<br>Continue ?").arg( onValidated: {
infoKeysPage.currentKey) var status = Pass.deleteGPGKey(infoKeysPage.currentKey);
onDoubleValidated: {
var status = Pass.gpgDeleteKeyId(infoKeysPage.currentKey)
if (status) {
PopupUtils.open(infoKeysPageDeleteSuccess)
} else {
PopupUtils.open(infoKeysPageDeleteError)
}
} }
} }
} }
Component { Component {
id: infoKeysPageDeleteError id: infoKeysPageDeleteError
ErrorDialog { ErrorDialog {
textError: i18n.tr("Key removal failed !") textError: i18n.tr("Key removal failed !")
} }
} }
Component { Component {
id: infoKeysPageDeleteSuccess id: infoKeysPageDeleteSuccess
SuccessDialog { SuccessDialog {
textSuccess: i18n.tr("Key successfully deleted !") textSuccess: i18n.tr("Key successfully deleted !")
onDialogClosed: { onDialogClosed: {
infoKeysListView.model = Pass.gpgGetAllKeysModel() infoKeysListView.model = Pass.getAllGPGKeys();
} }
} }
} }
Component {
id: infoKeysPageGetAllError
ErrorDialog {
textError: i18n.tr("An Error occured getting GPG keys !")
}
}
header: StackHeader {
id: infoKeysHeader
title: i18n.tr('Info Keys')
}
} }

View File

@ -1,19 +1,14 @@
import QtQuick 2.4
import Ubuntu.Components 1.3
import Pass 1.0
import "../headers"
import "../../components" import "../../components"
import "../headers"
import Lomiri.Components 1.3
import Pass 1.0
import QtQuick 2.4
Page { Page {
id: settingsPage id: settingsPage
property string gpgKeyId: "" property string gpgKeyId: ""
header: StackHeader {
id: settingsHeader
title: i18n.tr('Settings')
}
Flow { Flow {
anchors.top: settingsHeader.bottom anchors.top: settingsHeader.bottom
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
@ -26,34 +21,58 @@ Page {
width: parent.width width: parent.width
height: units.gu(4) height: units.gu(4)
text: i18n.tr('GPG') text: i18n.tr('GPG')
color: theme.palette.normal.backgroundText
} }
PageStackLink { PageStackLink {
page: Qt.resolvedUrl("ImportKeyFile.qml") page: Qt.resolvedUrl("ImportKeyFile.qml")
text: i18n.tr('Import a GPG key file') text: i18n.tr('Import a GPG key file')
} }
PageStackLink { PageStackLink {
page: Qt.resolvedUrl("InfoKeys.qml") page: Qt.resolvedUrl("InfoKeys.qml")
text: i18n.tr('Show GPG keys') text: i18n.tr('Show GPG keys')
} }
Text { Text {
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
width: parent.width width: parent.width
height: units.gu(4) height: units.gu(4)
text: i18n.tr('Password Store') text: i18n.tr('Password Store')
color: theme.palette.normal.backgroundText
} }
PageStackLink {
page: Qt.resolvedUrl("git/ImportGitClone.qml")
text: i18n.tr('Import a Password Store using Git')
}
PageStackLink { PageStackLink {
page: Qt.resolvedUrl("ImportZip.qml") page: Qt.resolvedUrl("ImportZip.qml")
text: i18n.tr('Import a Password Store Zip') text: i18n.tr('Import a Password Store Zip')
} }
PageStackLink {
page: Qt.resolvedUrl("DeleteRepo.qml")
text: i18n.tr('Delete Password Store')
}
Text { Text {
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
width: parent.width width: parent.width
height: units.gu(4) height: units.gu(4)
color: UbuntuColors.red color: LomiriColors.red
text: i18n.tr( text: i18n.tr('Warning: importing delete any exiting Password Store')
'Warning: importing delete any exiting Password Store')
} }
} }
header: StackHeader {
id: settingsHeader
title: i18n.tr('Settings')
}
} }

View File

@ -0,0 +1,38 @@
import Git 1.0
import Lomiri.Components 1.3
import Pass 1.0
import QtQuick 2.4
Column {
width: parent.width
spacing: units.gu(1)
Text {
id: repoUrlLabel
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
width: parent.width
text: i18n.tr('Repo Url')
}
TextField {
id: repoUrlInput
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
width: parent.width
}
Button {
id: buttonClone
width: parent.width
color: theme.palette.normal.positive
text: i18n.tr('Clone')
onClicked: {
Git.cloneHttp(repoUrlInput.text, Pass.password_store);
}
}
}

View File

@ -0,0 +1,56 @@
import Git 1.0
import Lomiri.Components 1.3
import Pass 1.0
import QtQuick 2.4
Column {
anchors.top: parent.fill
spacing: units.gu(1)
Text {
id: repoUrlLabel
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
width: parent.width
text: i18n.tr('Repo Url')
}
TextField {
id: repoUrlInput
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
width: parent.width
}
Text {
id: repoPasswordLabel
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
width: parent.width
text: i18n.tr('Password')
}
TextField {
id: repoPasswordInput
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
width: parent.width
echoMode: TextInput.Password
}
Button {
id: buttonClone
width: parent.width
color: theme.palette.normal.positive
text: i18n.tr('Clone')
onClicked: {
Git.cloneHttpPass(repoUrlInput.text, Pass.password_store, repoPasswordInput.text);
}
}
}

View File

@ -0,0 +1,38 @@
import Git 1.0
import Lomiri.Components 1.3
import Lomiri.Components.Pickers 1.3
import QtQuick 2.4
OptionSelector {
id: combo
width: parent.width
model: ["HTTP", "HTTP AUTH"]
onDelegateClicked: function(i) {
if (i === 0)
timer.setTimeout(function() {
importGitCloneForm.source = Qt.resolvedUrl("GitCloneHttp.qml");
}, 500);
else if (i === 1)
timer.setTimeout(function() {
importGitCloneForm.source = Qt.resolvedUrl("GitCloneHttpAuth.qml");
}, 500);
}
Timer {
id: timer
function setTimeout(cb, delayTime) {
timer.interval = delayTime;
timer.repeat = false;
timer.triggered.connect(cb);
timer.triggered.connect(function release() {
timer.triggered.disconnect(cb); // This is important
timer.triggered.disconnect(release); // This is important as well
});
timer.start();
}
}
}

View File

@ -0,0 +1,92 @@
import "../../../components"
import "../../../dialogs"
import "../../headers"
import Git 1.0
import Lomiri.Components 1.3
import Lomiri.Components.Popups 1.3
import Pass 1.0
import QtQuick 2.4
Page {
id: importGitClonePage
Component.onCompleted: {
Git.cloneSucceed.connect(function() {
PopupUtils.open(dialogGitCloneSuccess);
});
Git.cloneFailed.connect(function() {
PopupUtils.open(dialogGitCloneError);
});
PopupUtils.open(importGitCloneValidation, importGitClonePage);
importGitCloneForm.source = Qt.resolvedUrl("GitCloneHttp.qml");
}
Column {
anchors.top: importGitCloneHeader.bottom
anchors.right: parent.right
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.leftMargin: units.gu(2)
anchors.rightMargin: units.gu(2)
spacing: units.gu(1)
Rectangle {
width: parent.width
height: units.gu(1)
}
GitModeOptionSelector {
id: combo
}
Loader {
id: importGitCloneForm
width: parent.width
}
}
Component {
id: importGitCloneValidation
SimpleValidationDialog {
text: i18n.tr("Importing a git repo will delete<br>any existing password store!<br>Continue ?")
continueText: i18n.tr("Yes")
continueColor: theme.palette.normal.negative
onCanceled: {
pageStack.pop();
}
}
}
Component {
id: dialogGitCloneError
ErrorDialog {
textError: i18n.tr("An error occured during git clone !")
}
}
Component {
id: dialogGitCloneSuccess
SuccessDialog {
textSuccess: i18n.tr("Password store sucessfully imported !")
onDialogClosed: {
pageStack.pop();
pageStack.pop();
}
}
}
header: StackHeader {
id: importGitCloneHeader
title: i18n.tr('Git Clone Import')
}
}

View File

@ -4,18 +4,14 @@ import QtQuick 2.4
Rectangle { Rectangle {
property bool commonBorder: true property bool commonBorder: true
property int lBorderwidth: 1 property int lBorderwidth: 1
property int rBorderwidth: 1 property int rBorderwidth: 1
property int tBorderwidth: 1 property int tBorderwidth: 1
property int bBorderwidth: 1 property int bBorderwidth: 1
property int commonBorderWidth: 1 property int commonBorderWidth: 1
z: -1
property string borderColor: "white" property string borderColor: "white"
z: -1
color: borderColor color: borderColor
anchors { anchors {
@ -23,10 +19,10 @@ Rectangle {
right: parent.right right: parent.right
top: parent.top top: parent.top
bottom: parent.bottom bottom: parent.bottom
topMargin: commonBorder ? -commonBorderWidth : -tBorderwidth topMargin: commonBorder ? -commonBorderWidth : -tBorderwidth
bottomMargin: commonBorder ? -commonBorderWidth : -bBorderwidth bottomMargin: commonBorder ? -commonBorderWidth : -bBorderwidth
leftMargin: commonBorder ? -commonBorderWidth : -lBorderwidth leftMargin: commonBorder ? -commonBorderWidth : -lBorderwidth
rightMargin: commonBorder ? -commonBorderWidth : -rBorderwidth rightMargin: commonBorder ? -commonBorderWidth : -rBorderwidth
} }
} }

BIN
tests/assets/archive.zip Normal file

Binary file not shown.

View File

@ -0,0 +1 @@
utpasspassphrase

Binary file not shown.

View File

@ -0,0 +1 @@
*.gpg diff=gpg

View File

@ -0,0 +1 @@
UTPass Test

View File

View File

@ -0,0 +1 @@
<EFBFBD>^<03><><EFBFBD><EFBFBD><16>8m@%f#<23><><EFBFBD>¹<EFBFBD><C2B9><1F>nW<6E>[q<><71><EFBFBD>X<12>&<26>>&MO`0<><30>@

View File

@ -0,0 +1 @@
add_subdirectory(TestsUtils)

View File

@ -0,0 +1,29 @@
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
set(PLUGIN "TestsUtils")
set(
SRC
plugin.cpp
utils.cpp
)
set(CMAKE_AUTOMOC ON)
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(QT_IMPORTS_DIR "/lib/${ARCH_TRIPLET}")
install(TARGETS ${PLUGIN} DESTINATION ${QT_IMPORTS_DIR}/${PLUGIN}/)
install(FILES qmldir DESTINATION ${QT_IMPORTS_DIR}/${PLUGIN}/)

View File

@ -0,0 +1,10 @@
#include <QtQml>
#include "plugin.h"
#include "utils.h"
void TestsUtilsPlugin::registerTypes(const char *uri)
{
//@uri TestUtils
qmlRegisterSingletonType<TestsUtilsPlugin>(uri, 1, 0, "TestUtils", [](QQmlEngine *, QJSEngine *) -> QObject * { return new TestsUtils; });
}

View File

@ -0,0 +1,16 @@
#ifndef TESTSUTILSPLUGIN_H
#define TESTSUTILSPLUGIN_H
#include <QQmlExtensionPlugin>
class TestsUtilsPlugin : public QQmlExtensionPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID
"org.qt-project.Qt.QQmlExtensionInterface")
public:
void registerTypes(const char *uri) override;
};
#endif

View File

@ -0,0 +1,2 @@
module TestUtils
plugin TestUtils

View File

@ -0,0 +1,38 @@
#include <QFile>
#include <QDir>
#include <QUrl>
#include <QUuid>
#include <QtCore/QStandardPaths>
#include <quazip5/JlCompress.h>
#include "utils.h"
QString TestsUtils::getTempPath()
{
qFatal("yp");
// Get the system's temporary directory
QString tempDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
qDebug() << "TempDir : " << tempDir;
// Generate a unique UUID
QString uuid = QUuid::createUuid().toString(QUuid::WithoutBraces);
// Create a new directory using the generated UUID
QString newTempDir = tempDir + "/" + uuid;
QDir dir;
if (!dir.exists(newTempDir)) {
// Create the directory
if (dir.mkpath(newTempDir)) {
return newTempDir; // Return the path if successful
} else {
return "Failed to create directory"; // Return an error message
}
} else {
return newTempDir; // If the directory already exists, return its path
}
}

View File

@ -0,0 +1,19 @@
#ifndef TESTSUTILS_H
#define TESTSUTILS_H
#include <QObject>
#include <QUrl>
#include <QQuickWindow>
class TestsUtils : public QObject
{
Q_OBJECT
public:
TestsUtils() = default;
~TestsUtils() override = default;
Q_INVOKABLE QString getTempPath();
};
#endif

11
tests/unit/tst_git.qml Normal file
View File

@ -0,0 +1,11 @@
import Git 1.0
import QtQuick 2.9
import QtTest 1.2
TestCase {
function test_git_clone() {
verify(Git.clone("", ""));
}
name: "git"
}

11
tests/unit/tst_pass.qml Normal file
View File

@ -0,0 +1,11 @@
import Git 1.0
import QtQuick 2.9
import QtTest 1.2
TestCase {
function test_git_clone() {
verify(Git.clone("", ""));
}
name: "git"
}

15
tests/unit/tst_utils.qml Normal file
View File

@ -0,0 +1,15 @@
import QtQuick 2.9
import QtTest 1.2
import TestUtils 1.0
import Utils 1.0
TestCase {
function test_unzip() {
var tempPath = TestUtils.getTempPath() + "/password-store";
var zipUrl = Qt.resolvedUrl("../assets/archive.zip");
var r = Utils.unzip(zipUrl, tempPath);
verify(r, "Unzip return an error %1".arg(zipUrl));
}
name: "utils"
}