Newer
Older
)
try:
sign_count = webauthn_assertion_response.verify()
except Exception as e:
return jsonify( {
"error": True,
"message": "Assertion failed. Error: {}".format( e )
} )
############################################################################
if len( challenge ) % 4 != 0:
challenge += "=" * ( 4 - ( len( challenge ) % 4 ) )
newuser = base64.b64decode( challenge )
newuser = json.loads( newuser )
user_type = int( newuser[ "user" ][ "account_type" ] )
email = newuser[ "user" ][ "email" ]
email_hash = pbkdf2( email, random_data( 100 ), config.EMAIL_NB_ITERATIONS ).hash()
request_uuid = newuser[ "user" ][ "uuid" ]
request_id = newuser[ "user" ][ "id" ]
username_id = newuser[ "user" ][ "username_id" ]
n = config.db.query( "SELECT name FROM account_type WHERE id = %s", ( user_type, ) )
n = n.fetchone()
n = n[ 0 ]

Marco De Donno
committed
username = n + "_" + str( username_id )
username = username.lower()
try:
config.db.query( "UPDATE signin_requests SET validation_time = now(), assertion_response = %s, status = 'validated' WHERE id = %s", ( assertion_response_s, request_id ) )
config.db.query( "INSERT INTO users ( username, email, type ) VALUES ( %s, %s, %s )", ( username, email_hash, user_type ) )
config.db.commit()

Marco De Donno
committed
except:
return jsonify( {
"error": True,
"message": "Can not insert into database."
} )
############################################################################
email_content = render_jinja_html(
"templates/email", "signin.html",
username = username,
url = "https://icnml.unil.ch" + url_for( "config_new_user", uuid = newuser[ "user" ][ "uuid" ] )
)
msg = MIMEText( email_content, "html" )
msg[ "Subject" ] = "ICNML - Login information"
msg[ "From" ] = config.sender
msg[ "To" ] = email
s = smtplib.SMTP( config.smtpserver )
s.sendmail( config.sender, [ email ], msg.as_string() )
s.quit()
############################################################################
return jsonify( {
@app.route( baseurl + "/do_validation_reject", methods = [ "POST" ] )
"""
Reject the request for a new user.
"""
request_id = request.form.get( "id" )
try:
sql = "UPDATE signin_requests SET status = 'rejected' WHERE id = %s"
r = config.db.query( sql, ( request_id, ) )
config.db.commit()
return jsonify( {
"error": False
} )
except Exception as e:
print e
return jsonify( {
"error": True
} )
def config_new_user( uuid ):
"""
Serve the first page to the new user to configure his account.
"""
session.clear()
q = config.db.query( "SELECT email FROM signin_requests WHERE uuid = %s", ( uuid, ) )
r = q.fetchone()
try:
email = r[ "email" ]
session[ "signin_user_validation_email" ] = email
session[ "signin_user_validation_uuid" ] = uuid
return render_template(
baseurl = baseurl,
js = config.cdnjs,
css = config.cdncss,
session_timeout = config.session_timeout,
envtype = envtype,
next_step = "do_config_new_user"
@app.route( baseurl + "/do_config", methods = [ "POST" ] )
"""
Save the configuration of the new user to the database.
"""
email = session[ "signin_user_validation_email" ]
uuid = session[ "signin_user_validation_uuid" ]
username = request.form.get( "username" )
password = request.form.get( "password" )
############################################################################
q = config.db.query( "SELECT count(*) FROM signin_requests WHERE uuid = %s AND email = %s", ( uuid, email, ) )
r = q.fetchone()
r = r[ 0 ]
if r == 0:
return jsonify( {
"error": True,
"message": "no signin request"
} )
q = config.db.query( "SELECT * FROM users WHERE username = %s", ( username, ) )
user = q.fetchone()
if user == None:
return jsonify( {
} )
elif not password.startswith( "pbkdf2" ):
return jsonify( {
"error": True,
"message": "password not in the correct format"
elif not pbkdf2( email, user[ "email" ] ).verify():
"error": True,
"message": "email not corresponding to the request form"
elif user.get( "password", None ) != None:
"error": True,
"message": "password already set"
} )
############################################################################
password = pbkdf2( password, random_data( 65 ), config.PASSWORD_NB_ITERATIONS ).hash()
q = config.db.query( "UPDATE users SET password = %s WHERE username = %s", ( password, username, ) )
config.db.commit()
############################################################################
return jsonify( {
@app.route( baseurl + "/config/donor/<h>" )
def config_new_user_donor( h ):
"""
Serve the configuration page for a new donor.
"""
session.clear()
sql = "SELECT id, username, email FROM users WHERE type = 2 AND password IS NULL"
for r in config.db.query_fetchall( sql ):
if h == hashlib.sha512( r[ "email" ] ).hexdigest():
user = r
break
else:
return redirect( url_for( "home" ) )
session[ "email_hash" ] = h
session[ "user_id" ] = user[ "id" ]
return render_template(
"users/config.html",
baseurl = baseurl,
js = config.cdnjs,
css = config.cdncss,
session_timeout = config.session_timeout,
envtype = envtype,
next_step = "do_config_new_donor",
hash = h
)
@app.route( baseurl + "/do_config/donor", methods = [ "POST" ] )
def do_config_new_donor():
"""
Save the donor configuration to the database.
"""
username = request.form.get( "username" )
password = request.form.get( "password" )
password = pbkdf2( password, random_data( 100 ), config.PASSWORD_NB_ITERATIONS ).hash()
h = request.form.get( "hash" )
sql = "SELECT id FROM users WHERE username = %s"
user_id = config.db.query_fetchone( sql, ( username, ) )[ "id" ]
if session[ "email_hash" ] == h and session[ "user_id" ] == user_id:
q = config.db.query( "UPDATE users SET password = %s WHERE username = %s", ( password, username, ) )
config.db.commit()
return jsonify( {
"error": False
} )
else:
return jsonify( {
"error": True,
"message": "Invalid parameters"
} )
"""
Serve the help page for the TOTP.
"""
return render_template(
"totp_help.html",
baseurl = baseurl,
js = config.cdnjs,
css = config.cdncss,
################################################################################
# QR Code generation
def renew_secret():
"""
Request a new TOTP secret.
"""
return secret
def get_secret():
"""
Retrieve the current secret.
"""
secret = session.get( "secret", None )
if secret == None:
secret = renew_secret()
return secret
"""
Set the new secret value for the TOTP in the database.
"""
config.db.query( "UPDATE users SET totp = %s WHERE username = %s", ( session[ "secret" ], session[ "username" ], ) )
config.db.commit()
return jsonify( {
"""
Serve the current secret as JSON.
"""
get_secret()
return jsonify( {
"error": False,
"secret": session[ "secret" ]
@login_required
def request_renew_secret():
"""
Serve current secret.
"""
renew_secret()
return jsonify( {
"error": False,
"secret": session[ "secret" ]
"""
Generage the png QRcode with the totp value ready to scan.
"""
if "username" in session:
qrcode_value = "otpauth://totp/ICNML%20" + session[ "username" ] + "?secret=" + get_secret()
qrcode_value = "otpauth://totp/ICNML?secret=" + get_secret()
img = qrcode.make( qrcode_value )
temp = StringIO()
img.save( temp, format = "png" )
temp.seek( 0 )
return send_file( temp, mimetype = "image/png" )
@login_required
"""
Serve the QRcode configuration page.
"""
return render_template(
"qrcode.html",
baseurl = baseurl,
secret = get_secret(),
js = config.cdnjs,
session_timeout = config.session_timeout,

Marco De Donno
committed
envtype = envtype,

Marco De Donno
committed
account_type = session.get( "account_type", None )
################################################################################
# Data decryption

Marco De Donno
committed
encryption_prefix = "icnml$"
"""
Try to decrypt the data stored server-side.
This encryption is done with a key derived from the password of the user,
only stored in RAM while the user is connected.
"""
data = AESCipher( session[ "password" ] ).decrypt( data )

Marco De Donno
committed
if data.startswith( encryption_prefix ):
return data[ len( encryption_prefix ): ]
else:
return "-"

Marco De Donno
committed
return "-"
"""
Encryption of any data passed in argument with a key derived from the user password.
This key is only stored in RAM (in redis) while the user is connected.
The sensitive shall be encrypted on the client side first, hence never
leaving the computer of the user.
"""
return AESCipher( session[ "password" ] ).encrypt( encryption_prefix + data )
################################################################################
# File upload
@app.route( baseurl + "/upload", methods = [ "GET", "POST" ] )
@login_required
def upload_file():
"""
Main function dealing with the upload of files (tenprint, latent and consent forms).
This function accept traditionals images and NIST files for the fingerprint data,
and PDFs for the consent forms.
"""
upload_type = request.form.get( "upload_type", None )
file_extension = request.form.get( "extension", None )
if isinstance( file_extension, str ):
file_extension = file_extension.lower()
if upload_type == None:
"error": True,
"msg": "Must specify a file type to upload a file"
if "file" not in request.files:
return jsonify( {
"error": True,
"msg": "No file in the POST request"
} )
elif "upload_id" not in request.form:
return jsonify( {
"error": True,
"msg": "No upload_id"
} )

Marco De Donno
committed
upload_uuid = request.form.get( "upload_id" )
sql = "SELECT id FROM submissions WHERE uuid = %s"

Marco De Donno
committed
r = config.db.query( sql, ( upload_uuid, ) )
except:
return jsonify( {
"error": True,
"msg": "upload not related to a submission form"
file_name = do_encrypt( file.filename )
_, file_ext = os.path.splitext( file.filename )
file_uuid = str( uuid4() )
fp = StringIO()
file.save( fp )
file_size = fp.tell()
fp.seek( 0 )
if file_extension in config.NIST_file_extensions:
file_data = fp.getvalue()
file_data = base64.b64encode( file_data )
return jsonify( {
"error": True,
"msg": "Error while loading the NIST file"
} )

Marco De Donno
committed
# Save the NIST file in the DB
sql = "INSERT INTO files ( folder, creator, filename, type, format, size, width, height, resolution, uuid, data ) VALUES ( %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s )"
data = ( upload_id, session[ "user_id" ], file_name, 5, "NIST", file_size, None, None, None, file_uuid, file_data )
config.db.query( sql, data )
# Segmentation of the NIST file
fpc_in_file = []
for fpc in config.all_fpc:
try:
img = n.get_print( fpc = fpc )
buff = StringIO()
img.save( buff, format = "TIFF" )
buff.seek( 0 )
img_data = buff.getvalue()
img_data = base64.b64encode( img_data )
sql = "INSERT INTO files_segments ( tenprint, uuid, pc, data ) values ( %s, %s, %s, %s )"
data = ( file_uuid, str( uuid4() ), fpc, img_data, )
config.db.query( sql, data )
fpc_in_file.append( fpc )
except:
pass
return jsonify( {
"error": False,
"fpc": fpc_in_file
} )
else:
if upload_type in [ "latent_target", "latent_incidental", "tenprint_card_front", "tenprint_card_back" ]:
img = Image.open( fp )
img_format = img.format
width, height = img.size
except:
return jsonify( {
"error": True,
"msg": "No resolution found in the image. Upload not possible at the moment."
} )
try:
img = rotate_image_upon_exif( img )
except:
pass
buff = StringIO()
img.save( buff, format = img_format )
buff.seek( 0 )
file_data = buff.getvalue()
if upload_type in [ "tenprint_card_front", "tenprint_card_back" ]:
file_data_r = file_data
file_data = base64.b64encode( file_data )
sql = "SELECT id FROM files_type WHERE name = %s"
upload_type_id = config.db.query( sql, ( upload_type, ) ).fetchone()[ 0 ]
####################################################################
if upload_type == "consent_form":
sql = "SELECT email_aes FROM submissions WHERE uuid = %s"
email = config.db.query_fetchone( sql, ( upload_uuid, ) )[ "email_aes" ]
email = do_decrypt( email )
sql = "SELECT username, email FROM users WHERE type = 2 ORDER BY id DESC"
for username_db, email_db in config.db.query_fetchall( sql ):
if pbkdf2( email, email_db ).verify():
username = username_db
url_hash = hashlib.sha512( email_db ).hexdigest()
break
else:
return jsonify( {
"error": True,
"message": "user not found"
} )
# Email for the donor
email_content = render_jinja_html(
"templates/email", "donor.html",
username = username,
url = "https://icnml.unil.ch" + url_for( "config_new_user_donor", h = url_hash )
msg[ "Subject" ] = "ICNML - You have been added as donor"
msg[ "From" ] = config.sender
msg[ "To" ] = email
msg.attach( MIMEText( email_content, "html" ) )
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
part = MIMEApplication( file_data_r, Name = "consent_form.pdf" )
part[ "Content-Disposition" ] = "attachment; filename=consent_form.pdf"
msg.attach( part )
try:
s = smtplib.SMTP( config.smtpserver )
s.sendmail( config.sender, [ email ], msg.as_string() )
s.quit()
except:
return jsonify( {
"error": True,
"message": "Can not send the email to the user"
} )
else:
# Consent form save
file_data = base64.b64encode( file_data )
file_data = gpg.encrypt( file_data, *config.gpg_key )
file_data = str( file_data )
file_data = base64.b64encode( file_data )
sql = "INSERT INTO cf ( uuid, data, email ) VALUES ( %s, %s, %s )"
config.db.query( sql , ( file_uuid, file_data, pbkdf2( email, iterations = 100000 ).hash(), ) )
sql = "UPDATE submissions SET consent_form = true WHERE uuid = %s"
config.db.query( sql, ( upload_uuid, ) )
config.db.commit()
else:
sql = "INSERT INTO files ( folder, creator, filename, type, format, size, width, height, resolution, uuid, data ) VALUES ( %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s )"
data = ( upload_id, session[ "user_id" ], file_name, upload_type_id, img_format, file_size, width, height, res, file_uuid, file_data )
config.db.commit()
"error": False,
"filesize": file_size,
"uuid": file_uuid
else:
return abort( 403 )
################################################################################
@app.route( baseurl + "/submission/new" )
"""
Serve the page to start a new submission (new donor).
"""
return render_template(
baseurl = baseurl,
js = config.cdnjs,
css = config.cdncss,
session_timeout = config.session_timeout,
session_security_key = session.get( "session_security_key" ),
envtype = envtype
@app.route( baseurl + "/submission/do_new", methods = [ "POST" ] )
"""
Check the new donor data, and store the new submission process in the database.
"""
email = request.form.get( "email", False )
# Check for duplicate base upon the email data
sql = "SELECT id, email_hash FROM submissions WHERE submitter_id = %s"
r = config.db.query( sql, ( session[ "user_id" ], ) )
if pbkdf2( email, case[ "email_hash" ] ).verify():
"error": True,
"msg": "Email already used"
email_hash = pbkdf2( email, iterations = config.EMAIL_NB_ITERATIONS ).hash()
upload_nickname = request.form.get( "upload_nickname", None )
upload_nickname = do_encrypt( upload_nickname )
sql = "INSERT INTO submissions ( uuid, email_aes, email_hash, nickname, status, submitter_id ) VALUES ( %s, %s, %s, %s, %s, %s ) RETURNING id"
data = ( id, email_aes, email_hash, upload_nickname, status, submitter_id, )
config.db.query( sql, data )
config.db.commit()
userid = config.db.query_fetchone( "SELECT nextval( 'username_donor_seqi ) as id" )[ "id" ]
username = "donor_%d" % userid
sql = "INSERT INTO users ( username, email, type ) VALUES ( %s, %s, %s )"
data = ( username, email_hash, 2 )
config.db.commit()
return jsonify( {
"error": True,
"msg": "Email not provided"
@app.route( baseurl + "/submission/<id>/add_files" )
@login_required
def submission_upload_tplp( id ):
"""
Serve the page to upload tenprint and latent images files.
This page is not accessible if a consent form is not available in the
database for this particular donor.
"""
sql = """
SELECT email_aes as email, nickname, created_time, consent_form
FROM submissions
WHERE submitter_id = %s AND uuid = %s
"""
r = config.db.query( sql, ( session[ "user_id" ], id ) )
if user[ "consent_form" ]:
for key in [ "email", "nickname" ]:
user[ key ] = do_decrypt( user[ key ] )
return render_template(
"submission/add_files.html",
baseurl = baseurl,
js = config.cdnjs,
css = config.cdncss,
session_timeout = config.session_timeout,
upload_id = id,
session_security_key = session.get( "session_security_key" ),
nist_file_extensions = json.dumps( config.NIST_file_extensions ),
**user
)
else:
return redirect( url_for( "submission_consent_form", id = id ) )
except:
return jsonify( {
"error": True,
"msg": "Case not found"
@app.route( baseurl + "/submission/<id>/consent_form" )
@login_required
def submission_consent_form( id ):
"""
Serve the page to upload the consent form for the user.
"""

Marco De Donno
committed
sql = "SELECT email_aes as email, nickname, created_time FROM submissions WHERE submitter_id = %s AND uuid = %s"
r = config.db.query( sql, ( session[ "user_id" ], id ) )

Marco De Donno
committed
user = r.fetchone()
if user != None:
user[ key ] = do_decrypt( user[ key ] )
return render_template(
"submission/consent_form.html",
baseurl = baseurl,
js = config.cdnjs,
css = config.cdncss,
session_timeout = config.session_timeout,
upload_id = id,
session_security_key = session.get( "session_security_key" ),

Marco De Donno
committed
else:
return abort( 404 )
@app.route( baseurl + "/submission/<id>/set/nickname", methods = [ "POST" ] )

Marco De Donno
committed
@login_required
def submission_update_nickname( id ):
"""
Change the nickname of the donor in the database.
THIS INFORMATION SHALL BE ENCRYPTED ON THE CLIENT SIDE FIRST WITH A UNIQUE
ENCRYPTION KEY NOT TRANSMETTED TO THE SERVER!
"""

Marco De Donno
committed
nickname = request.form.get( "nickname", None )
if nickname != None and len( nickname ) != 0:
try:
nickname = do_encrypt( nickname )
sql = "UPDATE submissions SET nickname = %s WHERE uuid = %s"
config.db.query( sql, ( nickname, id, ) )
config.db.commit()
return jsonify( {

Marco De Donno
committed
} )
except:
return jsonify( {
"error": True,
"message": "DB error"

Marco De Donno
committed
} )
else:
return jsonify( {
"error": True,
"message": "No new nickname in the POST request"

Marco De Donno
committed
} )
@app.route( baseurl + "/submission/list" )
"""
Get the list of all submissions folder for the currently logged submitter.
"""
sql = "SELECT * FROM submissions WHERE submitter_id = %s ORDER BY created_time DESC"
r = config.db.query( sql, ( session[ "user_id" ], ) )
"id": donor.get( "id", None ),
"email": do_decrypt( donor.get( "email_aes", None ) ),
"nickname": do_decrypt( donor.get( "nickname", None ) ),
"uuid": donor.get( "uuid", None )
} )
return render_template(
baseurl = baseurl,
js = config.cdnjs,
css = config.cdncss,
session_timeout = config.session_timeout,
session_security_key = session.get( "session_security_key" ),
envtype = envtype
@app.route( baseurl + "/submission/<id>/latent/list" )
@app.route( baseurl + "/submission/<id>/latent/list/<ltype>" )
@login_required
def submission_latent_list( id, ltype = "all" ):
"""
Get the list of latent for a particular submission folder.
"""
if ltype in [ "target", "incidental", "all" ]:
sql = "SELECT id, nickname FROM submissions WHERE uuid = %s AND submitter_id = %s"
r = config.db.query( sql, ( id, session[ "user_id" ], ) )
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
case_id, nickname = r.fetchone()
nickname = do_decrypt( nickname )
sql = """
SELECT files.uuid, files.filename, files.size, files.creation_time
FROM files
LEFT JOIN files_type ON files.type = files_type.id
WHERE folder = %s AND
"""
if ltype == "target":
sql += " files_type.name = 'latent_target'"
elif ltype == "incidental":
sql += " files_type.name = 'latent_incidental'"
elif ltype == "all":
sql += " ( files_type.name = 'latent_target' OR files_type.name = 'latent_incidental' )"
sql += " ORDER BY files.id DESC"
r = config.db.query( sql, ( case_id, ) )
files = r.fetchall()
for i, v in enumerate( files ):
v[ "filename" ] = do_decrypt( v[ "filename" ] )
v[ "size" ] = round( ( float( v[ "size" ] ) / ( 1024 * 1024 ) ) * 100 ) / 100
return render_template(
"submission/latent_list.html",
baseurl = baseurl,
js = config.cdnjs,
css = config.cdncss,
session_timeout = config.session_timeout,
submission_id = id,
latent_type = ltype,
files = files,
nickname = nickname,
session_security_key = session.get( "session_security_key" ),
envtype = envtype
else:
return abort( 403 )
@app.route( baseurl + "/submission/<id>/latent/<lid>" )
@login_required
def submission_latent( id, lid ):
"""
Serve the page to edit a particular latent image.
"""
sql = "SELECT id, nickname FROM submissions WHERE uuid = %s"
r = config.db.query( sql, ( id, ) )
submission_id, nickname = r.fetchone()
nickname = do_decrypt( nickname )
sql = """
SELECT
files.uuid, files.filename, files.note,
files.format, files.resolution, files.width, files.height, files.size,
files.creation_time, files.type,
files_type.name as file_type
FROM files
LEFT JOIN files_type ON files.type = files_type.id
WHERE
folder = %s AND
files.uuid = %s
"""
r = config.db.query( sql, ( submission_id, lid, ) )

Marco De Donno
committed
file = r.fetchone()
file[ "size" ] = round( 100 * float( file[ "size" ] ) / ( 1024 * 1024 ) ) / 100
file[ "filename" ] = do_decrypt( file[ "filename" ] )
file[ "note" ] = do_decrypt( file[ "note" ] )
file[ "file_type" ] = file[ "file_type" ].replace( "latent_", "" )
return render_template(
"submission/latent.html",
baseurl = baseurl,
js = config.cdnjs,
css = config.cdncss,
session_timeout = config.session_timeout,
submission_id = id,

Marco De Donno
committed
file = file,
session_security_key = session.get( "session_security_key" ),
envtype = envtype
)
@app.route( baseurl + "/submission/<id>/latent/<lid>/pfsp" )
@login_required
def submission_latent_pfsp( id, lid ):
"""
Serve the page to set the PFSP information (location on the finger
or the palm print) for the latent.
"""
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
sql = "SELECT id, nickname FROM submissions WHERE uuid = %s"
r = config.db.query( sql, ( id, ) )
submission_id, nickname = r.fetchone()
nickname = do_decrypt( nickname )
sql = """
SELECT
files.uuid, files.filename, files.note,
files.format, files.resolution, files.width, files.height, files.size,
files.creation_time, files.type,
files_type.name as file_type
FROM files
LEFT JOIN files_type ON files.type = files_type.id
WHERE
folder = %s AND
files.uuid = %s
"""
r = config.db.query( sql, ( submission_id, lid, ) )
file = r.fetchone()
file[ "size" ] = round( 100 * float( file[ "size" ] ) / ( 1024 * 1024 ) ) / 100
file[ "filename" ] = do_decrypt( file[ "filename" ] )
file[ "note" ] = do_decrypt( file[ "note" ] )
file[ "file_type" ] = file[ "file_type" ].replace( "latent_", "" )
sql = "SELECT pfsp FROM latent_info WHERE uuid = %s"
try:
current_pfsp = config.db.query( sql, ( lid, ) ).fetchone()[ 0 ]
except:
current_pfsp = None
for z in pfsp.zones:
if z[ "desc" ] == current_pfsp:
current_pfsp = ",".join( z[ "sel" ] )
return render_template(
"submission/latent_pfsp.html",
baseurl = baseurl,
js = config.cdnjs,
css = config.cdncss,
session_timeout = config.session_timeout,
submission_id = id,
nickname = nickname,
file = file,
pfsp_zones = pfsp.zones,
current_pfsp = current_pfsp,
session_security_key = session.get( "session_security_key" ),
envtype = envtype
@app.route( baseurl + "/submission/<id>/latent/<lid>/set/pfsp", methods = [ "POST" ] )
@login_required
def submission_latent_pfsp_set( id, lid ):
"""
Save the PFSP information relative to a latent.
"""
pfsp = request.form.get( "pfsp" )
sql = "SELECT id FROM latent_info WHERE uuid = %s"
q = config.db.query( sql, ( lid, ) ).fetchone()
if q == None:
sql = "INSERT INTO latent_info ( uuid, pfsp ) values ( %s, %s )"
config.db.query( sql, ( lid, pfsp, ) )
else:
sql = "UPDATE latent_info SET pfsp = %s WHERE uuid = %s"
config.db.query( sql, ( pfsp, lid, ) )
config.db.commit()
return jsonify( {
} )
@app.route( baseurl + "/submission/<id>/latent/<lid>/delete" )
@login_required
def submission_latent_delete( id, lid ):
"""
Delete a latent from the database.