diff --git a/qml/pages/FirstPage.qml b/qml/pages/FirstPage.qml
index b63513e..85faee5 100644
--- a/qml/pages/FirstPage.qml
+++ b/qml/pages/FirstPage.qml
@@ -9,11 +9,12 @@ Page {
property var refresh_issued
property ListModel keysList: ListModel{}
property int editorStyle: TextEditor.UnderlineBackground
+ property bool allow_deletion: false
- property var keyName;
- property var secret;
- property string hashAlgo: 'SHA1';
- property string issuer: '';
+ property var keyName
+ property var secret
+ property string hashAlgo: 'SHA1'
+ property string issuer: ''
// The effective value will be restricted by ApplicationWindow.allowedOrientations
allowedOrientations: Orientation.All
@@ -21,27 +22,41 @@ Page {
onStatusChanged: {
if ((status == Component.Ready))
{
+ refresh_issued = true;
python.getKeys();
- refresh_issued = false;
}
}
// To enable PullDownMenu, place our content in a SilicaFlickable
- SilicaFlickable {
+ property bool deletingItems
+
+ SilicaListView {
+ id: listView
+
anchors.fill: parent
+ model: keysList
+
+ header: PageHeader {
+ title: qsTr("YUBIKEY OATH TOTP Keys")
+ }
+
+ /*
+ ViewPlaceholder {
+ enabled: (keysList.populated && keysList.count === 0)
+ text: "No Keys"
+ hintText: "YubiKey not connected or no OATH Keys?"
+ }
+ */
// PullDownMenu and PushUpMenu must be declared in SilicaFlickable, SilicaListView or SilicaGridView
PullDownMenu {
-// MenuItem {
-// text: qsTr("Show Page 2")
-// onClicked: {
-// console.log(new Date().getTime())
-// console.log(keys[0]['code']['valid_to']*1000);
-// console.log(keys[0]['code']['valid_to']*1000 - new Date().getTime());
-// console.log((keys[0]['code']['valid_to'] - keys[0]['code']['valid_from']));
-// console.log((keys[0]['code']['valid_to'] - new Date().getTime()/1000)/ (keys[0]['code']['valid_to'] - keys[0]['code']['valid_from']));
-// }
-// }
+ MenuItem {
+ text: qsTr("Toggel allow key deletion")
+ onClicked: {
+ if (allow_deletion === true) allow_deletion = false
+ else allow_deletion = true
+ }
+ }
MenuItem {
text: qsTr("Add Key")
onClicked: {
@@ -61,99 +76,99 @@ Page {
}
- // Tell SilicaFlickable the height of its content.
- contentHeight: column.height
+ delegate: ListItem {
+ function remove() {
+ remorseDelete(function() {
+ refresh_issued = true;
+ python.call('ykcon.ykcon.deleteKey', [model.cred['id']], function() {});
+ python.getKeys();
+ })
- // Place our content in a Column. The PageHeader is always placed at the top
- // of the page, followed by our content.
- Column {
- id: column
+ }
- width: page.width
- spacing: Theme.paddingLarge
- PageHeader {
- title: qsTr("YUBIKEY OATH TOTP Keys")
+ onClicked: {
+ if (!menuOpen && pageStack.depth == 2) {
+ pageStack.animatorPush(Qt.resolvedUrl("ListPage.qml"))
+ }
}
- ProgressBar {
- id: codeElapsed
+ ListView.onRemove: animateRemoval()
+ opacity: enabled ? 1.0 : 0.0
+ Behavior on opacity { FadeAnimator {}}
+
+ menu: Component {
+ ContextMenu {
+ MenuItem {
+ text: qsTr("To clipboard")
+ onClicked: Clipboard.text = model.code['value']
+ }
+ MenuItem {
+ enabled: allow_deletion
+ text: qsTr("Delete")
+ onClicked: remove()
+ }
+ }
+ }
+ Row {
+ width: parent.width
+ spacing: Theme.paddingMedium
+
anchors {
left: parent.left
right: parent.right
- margins: Theme.paddingSmall
+ margins: Theme.paddingLarge
}
- //width: Theme.iconSizeMedium
- //height: Theme.iconSizeMedium
- width: parent.width
- minimumValue: 0
- maximumValue: 100
- //valueText: value
- //label: "Progress"
- Timer {
- id: refresh_timer
- interval: 100
- repeat: true
- onTriggered: {
- try{
- codeElapsed.value = 100*((keys[0]['code']['valid_to'] - new Date().getTime()/1000)/ (keys[0]['code']['valid_to'] - keys[0]['code']['valid_from']));
- if (codeElapsed.value <= 0 && refresh_issued === false) {
- refresh_issued = true;
- python.getKeys();
- }
- } catch (e) {
- console.log('error?')
- codeElapsed = 0;
- python.getKeys();
- }
- }
- running: Qt.application.active
+
+ Label {
+ text: model.cred['id']
+ font.pixelSize: Theme.fontSizeSmall
+ wrapMode: Text.Wrap
+ width: parent.width * 3 / 4
+ }
+ Label {
+ text: model.code['value']
+ font.pixelSize: Theme.fontSizeSmall
+ font.bold: true
+ wrapMode: Text.Wrap
+ width: parent.width * 1 / 4
}
}
+ }
+ VerticalScrollDecorator {}
+
+ ProgressBar {
+ id: codeElapsed
+ anchors {
+ bottom: parent.bottom
+ left: parent.left
+ right: parent.right
+ margins: Theme.paddingSmall
+ }
- Repeater {
-
- model: keysList
-
- Row {
- width: parent.width
- spacing: Theme.paddingMedium
-
- anchors {
- left: parent.left
- right: parent.right
- margins: Theme.paddingLarge
- }
-
- Label {
- text: model.cred['id']
- color: Theme.secondaryHighlightColor
- font.pixelSize: Theme.fontSizeSmall
- wrapMode: Text.Wrap
- width: parent.width * 3 / 4
+ width: parent.width
+ minimumValue: 0
+ maximumValue: 100
+
+ Timer {
+ id: refresh_timer
+ interval: 100
+ repeat: true
+ onTriggered: {
+ try{
+ codeElapsed.value = 100*((keys[0]['code']['valid_to'] - new Date().getTime()/1000)/ (keys[0]['code']['valid_to'] - keys[0]['code']['valid_from']));
+ if (codeElapsed.value <= 0 && refresh_issued === false) {
+ refresh_issued = true;
+ python.getKeys();
}
- Label {
- text: model.code['value']
- color: Theme.secondaryHighlightColor
- font.pixelSize: Theme.fontSizeSmall
- font.bold: true
- wrapMode: Text.Wrap
- width: parent.width * 1 / 4
+ } catch (e) {
+ codeElapsed.value = 0;
+ python.getKeys();
}
}
+ running: Qt.application.active
}
-
-
-// Label {
-// id: label1
-// x: Theme.horizontalPageMargin
-// text: qsTr("Hello Sailors")
-// color: Theme.secondaryHighlightColor
-// font.pixelSize: Theme.fontSizeSmall
-// wrapMode: Text.Wrap
-// width: parent.width
-// }
-
}
+
}
Python {
@@ -162,9 +177,8 @@ Page {
addImportPath(Qt.resolvedUrl('.'));
setHandler('keys', function(val) {
- //label1.text = val
- keysList.clear();
keys = JSON.parse(val);
+ keysList.clear();
for (var s_key in keys) {
keysList.append({'cred': keys[s_key]['cred'], 'code': keys[s_key]['code']})
}
@@ -173,9 +187,19 @@ Page {
});
setHandler('no_key', function(val) {
- //label1.text = val
refresh_timer.stop()
+ });
+ setHandler('del:key_not_found', function(val) {
+ console.log("del:key_not_found")
+ });
+
+ setHandler('del:succ', function(val) {
+ console.log("del:succ")
+ });
+
+ setHandler('del:not_unique', function(val) {
+ console.log("del:not_unique")
});
importModule('ykcon', function () {});
@@ -199,107 +223,79 @@ Page {
}
Component {
- id: newKeyDialog
- Dialog {
-
- onAccepted: {
-
- keyName = nameField.text
- secret = secretField.text
- hashAlgo = cbxHashAlgo.currentItem.text
- issuer = issuerField.text
-
- refresh_issued = true;
- python.call('ykcon.ykcon.writeKey', [keyName, secret, hashAlgo, issuer], function() {});
- python.getKeys();
-
- // pageStack.replace(newKeyDialog_page2)
-
+ id: newKeyDialog
+ Dialog {
+
+ onAccepted: {
+ keyName = nameField.text
+ secret = secretField.text
+ hashAlgo = cbxHashAlgo.currentItem.text
+ issuer = issuerField.text
+
+ refresh_issued = true;
+ python.call('ykcon.ykcon.writeKey', [keyName, secret, hashAlgo, issuer], function() {});
+ python.getKeys();
+ }
+
+ Column {
+ id: column
+ width: parent.width
+
+ DialogHeader {
+ id: header
+ title: qsTr("Add OATH TOTP Key")
}
- Column {
- id: column
- width: parent.width
-
- DialogHeader {
- id: header
- title: "Add OATH TOTP Key"
- }
-
- SectionHeader {
- text: "Credential details"
- }
-
- TextField {
- id: nameField
- focus: true
- label: "Name"
- EnterKey.iconSource: "image://theme/icon-m-enter-next"
- EnterKey.onClicked: secretField.focus = true
-
- backgroundStyle: page.editorStyle
- }
+ SectionHeader {
+ text: qsTr("Credential details")
+ }
- TextField {
- id: secretField
- focus: true
- label: "Secret"
- EnterKey.iconSource: "image://theme/icon-m-enter-next"
- EnterKey.onClicked: issuerField.focus = true
+ TextField {
+ id: nameField
+ focus: true
+ label: qsTr("Name")
+ EnterKey.iconSource: "image://theme/icon-m-enter-next"
+ EnterKey.onClicked: secretField.focus = true
- backgroundStyle: page.editorStyle
- }
-
- SectionHeader {
- text: "Advanced (optional)"
- }
-
- TextField {
- id: issuerField
- focus: true
- label: "Issuer"
- EnterKey.iconSource: "image://theme/icon-m-enter-close"
- EnterKey.onClicked: focus = false
+ backgroundStyle: page.editorStyle
+ }
- backgroundStyle: page.editorStyle
- }
+ TextField {
+ id: secretField
+ focus: true
+ label: qsTr("Secret")
+ EnterKey.iconSource: "image://theme/icon-m-enter-next"
+ EnterKey.onClicked: issuerField.focus = true
- ComboBox {
- id: cbxHashAlgo
- label: "Hash Algorythm:"
- width: parent.width
+ backgroundStyle: page.editorStyle
+ }
- menu: ContextMenu {
- MenuItem { text: "SHA1" }
- MenuItem { text: "SHA256" }
- MenuItem { text: "SHA512" }
- }
- }
- }
- }
- }
+ SectionHeader {
+ text: qsTr("Advanced (optional)")
+ }
- /*
- Component {
- id: newKeyDialog_page2
- Dialog {
+ TextField {
+ id: issuerField
+ focus: true
+ label: qsTr("Issuer")
+ EnterKey.iconSource: "image://theme/icon-m-enter-close"
+ EnterKey.onClicked: focus = false
- onAccepted: {
- refresh_issued = true;
- python.call('ykcon.ykcon.writeKey', [keyName, secret, hashAlgo, issuer], function() {});
- python.getKeys();
+ backgroundStyle: page.editorStyle
}
- Column {
- id: column
+ ComboBox {
+ id: cbxHashAlgo
+ label: qsTr("Hash Algorythm:")
width: parent.width
- DialogHeader {
- id: header
- title: "Plug Yubikey now and continue."
+ menu: ContextMenu {
+ MenuItem { text: "SHA1" }
+ MenuItem { text: "SHA256" }
+ MenuItem { text: "SHA512" }
}
- }
+ }
}
}
- */
+ }
}
diff --git a/qml/pages/ykcon.py b/qml/pages/ykcon.py
index 7a74301..d4a882c 100644
--- a/qml/pages/ykcon.py
+++ b/qml/pages/ykcon.py
@@ -1,6 +1,7 @@
# This Python file uses the following encoding: utf-8
# Some of the code may be taken from https://github.com/Yubico/yubioath-desktop/blob/main/py/yubikey.py
+# and https://github.com/Yubico/yubikey-manager/blob/main/ykman/cli/oath.py
import pyotherside
import json
@@ -24,12 +25,14 @@ def __init__(self):
pass
def getKeys(self):
-
could_register = False
-
for device, info in list_all_devices():
- if info.version >= (5, 0, 0): # The info object provides details about the YubiKey
- connection, _, _ = connect_to_device(serial=info.serial, connection_types=[SmartCardConnection])
+ # The info object provides details about the YubiKey
+ if info.version >= (5, 0, 0):
+ connection, _, _ = connect_to_device(
+ serial=info.serial,
+ connection_types=[SmartCardConnection]
+ )
with connection:
could_register = True
mySession = OathSession(connection)
@@ -38,22 +41,32 @@ def getKeys(self):
for key in entries:
cred = key
code = entries[key]
- code_dct = {'cred':cred,
- 'code' :code
+ code_dct = {'cred': cred,
+ 'code': code
}
codes.append(code_dct)
if len(codes) > 0:
- pyotherside.send('keys', json.dumps(codes, default=dumper, indent=2))
+ pyotherside.send(
+ 'keys',
+ json.dumps(
+ codes,
+ default=dumper,
+ indent=2
+ )
+ )
else:
pyotherside.send('no_key')
if not could_register:
pyotherside.send('no_key')
def writeKey(self, name, secret, hash_algo, issuer='', digits=6):
-
- new_credentials = CredentialData(name, OATH_TYPE.TOTP, HASH_ALGORITHM.SHA1, parse_b32_key(secret))
-
+ new_credentials = CredentialData(
+ name,
+ OATH_TYPE.TOTP,
+ HASH_ALGORITHM.SHA1,
+ parse_b32_key(secret)
+ )
if '256' in hash_algo:
new_credentials.hash_algorithm = HASH_ALGORITHM.SHA256
if '512' in hash_algo:
@@ -62,19 +75,60 @@ def writeKey(self, name, secret, hash_algo, issuer='', digits=6):
new_credentials.digits = int(digits)
for device, info in list_all_devices():
- if info.version >= (5, 0, 0): # The info object provides details about the YubiKey
- connection, _, _ = connect_to_device(serial=info.serial, connection_types=[SmartCardConnection])
+ # The info object provides details about the YubiKey
+ if info.version >= (5, 0, 0):
+ connection, _, _ = connect_to_device(
+ serial=info.serial,
+ connection_types=[SmartCardConnection]
+ )
with connection:
mySession = OathSession(connection)
mySession.put_credential(credential_data=new_credentials)
+ def deleteKey(self, name):
+ for device, info in list_all_devices():
+ # The info object provides details about the YubiKey
+ if info.version >= (5, 0, 0):
+ connection, _, _ = connect_to_device(
+ serial=info.serial,
+ connection_types=[SmartCardConnection]
+ )
+ with connection:
+ could_register = True
+ mySession = OathSession(connection)
+ creds = mySession.list_credentials()
+ hits = _search(creds, name, True)
+ if len(hits) == 0:
+ pyotherside.send("del:key_not_found")
+ elif len(hits) == 1:
+ cred = hits[0]
+ mySession.delete_credential(cred.id)
+ pyotherside.send("del:succ")
+ else:
+ pyotherside.send("del:not_unique")
+
def dumper(obj):
"""JSON serialization of bytes"""
if isinstance(obj, bytes):
return obj.decode()
try:
return obj.toJSON()
- except: # pylint: disable=bare-except
+ except:
return obj.__dict__
+def _search(creds, query, show_hidden):
+ hits = []
+ for c in creds:
+ cred_id = _string_id(c)
+ # if not show_hidden and is_hidden(c):
+ # continue
+ if cred_id == query:
+ return [c]
+ if query.lower() in cred_id.lower():
+ hits.append(c)
+ return hits
+
+def _string_id(credential):
+ return credential.id.decode("utf-8")
+
ykcon = Ykcon()
diff --git a/rpm/harbour-yubigo.spec b/rpm/harbour-yubigo.spec
index 81cdbd9..a74b410 100644
--- a/rpm/harbour-yubigo.spec
+++ b/rpm/harbour-yubigo.spec
@@ -9,8 +9,8 @@ Name: harbour-yubigo
# << macros
Summary: ykman GUI for SFOS
-Version: 0.3
-Release: 3
+Version: 0.4
+Release: 1
Group: Qt/Qt
License: GPL3
URL: https://github.com/fridlmue/harbour-yubigo
diff --git a/rpm/harbour-yubigo.yaml b/rpm/harbour-yubigo.yaml
index 056c65f..912dd6a 100644
--- a/rpm/harbour-yubigo.yaml
+++ b/rpm/harbour-yubigo.yaml
@@ -1,7 +1,7 @@
Name: harbour-yubigo
Summary: ykman GUI for SFOS
-Version: 0.3
-Release: 3
+Version: 0.4
+Release: 1
# The contents of the Group field should be one of the groups listed here:
# https://github.com/mer-tools/spectacle/blob/master/data/GROUPS
Group: Qt/Qt
diff --git a/translations/harbour-yubigo-de.ts b/translations/harbour-yubigo-de.ts
index 387ce33..e02030e 100644
--- a/translations/harbour-yubigo-de.ts
+++ b/translations/harbour-yubigo-de.ts
@@ -10,6 +10,10 @@
FirstPage
+
+
+
+
@@ -19,7 +23,43 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/translations/harbour-yubigo.ts b/translations/harbour-yubigo.ts
index 387ce33..e02030e 100644
--- a/translations/harbour-yubigo.ts
+++ b/translations/harbour-yubigo.ts
@@ -10,6 +10,10 @@
FirstPage
+
+
+
+
@@ -19,7 +23,43 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+