* Ported from NodeJS to Python

* Migrated to libfprint-2
This commit is contained in:
2021-12-08 14:35:55 +01:00
commit 62ed8744f2
7 changed files with 475 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
{
}
+16
View File
@@ -0,0 +1,16 @@
# YOUreka IDID Fingerprint Service
This service uses the libfprint library (version 2 and newwer) to capture fingerprints for profiles in YOUreka ID Domains.
## Dependencies
## Debian Packages:
1. sudo apt install libfprint-2-dev
2. sudo apt install python3-pip
## PIP Packages
1. pip3 install python-socketio
2. pip3 install eventlet
# How to run
```python3 app.py```
BIN
View File
Binary file not shown.
+149
View File
@@ -0,0 +1,149 @@
import eventlet
import socketio
import uuid
import base64
import gi
gi.require_version("FPrint", "2.0")
gi.require_version("Gio", "2.0")
from gi.repository import FPrint, GLib, Gio
sio = socketio.Server()
static_files = {
'/assets': './public',
'/': {'filename': './public/index.html'}
}
app = socketio.WSGIApp(sio, static_files=static_files)
stopEnrollment = False
def progress(device, completed_stages, prnt, sessionId, error):
print({'completedStages': completed_stages, 'sessionId': sessionId})
global stopEnrollment
if (error):
stopEnrollment = True
print({'error': error})
else:
if (completed_stages == 5):
if (not stopEnrollment):
sio.emit("fprint-enroll-status", { 'stage': completed_stages }, room=sessionId)
else:
print('Completed with an error.')
def enroll(sessionId, data):
ctx = FPrint.Context()
devices = ctx.get_devices()
if (len(devices) == 0):
print("There is no attached device.")
else:
device = devices[0]
if (not device):
print("There is no attached device")
if (not device.is_open()):
device.open_sync()
while (not device.is_open()):
print('Opening...')
try:
prt = FPrint.Print.new(device)
prt = device.enroll_sync(prt, None, progress_cb=progress, progress_data=sessionId)
img = prt.serialize()
sio.emit('fprint-enroll-done', { 'ok': True, "fingerprint": base64.b64encode(img).decode('utf-8'), 'id': str(uuid.uuid4()), 'data': data })
with open('adebisi-fa.jpg', 'w+b') as f:
f.write(img)
except Exception as e:
print({'error': e});
sio.emit('fprint-enroll-done', { 'ok': False, 'message': e})
finally:
device.close_sync()
def open_dev(sessionId):
device = None;
ctx = FPrint.Context()
devices = ctx.get_devices()
if (len(devices) == 0):
print("There is no attached device.")
else:
device = devices[0]
if (not device):
print("There is no attached device")
if (not device.is_open()):
device.open_sync()
while (not device.is_open()):
print('Opening...')
return device
def verify(sessionId, data):
ctx = FPrint.Context()
devices = ctx.get_devices()
if (len(devices) == 0):
print("There is no attached device.")
else:
device = devices[0]
if (not device):
print("There is no attached device")
if (not device.is_open()):
device.open_sync()
while (not device.is_open()):
print('Opening...')
with open('adebisi-fa.jpg', 'r+b') as f:
img = f.read()
prtRecord = FPrint.Print.deserialize(img)
canceller = Gio.Cancellable.new()
(match, prtFound) = device.verify_sync(prtRecord, canceller, None)
if (match):
sio.emit('fprint-verify-done', { 'ok': True, 'data': data }, room=sessionId)
else:
sio.emit('fprint-verify-done', { 'ok': False }, room=sessionId)
device.close_sync()
def verify_short(sessionId, data):
d = open_dev(sessionId)
with open('adebisi-fa.jpg', 'r+b') as f:
img = f.read()
prtRecord = FPrint.Print.deserialize(img)
(match, print) = d.verify_sync(prtRecord, None, None)
if (match):
sio.emit('fprint-verify-done', { 'ok': True, 'data': data }, room=sessionId)
else:
sio.emit('fprint-verify-done', { 'ok': False }, room=sessionId)
d.close_sync()
# events
@sio.on("fprint-verify")
def fprint_verify(sid, data):
verify_short(sid, data)
pass
@sio.on("fprint-enroll")
def fprint_enroll(sid, data):
print('Enrolling {}'.format(data['finger']))
enroll(sid, data)
pass
@sio.event
def capture(sid, data):
print(data)
pass
@sio.event
def connect(sid, data):
# print({ 'sid': sid, 'event': 'connect', 'data': data});
pass
@sio.event
def disconnect(sid):
print({ 'sid': sid, 'event': 'disconnect'});
pass
@sio.on('capture-other')
def capture_other(sid, data):
print({ "event": "capture_other", "data": data })
pass
eventlet.wsgi.server(eventlet.listen(('127.0.0.1', 9000), reuse_port=False), app)
+131
View File
@@ -0,0 +1,131 @@
var app = new Vue({
el: '#app',
data: {
step: 0,
phone: '',
errorMessage: '',
otp: '',
busy: {
login: false
},
user: {
phone: '',
username: '',
roles: []
},
staff: {
reference: ''
},
fingerDetails: {
'right-thumb': { },
'right-index-finger': { }
},
socket: null
},
created: function () {
var userStr = localStorage.getItem("user");
if (userStr) {
this.user = JSON.parse(userStr);
this.step = 1;
}
this.socket = io();
this.socket.on("connect", () => { })
this.socket.on("fprint-enroll-done", (data) => {
console.log({ event: 'enroll-done', data});
if (data.ok) {
let { id, fingerprint, data: { finger }} = data;
this.step = 1.2;
this.fingerDetails[finger] = { id, fingerprint, finger }
}
this.busy.login = false;
})
this.socket.on("fprint-verify-done", (data) => {
console.log({ event: 'verify-done', data});
});
},
methods: {
authenticateUrl: function (api, authenticate = true) {
return `https://apis.a2loft.net/identity${api}` + (authenticate ? `?phone=${this.user.phone}&otp=${this.otp}` : '');
},
login: async function () {
this.busy.login = true;
var { data } = await axios.get(
this.authenticateUrl(`/validate/uc/${this.phone}/${this.otp}`, false)
);
this.errorMessage = '';
if (!data.ok)
this.errorMessage = "Invalid OTP specified.";
else {
localStorage.setItem("user", JSON.stringify(data.data));
this.user = data.data;
this.step = 1.0;
}
this.busy.login = false;
},
logout: function () {
localStorage.removeItem("user");
this.step = 0;
},
loadUserByReference: async function () {
this.busy.login = true;
try {
var { data } = await axios.get(
this.authenticateUrl(`/uc/${this.staff.reference}`)
);
if (!data.ok) {
swal("Oops!", `Operation failed. ${data.message}.` + this.staff.reference, "error");
} else {
this.staff = data.data;
// this.staff.verified = false;
this.step = 1.1;
}
} finally {
this.busy.login = false;
}
},
captureFingerPrint: async function (finger) {
this.busy.login = true;
this.step = 1.1;
// var { data: { success, message: { id, data, fingerprint } } } = await axios.post("http://localhost:9000/api/user", {
// name: this.staff.reference
// });
// this.fingerDetails = { id, fingerprint };
// var { data } = await axios.delete(`http://localhost:9000/api/user/${id}`);
// console.log(`${id} purged.`);
this.socket.emit("fprint-enroll", { finger });
},
saveFingerPrint: async function () {
this.busy.login = true;
try {
var fingerprints = Object.keys(this.fingerprints).map(finger => {
return {
finger,
fingerprint: this.fingerprints[finger]
}
});
var { data } = await axios.post(
this.authenticateUrl(`/uc/fingerprints/${this.staff.reference}`),
{ fingerprints }
);
if (data.ok) {
swal("Saved", `Fingerprint saved for ${this.staff.name} with reference ${this.staff.reference}.`, "success");
this.newEnrolment();
} else {
swal("Oops!", `Operation failed. ${data.message}.` + this.staff.reference, "error");
}
} finally {
this.busy.login = false;
}
},
newEnrolment: function () {
this.step = 1.0;
this.staff.reference = "";
}
}
})
+175
View File
@@ -0,0 +1,175 @@
<html>
<head>
<title>Fingerprint Service</title>
<script src="https://cdn.socket.io/4.4.0/socket.io.min.js" integrity="sha384-1fOn6VtTq3PWwfsOrk45LnYcGosJwzMHv+Xh/Jx5303FVOXzEnw0EpLv30mtjmlj" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<style>
.disabledbutton {
pointer-events: none;
opacity: 0.4;
}
</style>
</head>
<body style="font-size: 14px; font-family: 'Segoe UI';">
<div class="container" id="app">
<div class="col-md-12">
<div style="margin-top: 100px;">
</div>
<h1>
UNICAL Biometrics Enrolment Wizard
</h1>
<hr />
<div v-if="step > 0" style="text-align: right">
<span style="cursor: pointer;" v-on:click="newEnrolment">+ New
Enrolment</span>&nbsp;&nbsp;&bull;&nbsp;&nbsp;
<span>~{{user.username.substring(7)}}</span> &nbsp;&nbsp;&bull;&nbsp;&nbsp; <span v-on:click='logout'
style="cursor: pointer; font-weight: bold;">Logout</span>
<hr />
</div>
<div v-if="step == 0">
<div style="background-color: whitesmoke; margin-left: auto; margin-right: auto; padding: 10px;">
<form class="form" style="padding: 20px;">
<h3>Agent Login</h3>
<br />
<div class="form-group">
<label>Phone Number</label>
<input type="text" v-model="phone" class="form-control" />
</div>
<div class="form-group">
<label>One Time Password</label>
<input type="password" v-model="otp" class="form-control" />
</div>
<div class="form-group">
<div class="btn btn-success btn-block" v-bind:class="{ disabledbutton: busy.login || !phone || !otp }"
v-on:click="login">
<span v-if="!busy.login">Login</span>
<span v-else>Please wait...</span>
</div>
</div>
<div class="form-group" v-if="errorMessage">
<div class="alert alert-danger">{{errorMessage}}</div>
</div>
</form>
</div>
</div>
<div v-if="step >= 1">
<div v-if="step == 1.0">
<form class="form" style="padding: 20px;">
<h3>New Profile Enrollment</h3>
<br />
<div class="form-group">
<label>Reference Number</label>
<input type="text" v-model="staff.reference" class="form-control" />
</div>
<div class="form-group">
<label>OTP</label>
<input type="text" v-model="otp" class="form-control" />
</div>
<div class="form-group">
<div class="btn btn-success" v-bind:class="{ disabledbutton: busy.login || !staff.reference || !otp }"
v-on:click="loadUserByReference">
<span v-if="!busy.login">Get Details</span>
<span v-else>Please wait...</span>
</div>
</div>
</form>
</div>
<div v-if="step >= 1.1">
<form class="form form-horizontal" style="padding: 20px;">
<h3>Enroll Profile</h3>
<br />
<div class="form-group">
<label>Reference Number</label>
<input type="text" readonly v-model="staff.reference" class="form-control" />
</div>
<div class="form-group">
<label>Name</label>
<input type="text" readonly v-model="staff.name" class="form-control" />
</div>
<div class="form-group">
<label>Reference</label>
<input type="text" readonly v-model="staff.domainReference" class="form-control" />
</div>
<div class="form-group">
<label>Rank</label>
<input type="text" readonly v-model="staff.domainRankDescription" class="form-control" />
</div>
<div class="form-group">
<label>Total Fingerprint Enrollment(s)</label>
<input type="text" readonly v-model="staff.fingerPrintCaptureCount" class="form-control" />
</div>
<div class="form-group" v-if="step >= 1.1">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>Finger</label>
<input type="text" readonly value="Right Thumb" class="form-control" />
</div>
<div class="form-group" v-if="fingerDetails['right-thumb'].id">
<label>Enrolment ID</label>
<input type="text" readonly v-model="fingerDetails['right-thumb'].id" class="form-control" />
</div>
<div class="btn btn-success" style="display: inline" v-if="!fingerDetails['right-thumb'].id" v-bind:class="{ disabledbutton: busy.login }"
v-on:click="() => { captureFingerPrint('right-thumb') }">
<span v-if="!busy.login">Capture Fingerprint</span>
<span v-else>Please wait...</span>
</div>
<div class="btn btn-warning" style="display: inline" v-if="fingerDetails['right-thumb'].id" v-bind:class="{ disabledbutton: busy.login }"
v-on:click="() => { captureFingerPrint('right-thumb') }">
<span v-if="!busy.login">Recapture Fingerprint</span>
<span v-else>Please wait...</span>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>Finger</label>
<input type="text" readonly value="Right Index Finger" class="form-control" />
</div>
<div class="form-group" v-if="fingerDetails['right-index-finger'].id">
<label>Enrolment ID</label>
<input type="text" readonly v-model="fingerDetails['right-index-finger'].id" class="form-control" />
</div>
<div class="btn btn-success" style="display: inline" v-if="!fingerDetails['right-index-finger'].id" v-bind:class="{ disabledbutton: busy.login }"
v-on:click="() => { captureFingerPrint('right-index-finger') }">
<span v-if="!busy.login">Capture Fingerprint</span>
<span v-else>Please wait...</span>
</div>
<div class="btn btn-warning" style="display: inline" v-if="fingerDetails['right-index-finger'].id" v-bind:class="{ disabledbutton: busy.login }"
v-on:click="() => { captureFingerPrint('right-index-finger') }">
<span v-if="!busy.login">Recapture Fingerprint</span>
<span v-else>Please wait...</span>
</div>
</div>
</div>
<!-- &nbsp;&nbsp;Your profile has <span style="color: red; font-weight: bold;" v-if="!staff.verified">NOT </span>been verified. -->
</div>
<div v-if="fingerDetails['right-thumb'].id && fingerDetails['right-index-finger'].id">
<div class="form-group">
<label>OTP</label>
<input type="text" v-model="otp" class="form-control" />
</div>
<div class="form-group">
<div class="btn btn-success" v-bind:class="{ disabledbutton: busy.login || !otp }"
v-on:click="saveFingerPrint">
<span v-if="!busy.login">Save Finger Print</span>
<span v-else>Please wait...</span>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- production version, optimized for size and speed -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>
<script src="/assets/app.js"></script>
</body>
</html>
+2
View File
@@ -0,0 +1,2 @@
python-socketio
eventlet