2017-03-07 11:10:22 +03:00
|
|
|
import QtQuick 2.5
|
|
|
|
import io.thp.pyotherside 1.4
|
2017-01-27 15:55:38 +03:00
|
|
|
|
2017-03-17 16:06:34 +03:00
|
|
|
|
2017-01-27 15:55:38 +03:00
|
|
|
// @disable-check M300
|
|
|
|
Python {
|
|
|
|
id: py
|
|
|
|
|
|
|
|
property int nDevices
|
|
|
|
property bool hasDevice
|
|
|
|
property string name
|
2017-11-20 17:30:26 +03:00
|
|
|
property var version
|
2017-03-01 13:57:40 +03:00
|
|
|
property string oathId
|
2017-01-27 15:55:38 +03:00
|
|
|
property var connections: []
|
2017-11-07 12:34:42 +03:00
|
|
|
property var entries: []
|
2017-01-31 18:17:10 +03:00
|
|
|
property int nextRefresh: 0
|
2017-01-27 15:55:38 +03:00
|
|
|
property var enabled: []
|
2017-12-01 14:25:04 +03:00
|
|
|
property bool yubikeyReady: false
|
|
|
|
property bool loggingReady: false
|
|
|
|
readonly property bool ready: yubikeyReady && loggingReady
|
2017-01-27 15:55:38 +03:00
|
|
|
property var queue: []
|
2017-02-28 12:04:46 +03:00
|
|
|
property bool hasOTP: enabled.indexOf('OTP') !== -1
|
|
|
|
property bool hasCCID: enabled.indexOf('CCID') !== -1
|
2017-02-07 16:17:54 +03:00
|
|
|
property bool validated
|
2017-03-08 18:07:12 +03:00
|
|
|
property bool slot1inUse
|
|
|
|
property bool slot2inUse
|
2017-02-28 15:20:44 +03:00
|
|
|
property int expiration: 0
|
2017-02-08 18:47:28 +03:00
|
|
|
signal wrongPassword
|
2017-03-29 13:08:19 +03:00
|
|
|
signal credentialsRefreshed
|
2017-12-01 14:25:04 +03:00
|
|
|
signal enableLogging(string log_level)
|
2017-01-27 15:55:38 +03:00
|
|
|
|
|
|
|
Component.onCompleted: {
|
2017-01-30 16:59:58 +03:00
|
|
|
importModule('site', function () {
|
|
|
|
call('site.addsitedir', [appDir + '/pymodules'], function () {
|
2017-01-27 15:55:38 +03:00
|
|
|
addImportPath(urlPrefix + '/py')
|
2017-12-01 14:25:04 +03:00
|
|
|
|
2017-03-03 15:03:25 +03:00
|
|
|
importModule('yubikey', function () {
|
2017-12-01 14:25:04 +03:00
|
|
|
yubikeyReady = true
|
|
|
|
})
|
|
|
|
importModule('logging_setup', function() {
|
|
|
|
loggingReady = true
|
2017-01-27 15:55:38 +03:00
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-02-09 14:45:04 +03:00
|
|
|
onHasDeviceChanged: {
|
2017-02-16 16:14:14 +03:00
|
|
|
device.validated = false
|
2017-02-09 14:45:04 +03:00
|
|
|
}
|
|
|
|
|
2017-12-01 14:25:04 +03:00
|
|
|
onEnableLogging: {
|
|
|
|
do_call('logging_setup.setup', [log_level || 'DEBUG'])
|
|
|
|
}
|
|
|
|
|
|
|
|
onReadyChanged: {
|
|
|
|
if (ready) {
|
|
|
|
for (var i in queue) {
|
|
|
|
do_call(queue[i][0], queue[i][1], queue[i][2])
|
|
|
|
}
|
|
|
|
queue = []
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-27 15:55:38 +03:00
|
|
|
function do_call(func, args, cb) {
|
|
|
|
if (!ready) {
|
|
|
|
queue.push([func, args, cb])
|
|
|
|
} else {
|
2017-02-01 18:51:02 +03:00
|
|
|
call(func, args.map(JSON.stringify), function (json) {
|
2017-01-27 15:55:38 +03:00
|
|
|
if (cb) {
|
2017-11-07 12:34:42 +03:00
|
|
|
try {
|
|
|
|
cb(json ? JSON.parse(json) : undefined)
|
|
|
|
} catch(err) {
|
|
|
|
console.log(err, json)
|
|
|
|
}
|
2017-01-27 15:55:38 +03:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-07 12:34:42 +03:00
|
|
|
function refresh(slotMode, refreshCredentialsOnMode) {
|
2017-01-27 15:55:38 +03:00
|
|
|
do_call('yubikey.controller.count_devices', [], function (n) {
|
|
|
|
nDevices = n
|
|
|
|
if (nDevices == 1) {
|
2017-11-07 12:34:42 +03:00
|
|
|
do_call('yubikey.controller.refresh', [slotMode], function (dev) {
|
2017-01-27 15:55:38 +03:00
|
|
|
name = dev ? dev.name : ''
|
2017-11-20 17:30:26 +03:00
|
|
|
version = dev ? dev.version : null
|
2017-01-27 15:55:38 +03:00
|
|
|
enabled = dev ? dev.enabled : []
|
|
|
|
connections = dev ? dev.connections : []
|
2017-02-28 12:57:10 +03:00
|
|
|
hasDevice = dev !== undefined && dev !== null
|
2017-01-27 15:55:38 +03:00
|
|
|
})
|
|
|
|
} else if (hasDevice) {
|
2017-09-28 10:41:36 +03:00
|
|
|
// No longer has device
|
2017-01-27 15:55:38 +03:00
|
|
|
hasDevice = false
|
2017-11-07 12:34:42 +03:00
|
|
|
entries = null
|
2017-01-31 18:17:10 +03:00
|
|
|
nextRefresh = 0
|
2017-01-27 15:55:38 +03:00
|
|
|
}
|
2017-02-23 12:40:24 +03:00
|
|
|
refreshCredentialsOnMode()
|
2017-01-27 15:55:38 +03:00
|
|
|
})
|
2017-01-30 16:59:58 +03:00
|
|
|
}
|
2017-01-27 15:55:38 +03:00
|
|
|
|
2017-02-23 14:18:20 +03:00
|
|
|
function refreshCCIDCredentials(force) {
|
2017-02-23 12:40:24 +03:00
|
|
|
var now = Math.floor(Date.now() / 1000)
|
2017-04-06 05:55:36 +03:00
|
|
|
if (force || (validated && nextRefresh <= now)) {
|
2017-03-01 13:57:40 +03:00
|
|
|
do_call('yubikey.controller.refresh_credentials',
|
2017-11-07 12:34:42 +03:00
|
|
|
[now], updateAllCredentials)
|
2017-02-23 12:40:24 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-23 14:18:20 +03:00
|
|
|
function refreshSlotCredentials(slots, digits, force) {
|
2017-02-23 12:40:24 +03:00
|
|
|
var now = Math.floor(Date.now() / 1000)
|
2017-04-06 05:55:36 +03:00
|
|
|
if (force || (nextRefresh <= now)) {
|
2017-03-01 13:57:40 +03:00
|
|
|
do_call('yubikey.controller.refresh_slot_credentials',
|
2017-03-30 11:00:07 +03:00
|
|
|
[slots, digits, now], updateAllCredentials)
|
2017-02-23 12:40:24 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-07 12:34:42 +03:00
|
|
|
function validate(password, remember) {
|
|
|
|
do_call('yubikey.controller.provide_password', [password, remember],
|
|
|
|
function (res) {
|
|
|
|
if (res) {
|
|
|
|
validated = true
|
|
|
|
} else {
|
|
|
|
wrongPassword()
|
|
|
|
}
|
2017-03-01 13:57:40 +03:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-04-10 10:17:27 +03:00
|
|
|
function promptOrSkip(prompt) {
|
2017-11-07 12:34:42 +03:00
|
|
|
do_call('yubikey.controller.needs_validation', [], function (res) {
|
|
|
|
if (res === true) {
|
|
|
|
prompt.open()
|
|
|
|
} else {
|
|
|
|
validated = true
|
2017-03-01 13:57:40 +03:00
|
|
|
}
|
2017-02-28 17:07:43 +03:00
|
|
|
})
|
2017-02-01 18:27:45 +03:00
|
|
|
}
|
|
|
|
|
2017-11-07 12:34:42 +03:00
|
|
|
function setPassword(password, remember) {
|
|
|
|
do_call('yubikey.controller.set_password', [password, remember],
|
2017-02-16 16:14:14 +03:00
|
|
|
function () {
|
2017-11-07 12:34:42 +03:00
|
|
|
validated = true
|
2017-02-16 16:14:14 +03:00
|
|
|
})
|
2017-02-10 16:01:49 +03:00
|
|
|
}
|
|
|
|
|
2017-11-07 12:34:42 +03:00
|
|
|
function updateAllCredentials(newEntries) {
|
2017-01-31 11:57:44 +03:00
|
|
|
var result = []
|
2017-11-07 12:34:42 +03:00
|
|
|
var minExpiration = (Date.now() / 1000) + 60
|
|
|
|
for (var i = 0; i < newEntries.length; i++) {
|
|
|
|
var entry = newEntries[i]
|
2017-02-03 15:58:22 +03:00
|
|
|
// Update min expiration
|
2017-11-07 12:34:42 +03:00
|
|
|
if (entry.code && entry.code.valid_to < minExpiration
|
|
|
|
&& entry.credential.period === 30) {
|
|
|
|
minExpiration = entry.code.valid_to
|
2017-01-31 18:17:10 +03:00
|
|
|
}
|
2017-02-03 15:58:22 +03:00
|
|
|
// Touch credentials should only be replaced by user
|
2017-11-07 12:34:42 +03:00
|
|
|
if (credentialExists(entry.credential.key) && entry.credential.touch) {
|
|
|
|
result.push(getEntry(entry.credential.key))
|
2017-02-03 15:58:22 +03:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
// HOTP credentials should only be replaced by user
|
2017-11-07 12:34:42 +03:00
|
|
|
if (credentialExists(entry.credential.key) && entry.credential.oath_type === 'HOTP') {
|
|
|
|
result.push(getEntry(entry.credential.key))
|
2017-02-03 15:58:22 +03:00
|
|
|
continue
|
|
|
|
}
|
2017-07-05 11:15:57 +03:00
|
|
|
// The selected credential should still be selected,
|
|
|
|
// with an updated code.
|
2017-11-29 16:45:53 +03:00
|
|
|
if (getSelected() != null) {
|
|
|
|
if (getSelected().credential.key === entry.credential.key) {
|
|
|
|
selectCredential(entry)
|
2017-07-05 11:15:57 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-03 15:58:22 +03:00
|
|
|
// TOTP credentials should be updated
|
2017-11-07 12:34:42 +03:00
|
|
|
result.push(entry)
|
2017-01-31 11:57:44 +03:00
|
|
|
}
|
2017-01-31 18:17:10 +03:00
|
|
|
nextRefresh = minExpiration
|
2017-03-08 15:58:42 +03:00
|
|
|
// Credentials is cleared so that
|
2017-04-25 11:16:13 +03:00
|
|
|
// the view will refresh even if objects are the same
|
2017-11-07 12:34:42 +03:00
|
|
|
entries = result
|
2017-11-29 00:20:20 +03:00
|
|
|
entries.sort(function (a, b) {
|
2017-11-29 15:18:47 +03:00
|
|
|
return getSortableName(a.credential).localeCompare(getSortableName(b.credential))
|
2017-11-29 00:20:20 +03:00
|
|
|
})
|
|
|
|
|
2017-02-28 15:20:44 +03:00
|
|
|
updateExpiration()
|
2017-03-29 13:08:19 +03:00
|
|
|
credentialsRefreshed()
|
2017-01-31 11:57:44 +03:00
|
|
|
}
|
|
|
|
|
2017-11-29 15:18:47 +03:00
|
|
|
function getSortableName(credential) {
|
|
|
|
return (credential.issuer || '') + (credential.name || '') + '/' + (credential.period || '')
|
|
|
|
}
|
|
|
|
|
2017-11-07 12:34:42 +03:00
|
|
|
function getEntry(key) {
|
|
|
|
for (var i = 0; i < entries.length; i++) {
|
|
|
|
if (entries[i].credential.key === key) {
|
|
|
|
return entries[i]
|
2017-02-03 15:58:22 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-07 12:34:42 +03:00
|
|
|
function credentialExists(key) {
|
|
|
|
if (entries != null) {
|
|
|
|
for (var i = 0; i < entries.length; i++) {
|
|
|
|
if (entries[i].credential.key === key) {
|
2017-02-03 15:58:22 +03:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2017-03-13 16:51:14 +03:00
|
|
|
function hasAnyCredentials() {
|
2017-11-07 12:34:42 +03:00
|
|
|
return entries != null && entries.length > 0
|
2017-03-13 16:51:14 +03:00
|
|
|
}
|
|
|
|
|
2017-02-28 15:20:44 +03:00
|
|
|
function updateExpiration() {
|
2017-03-01 13:57:40 +03:00
|
|
|
var maxExpiration = 0
|
2017-11-07 12:34:42 +03:00
|
|
|
if (entries !== null) {
|
|
|
|
for (var i = 0; i < entries.length; i++) {
|
|
|
|
if (entries[i].credential.period === 30) {
|
|
|
|
var exp = entries[i].code && entries[i].code.valid_to
|
2017-09-12 17:21:38 +03:00
|
|
|
if (exp !== null && exp > maxExpiration) {
|
|
|
|
maxExpiration = exp
|
|
|
|
}
|
2017-02-28 15:20:44 +03:00
|
|
|
}
|
|
|
|
}
|
2017-03-01 13:57:40 +03:00
|
|
|
expiration = maxExpiration
|
2017-02-28 15:20:44 +03:00
|
|
|
}
|
2017-03-01 13:57:40 +03:00
|
|
|
}
|
2017-02-28 15:20:44 +03:00
|
|
|
|
2017-11-07 12:34:42 +03:00
|
|
|
function calculate(entry, copyAfterUpdate) {
|
2017-02-07 16:17:54 +03:00
|
|
|
var now = Math.floor(Date.now() / 1000)
|
2017-11-21 19:25:55 +03:00
|
|
|
var margin = entry.credential.touch ? 10 : 0;
|
|
|
|
do_call('yubikey.controller.calculate', [entry.credential, now + margin],
|
2017-11-07 12:34:42 +03:00
|
|
|
function (code) {
|
|
|
|
updateSingleCredential(entry.credential, code, copyAfterUpdate)
|
2017-07-31 14:52:06 +03:00
|
|
|
})
|
2017-02-07 16:17:54 +03:00
|
|
|
}
|
|
|
|
|
2017-07-31 14:52:06 +03:00
|
|
|
function calculateSlotMode(slot, digits, copyAfterUpdate) {
|
2017-02-23 15:51:44 +03:00
|
|
|
var now = Math.floor(Date.now() / 1000)
|
2017-11-21 19:25:55 +03:00
|
|
|
var margin = entry.credential.touch ? 10 : 0;
|
|
|
|
do_call('yubikey.controller.calculate_slot_mode', [slot, digits, now + margin],
|
2017-11-07 12:34:42 +03:00
|
|
|
function (entry) {
|
|
|
|
updateSingleCredential(entry.credential, entry.code, copyAfterUpdate)
|
2017-07-31 14:52:06 +03:00
|
|
|
})
|
2017-02-23 15:51:44 +03:00
|
|
|
}
|
|
|
|
|
2017-04-25 11:16:58 +03:00
|
|
|
/**
|
|
|
|
Put a credential coming from the YubiKey in the
|
|
|
|
right position in the credential list.
|
|
|
|
*/
|
2017-11-07 12:34:42 +03:00
|
|
|
function updateSingleCredential(cred, code, copyAfterUpdate) {
|
|
|
|
var entry = null;
|
|
|
|
for (var i = 0; i < entries.length; i++) {
|
|
|
|
if (entries[i].credential.key === cred.key) {
|
|
|
|
entry = entries[i]
|
2017-11-20 17:30:26 +03:00
|
|
|
entry.code = code
|
2017-02-07 16:17:54 +03:00
|
|
|
}
|
|
|
|
}
|
2017-11-25 00:44:37 +03:00
|
|
|
if (!cred.touch) {
|
|
|
|
updateExpiration()
|
|
|
|
}
|
2017-03-30 10:48:59 +03:00
|
|
|
credentialsRefreshed()
|
2017-04-25 11:16:58 +03:00
|
|
|
// Update the selected credential
|
|
|
|
// after update, since the code now
|
|
|
|
// might be available.
|
2017-11-29 16:45:53 +03:00
|
|
|
selectCredential(entry)
|
2017-07-31 14:52:06 +03:00
|
|
|
if (copyAfterUpdate) {
|
|
|
|
copy()
|
|
|
|
}
|
2017-02-07 16:17:54 +03:00
|
|
|
}
|
|
|
|
|
2017-09-12 17:21:38 +03:00
|
|
|
function addCredential(name, key, issuer, oathType, algo, digits, period, touch, cb) {
|
2017-02-03 14:24:41 +03:00
|
|
|
do_call('yubikey.controller.add_credential',
|
2017-11-07 12:34:42 +03:00
|
|
|
[name, key, issuer, oathType, algo, digits, period, touch],
|
2017-02-16 16:14:14 +03:00
|
|
|
cb)
|
2017-02-03 14:24:41 +03:00
|
|
|
}
|
|
|
|
|
2017-02-27 16:13:54 +03:00
|
|
|
function addSlotCredential(slot, key, touch, cb) {
|
2017-03-01 13:57:40 +03:00
|
|
|
do_call('yubikey.controller.add_slot_credential',
|
|
|
|
[slot, key, touch], cb)
|
2017-02-27 16:13:54 +03:00
|
|
|
}
|
|
|
|
|
2017-02-03 18:23:28 +03:00
|
|
|
function deleteCredential(credential) {
|
2017-02-16 16:14:14 +03:00
|
|
|
do_call('yubikey.controller.delete_credential',
|
2017-11-07 12:34:42 +03:00
|
|
|
[credential])
|
2017-01-27 15:55:38 +03:00
|
|
|
}
|
2017-02-14 15:12:24 +03:00
|
|
|
|
2017-02-23 18:00:17 +03:00
|
|
|
function deleteSlotCredential(slot) {
|
2017-03-01 13:57:40 +03:00
|
|
|
do_call('yubikey.controller.delete_slot_credential', [slot])
|
2017-02-23 18:00:17 +03:00
|
|
|
}
|
|
|
|
|
2017-02-16 16:14:14 +03:00
|
|
|
function parseQr(screenShots, cb) {
|
2017-02-15 17:00:30 +03:00
|
|
|
do_call('yubikey.controller.parse_qr', [screenShots], cb)
|
2017-02-14 15:12:24 +03:00
|
|
|
}
|
2017-02-17 10:52:34 +03:00
|
|
|
|
|
|
|
function reset() {
|
|
|
|
do_call('yubikey.controller.reset', [])
|
|
|
|
}
|
2017-03-08 18:07:12 +03:00
|
|
|
|
|
|
|
function getSlotStatus(cb) {
|
|
|
|
do_call('yubikey.controller.slot_status', [], function (res) {
|
|
|
|
slot1inUse = res[0]
|
|
|
|
slot2inUse = res[1]
|
|
|
|
cb()
|
|
|
|
})
|
|
|
|
}
|
2017-01-27 15:55:38 +03:00
|
|
|
}
|