Newer
Older
challenge = challenge.replace( "=", "" )
session[ "validation_user_challenge" ] = challenge
############################################################################
q = config.db.query( "SELECT * FROM webauthn WHERE user_id = %s AND usage_counter > 0 ORDER BY last_usage DESC LIMIT 1", ( user_id, ) )
key = q.fetchone()
webauthn_user = webauthn.WebAuthnUser(
key[ "ukey" ], session[ "username" ], session[ "username" ], None,
key[ "credential_id" ], key[ "pub_key" ], key[ "sign_count" ], config.RP_ID
)
webauthn_assertion_options = webauthn.WebAuthnAssertionOptions( webauthn_user, challenge )
return jsonify( {
"error": False,
"data": webauthn_assertion_options.assertion_dict
@app.route( baseurl + "/do/validate_signin_2", methods = [ "POST" ] )
@admin_required
def do_validate_signin_2():
"""
Verification of the signature of the new user data by the admin.
"""
challenge = session.get( "validation_user_challenge" )
assertion_response = request.form
assertion_response_s = base64.b64encode( json.dumps( assertion_response ) )
credential_id = assertion_response.get( "id" )
q = config.db.query( "SELECT * FROM webauthn WHERE credential_id = %s", ( credential_id, ) )
user = q.fetchone()
webauthn_user = webauthn.WebAuthnUser(
user[ "ukey" ], session[ "username" ], session[ "username" ], None,
user[ "credential_id" ], user[ "pub_key" ], user[ "sign_count" ], "icnml.unil.ch"
)
webauthn_assertion_response = webauthn.WebAuthnAssertionResponse(
webauthn_user,
assertion_response,
challenge,
config.ORIGIN,
uv_required = False
)
try:
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 = utils.hash.pbkdf2( email, utils.rand.random_data( 100 ), config.EMAIL_NB_ITERATIONS ).hash()
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 ]
username = "{}_{}".format( n, 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( utils.sql.sql_insert_generate( "users", [ "username", "email", "type" ] ), ( username, email_hash, user_type ) )

Marco De Donno
committed
except:
return jsonify( {
"error": True,
"message": "Can not insert into database."
} )
############################################################################
email_content = utils.template.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.starttls()
s.login( config.smtpuser, config.smtppassword )
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"
config.db.query( sql, ( request_id, ) )
config.db.commit()
return jsonify( {
"error": False
} )
except:
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
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 utils.hash.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 = utils.hash.pbkdf2( password, utils.rand.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" ]
"users/config.html",
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 = utils.hash.pbkdf2( password, utils.rand.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:
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 my_render_template( "totp_help.html" )
################################################################################
# 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" ]
@app.route( baseurl + "/user/config/totp_qrcode.png" )
Generate the TOTP PNG QRcode image ready to scan.
qrcode_value = "otpauth://totp/ICNML%20{}?secret={}".format( session[ "username" ], get_secret() )
qrcode_value = "otpauth://totp/ICNML?secret={}".format( get_secret() )
img = qrcode.make( qrcode_value )
temp = StringIO()
img.save( temp, format = "png" )
temp.seek( 0 )
return send_file( temp, mimetype = "image/png" )
@app.route( baseurl + "/user/config/totp" )
@login_required
"users/totp.html",
secret = get_secret()
################################################################################
# DEK encryption/decryption
@app.route( baseurl + "/dek/reconstruct", methods = [ "POST" ] )
@login_required
def dek_regenerate():
try:
email_hash = request.form.get( "email_hash" )
username = session.get( "username" )
sql = "SELECT * FROM donor_dek WHERE donor_name = %s"
user = config.db.query_fetchone( sql, ( username, ) )
_, dek, _ = dek_generate( username = username, email_hash = email_hash, salt = user[ "salt" ] )
check = utils.aes.do_decrypt( user[ "dek_check" ], dek )
check = json.loads( check )
if check[ "value" ] != "ok":
raise
sql = "UPDATE donor_dek SET dek = %s WHERE id = %s AND donor_name = %s"
config.db.query( sql, ( dek, user[ "id" ], username, ) )
config.db.commit()
return jsonify( {
"error": False
} )
return jsonify( {
"error": True
} )
@app.route( baseurl + "/dek/delete" )
@login_required
def dek_delete():
try:
username = session.get( "username" )
sql = "UPDATE donor_dek SET dek = NULL WHERE donor_name = %s"
config.db.query( sql, ( username, ) )
config.db.commit()
return jsonify( {
"error": False
} )
except:
return jsonify( {
"error": True
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
} )
@app.route( baseurl + "/dek/fulldelete" )
@login_required
def dek_delete_fully():
try:
username = session.get( "username" )
sql = "UPDATE donor_dek SET dek = NULL WHERE donor_name = %s"
config.db.query( sql, ( username, ) )
sql = "UPDATE donor_dek SET salt = NULL WHERE donor_name = %s"
config.db.query( sql, ( username, ) )
sql = "UPDATE donor_dek SET dek_check = NULL WHERE donor_name = %s"
config.db.query( sql, ( username, ) )
config.db.commit()
return jsonify( {
"error": False
} )
except:
return jsonify( {
"error": True
################################################################################
# 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 "submission_id" not in request.form:
return jsonify( {
"error": True,
submission_uuid = request.form.get( "submission_id" )
sql = "SELECT id FROM submissions WHERE uuid = %s"
r = config.db.query( sql, ( submission_uuid, ) )
submission_id = r.fetchone()[ "id" ]
except:
return jsonify( {
"error": True,
"msg": "upload not related to a submission form"
uploaded_file = request.files[ "file" ]

Marco De Donno
committed
file_name = do_encrypt_user_session( uploaded_file.filename )
file_uuid = str( uuid4() )
uploaded_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 )
file_data = do_encrypt_dek( file_data, submission_uuid )
if not n.is_initialized():
raise
return jsonify( {
"error": True,
"msg": "Error while loading the NIST file"
} )

Marco De Donno
committed
# Save the NIST file in the DB
sql = utils.sql.sql_insert_generate( "files", [ "folder", "creator", "filename", "type", "format", "size", "uuid", "data" ] )
data = ( submission_id, session[ "user_id" ], file_name, 5, "NIST", file_size, file_uuid, file_data, )
config.db.query( sql, data )
# Segmentation of the NIST file
fpc_in_file = []
for fpc in config.all_fpc:
try:

Marco De Donno
committed
try:
img = n.get_print( fpc = fpc )
except:
img = n.get_palmar( fpc = fpc )
buff = StringIO()
img.save( buff, format = "TIFF" )
buff.seek( 0 )
img_data = buff.getvalue()
img_data = base64.b64encode( img_data )
img_data = do_encrypt_dek( img_data, submission_uuid )
sql = utils.sql.sql_insert_generate( "files_segments", [ "tenprint", "uuid", "pc", "data" ] )
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."
} )
img = utils.images.rotate_image_upon_exif( img )
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" ]:
create_thumbnail( file_uuid, img, submission_uuid )
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, ( submission_uuid, ) )[ "email_aes" ]

Marco De Donno
committed
email = do_decrypt_user_session( 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 utils.hash.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 = utils.template.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" ) )
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.starttls()
s.login( config.smtpuser, config.smtppassword )
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 = utils.sql.sql_insert_generate( "cf", [ "uuid", "data", "email" ] )
data = ( file_uuid, file_data, utils.hash.pbkdf2( email, iterations = 100000 ).hash(), )
sql = "UPDATE submissions SET consent_form = true WHERE uuid = %s"
config.db.query( sql, ( submission_uuid, ) )
file_data = do_encrypt_dek( file_data, submission_uuid )
sql = utils.sql.sql_insert_generate( "files", [
"folder", "creator",
"filename", "type",
"format", "size", "width", "height", "resolution",
"uuid", "data"
] )
submission_id, session[ "user_id" ],
file_name, upload_type_id,
img_format, file_size, width, height, res,
file_uuid, file_data,
)
config.db.commit()
else:
return abort( 403 )
################################################################################
@app.route( baseurl + "/submission/new" )
@submission_has_access
"""
Serve the page to start a new submission (new donor).
"""
return my_render_template( "submission/new.html" )
@app.route( baseurl + "/submission/do_new", methods = [ "POST" ] )
@submission_has_access
"""
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 utils.hash.pbkdf2( email, case[ "email_hash" ] ).verify():
"error": True,
"msg": "Email already used"
donor_uuid = str( uuid4() )

Marco De Donno
committed
email_aes = do_encrypt_user_session( email )
email_hash = utils.hash.pbkdf2( email, iterations = config.EMAIL_NB_ITERATIONS ).hash()
upload_nickname = request.form.get( "upload_nickname", None )

Marco De Donno
committed
upload_nickname = do_encrypt_user_session( upload_nickname )
userid = config.db.query_fetchone( "SELECT nextval( 'username_donor_seq' ) as id" )[ "id" ]
username = "donor_{}".format( userid )
sql = utils.sql.sql_insert_generate( "users", [ "username", "email", "type" ], "id" )
data = ( username, email_hash, 2 )
donor_user_id = config.db.query_fetchone( sql, data )[ "id" ]
dek_salt, dek, dek_check = dek_generate( email = email, username = username )
sql = utils.sql.sql_insert_generate( "donor_dek", [ "donor_name", "salt", "dek", "dek_check", "iterations", "algo", "hash" ], "id" )
data = ( username, dek_salt, dek, dek_check, config.DEK_NB_ITERATIONS, "pbkdf2", "sha512", )
config.db.query_fetchone( sql, data )
sql = utils.sql.sql_insert_generate( "submissions", [ "uuid", "email_aes", "email_hash", "nickname", "donor_id", "status", "submitter_id" ] )
data = ( donor_uuid, email_aes, email_hash, upload_nickname, donor_user_id, status, submitter_id, )
config.db.commit()
return jsonify( {
"id": donor_uuid
"error": True,
"msg": "Email not provided"
@app.route( baseurl + "/submission/<submission_id>/add_files" )
@submission_has_access
def submission_upload_tplp( submission_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.
"""
dek_check( submission_id )
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" ], submission_id ) )
if user[ "consent_form" ]:
for key in [ "email", "nickname" ]:

Marco De Donno
committed
user[ key ] = do_decrypt_user_session( user[ key ] )
"submission/add_files.html",
**user
)
else:
return redirect( url_for( "submission_consent_form", submission_id = submission_id ) )
except:
return jsonify( {
"error": True,
"msg": "Case not found"
@app.route( baseurl + "/submission/<submission_id>/consent_form" )
@submission_has_access
def submission_consent_form( submission_id ):
"""
Serve the page to upload the consent form for the user.
"""
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" ], submission_id ) )

Marco De Donno
committed
user = r.fetchone()
if user != None:

Marco De Donno
committed
user[ key ] = do_decrypt_user_session( user[ key ] )
"submission/consent_form.html",

Marco De Donno
committed
else:
return abort( 404 )
@app.route( baseurl + "/submission/<submission_id>/set/nickname", methods = [ "POST" ] )
@submission_has_access
def submission_update_nickname( submission_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:

Marco De Donno
committed
nickname = do_encrypt_user_session( nickname )

Marco De Donno
committed
sql = "UPDATE submissions SET nickname = %s WHERE uuid = %s"
config.db.query( sql, ( nickname, submission_id, ) )

Marco De Donno
committed
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" )
@submission_has_access
"""
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" ], ) )

Marco De Donno
committed
"email": do_decrypt_user_session( donor.get( "email_aes", None ) ),
"nickname": do_decrypt_user_session( donor.get( "nickname", None ) ),
donors = donors
@app.route( baseurl + "/submission/<submission_id>/latent/list" )
@app.route( baseurl + "/submission/<submission_id>/latent/list/<latent_type>" )
@submission_has_access
def submission_latent_list( submission_id, latent_type = "all" ):
"""
Get the list of latent for a particular submission folder.
"""
if latent_type in [ "target", "incidental", "all" ]:
sql = "SELECT id, nickname FROM submissions WHERE uuid = %s"
r = config.db.query( sql, ( submission_id, ) )
case_id, nickname = r.fetchone()

Marco De Donno
committed
nickname = do_decrypt_user_session( 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
"""
sql += " files_type.name = 'latent_target'"
sql += " files_type.name = 'latent_incidental'"
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 _, v in enumerate( files ):

Marco De Donno
committed
v[ "filename" ] = do_decrypt_user_session( v[ "filename" ] )
v[ "size" ] = round( ( float( v[ "size" ] ) / ( 1024 * 1024 ) ) * 100 ) / 100
"submission/latent_list.html",
files = files,
nickname = nickname
else:
return abort( 403 )
@app.route( baseurl + "/submission/<submission_id>/latent/<latent_id>" )
@submission_has_access
def submission_latent( submission_id, latent_id ):
"""
Serve the page to edit a particular latent image.
"""
sql = "SELECT id, nickname FROM submissions WHERE uuid = %s"
r = config.db.query( sql, ( submission_id, ) )
submission_folder_id, nickname = r.fetchone()

Marco De Donno
committed
nickname = do_decrypt_user_session( 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_folder_id, latent_id, ) )
latent = r.fetchone()
latent[ "size" ] = round( 100 * float( latent[ "size" ] ) / ( 1024 * 1024 ) ) / 100

Marco De Donno
committed
latent[ "filename" ] = do_decrypt_user_session( latent[ "filename" ] )
latent[ "note" ] = do_decrypt_user_session( latent[ "note" ] )
latent[ "file_type" ] = latent[ "file_type" ].replace( "latent_", "" )
"submission/latent.html",
file = latent
)
@app.route( baseurl + "/submission/<submission_id>/latent/<latent_id>/pfsp" )
@submission_has_access
def submission_latent_pfsp( submission_id, latent_id ):
"""
Serve the page to set the PFSP information (location on the finger
or the palm print) for the latent.
"""
sql = "SELECT id, nickname FROM submissions WHERE uuid = %s"
r = config.db.query( sql, ( submission_id, ) )
submission_folder_id, nickname = r.fetchone()

Marco De Donno
committed
nickname = do_decrypt_user_session( 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