Skip to content
module.py 106 KiB
Newer Older
    )

    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 ) )
        config.db.commit()
    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( {
        "error": False
@app.route( baseurl + "/do/validation_reject", methods = [ "POST" ] )
def do_validation_reject():
    """
        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
        } )
    
        return jsonify( {
            "error": True
        } )

@app.route( baseurl + "/config/<uuid>" )
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
Marco De Donno's avatar
Marco De Donno committed
        return my_render_template( 
            "users/config.html",
            next_step = "do_config_new_user"
        return redirect( url_for( "home" ) )
@app.route( baseurl + "/do/config", methods = [ "POST" ] )
def do_config_new_user():
    """
        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" )
    
    session[ "username" ] = username
    
    ############################################################################
    
    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( {
            "error": True,
            "message": "no user"
        } )
    
    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():
        return jsonify( {
            "error": True,
            "message": "email not corresponding to the request form"
    elif user.get( "password", None ) != None:
        return jsonify( {
            "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()
    
    session[ "username" ] = username
    
    ############################################################################
    
    return jsonify( {
        "error": False
@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" ]
Marco De Donno's avatar
Marco De Donno committed
    return my_render_template( 
        "users/config.html",
        next_step = "do_config_new_donor",
        hash = h
    )

@app.route( baseurl + "/do/config/donor", methods = [ "POST" ] )
    """
        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" ]
    session[ "username" ] = username
    
    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"
        } )

@app.route( baseurl + "/totp_help" )
Marco De Donno's avatar
Marco De Donno committed
def totp_help():
    """
        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.
    """
Marco De Donno's avatar
Marco De Donno committed
    secret = pyotp.random_base32( 40 )
    session[ "secret" ] = secret
    """
        Retrieve the current secret.
    """
    secret = session.get( "secret", None )
    if secret == None:
        secret = renew_secret()
    
    return secret

@app.route( baseurl + "/set_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( {
        "error": False
@app.route( baseurl + "/secret" )
def request_secret():
    """
        Serve the current secret as JSON.
    """
    get_secret()
    
    return jsonify( {
        "error": False,
        "secret": session[ "secret" ]
@app.route( baseurl + "/new_secret" )
def request_renew_secret():
    renew_secret()
    
    return jsonify( {
        "error": False,
        "secret": session[ "secret" ]
@app.route( baseurl + "/user/config/totp_qrcode.png" )
def user_totp_qrcode():
        Generate the TOTP PNG QRcode image ready to scan.
    if "username" in session:
        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" )
def user_totp_config():
        Serve the TOTP configuration page.
Marco De Donno's avatar
Marco De Donno committed
    return my_render_template( 
################################################################################
#    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
        } )
    
    except Exception as e:
        print e
        import traceback
        print traceback.print_exc()
        
        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
        } )

@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
Marco De Donno's avatar
Marco De Donno committed
################################################################################
#    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()
    
Marco De Donno's avatar
Marco De Donno committed
        return jsonify( {
            "error": True,
            "msg": "Must specify a file type to upload a file"
    if request.method == "POST":
        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,
                "msg": "No submission_id"
                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" ]
            file_name = do_encrypt_user_session( uploaded_file.filename )
            file_uuid = str( uuid4() )
            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 )
                    n = NISTf_auto( fp )
                except:
                        "msg": "Error while loading the NIST file"
                # 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:
                        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
                        res = int( img.info[ "dpi" ][ 0 ] )
                    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 )
                else:
                    file_data = fp.getvalue()
                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" ]
                    
                    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 = MIMEMultipart()
                    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(), )
                        config.db.query( sql , data )
                        
                        sql = "UPDATE submissions SET consent_form = true WHERE uuid = %s"
                        config.db.query( sql, ( submission_uuid, ) )
                        
                        config.db.commit()
                    
                else:
                    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"
                    ] )
Marco De Donno's avatar
Marco De Donno committed
                    data = ( 
                        submission_id, session[ "user_id" ],
                        file_name, upload_type_id,
                        img_format, file_size, width, height, res,
                        file_uuid, file_data,
                    )
                    config.db.query( sql, data )
                return jsonify( {
                    "error": False,
                    "uuid": file_uuid
    
    else:
        return abort( 403 )
################################################################################
Marco De Donno's avatar
Marco De Donno committed
#    Submission of a new donor
@app.route( baseurl + "/submission/new" )
def submission_new():
    """
        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" ] )
def submission_do_new():
    """
        Check the new donor data, and store the new submission process in the database.
    """
    email = request.form.get( "email", False )
    email = email.lower()
        # 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" ], ) )
        for case in r.fetchall():
            if utils.hash.pbkdf2( email, case[ "email_hash" ] ).verify():
                return jsonify( {
                    "error": True,
                    "msg": "Email already used"
Marco De Donno's avatar
Marco De Donno committed
            # Insert the new donor
            email_hash = utils.hash.pbkdf2( email, iterations = config.EMAIL_NB_ITERATIONS ).hash()
            
            upload_nickname = request.form.get( "upload_nickname", None )
            upload_nickname = do_encrypt_user_session( upload_nickname )
            submitter_id = session[ "user_id" ]
            
            status = "pending"
            
Marco De Donno's avatar
Marco De Donno committed
            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" )
            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.query( sql, data )
            config.db.commit()
            
            return jsonify( {
                "error": False,
        
    else:
        return jsonify( {
            "error": True,
            "msg": "Email not provided"
@app.route( baseurl + "/submission/<submission_id>/add_files" )
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.
    """
        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 ) )
        user = r.fetchone()
        
        if user[ "consent_form" ]:
            for key in [ "email", "nickname" ]:
                user[ key ] = do_decrypt_user_session( user[ key ] )
Marco De Donno's avatar
Marco De Donno committed
            return my_render_template( 
                submission_id = submission_id,
            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" )
def submission_consent_form( submission_id ):
    """
        Serve the page to upload the consent form for the user.
    """
Marco De Donno's avatar
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" ], submission_id ) )
        for key in [ "email", "nickname" ]:
            user[ key ] = do_decrypt_user_session( user[ key ] )
Marco De Donno's avatar
Marco De Donno committed
        return my_render_template( 
            "submission/consent_form.html",
            submission_id = submission_id,
@app.route( baseurl + "/submission/<submission_id>/set/nickname", methods = [ "POST" ] )
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!
    """
    nickname = request.form.get( "nickname", None )
    
    if nickname != None and len( nickname ) != 0:
        try:
            
            sql = "UPDATE submissions SET nickname = %s WHERE uuid = %s"
            config.db.query( sql, ( nickname, submission_id, ) )
                "error": False
                "error": True,
                "message": "DB error"
            "error": True,
            "message": "No new nickname in the POST request"
@app.route( baseurl + "/submission/list" )
def 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" ], ) )
Marco De Donno's avatar
Marco De Donno committed
    donors = []
    for donor in q:
        donors.append( {
            "id": donor.get( "id", None ),
            "email": do_decrypt_user_session( donor.get( "email_aes", None ) ),
            "nickname": do_decrypt_user_session( donor.get( "nickname", None ) ),
            "uuid": donor.get( "uuid", None )
Marco De Donno's avatar
Marco De Donno committed
    return my_render_template( 
        "submission/list.html",
@app.route( baseurl + "/submission/<submission_id>/latent/list" )
@app.route( baseurl + "/submission/<submission_id>/latent/list/<latent_type>" )
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()
        
        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 latent_type == "target":
            sql += " files_type.name = 'latent_target'"
        elif latent_type == "incidental":
            sql += " files_type.name = 'latent_incidental'"
        elif latent_type == "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 _, v in enumerate( files ):
            v[ "filename" ] = do_decrypt_user_session( v[ "filename" ] )
            v[ "size" ] = round( ( float( v[ "size" ] ) / ( 1024 * 1024 ) ) * 100 ) / 100
Marco De Donno's avatar
Marco De Donno committed
        return my_render_template( 
            "submission/latent_list.html",
            submission_id = submission_id,
            latent_type = latent_type,
Marco De Donno's avatar
Marco De Donno committed
@app.route( baseurl + "/submission/<submission_id>/latent/<latent_id>" )
Marco De Donno's avatar
Marco De Donno committed
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()
            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
        LEFT JOIN files_type ON files.type = files_type.id
Marco De Donno's avatar
Marco De Donno committed
    r = config.db.query( sql, ( submission_folder_id, latent_id, ) )
    latent = r.fetchone()
    latent[ "size" ] = round( 100 * float( latent[ "size" ] ) / ( 1024 * 1024 ) ) / 100
    latent[ "filename" ] = do_decrypt_user_session( latent[ "filename" ] )
    latent[ "note" ] = do_decrypt_user_session( latent[ "note" ] )
    latent[ "file_type" ] = latent[ "file_type" ].replace( "latent_", "" )
Marco De Donno's avatar
Marco De Donno committed
    return my_render_template( 
        submission_id = submission_id,
        nickname = nickname,
Marco De Donno's avatar
Marco De Donno committed
@app.route( baseurl + "/submission/<submission_id>/latent/<latent_id>/pfsp" )
Marco De Donno's avatar
Marco De Donno committed
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()
    
    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
    """
    
Marco De Donno's avatar
Marco De Donno committed
    r = config.db.query( sql, ( submission_folder_id, latent_id, ) )
    latent = r.fetchone()
    latent[ "size" ] = round( 100 * float( latent[ "size" ] ) / ( 1024 * 1024 ) ) / 100
    latent[ "filename" ] = do_decrypt_user_session( latent[ "filename" ] )
    latent[ "note" ] = do_decrypt_user_session( latent[ "note" ] )
    latent[ "file_type" ] = latent[ "file_type" ].replace( "latent_", "" )
    sql = "SELECT pfsp FROM latent_info WHERE uuid = %s"
    try:
Marco De Donno's avatar
Marco De Donno committed
        current_pfsp = config.db.query( sql, ( latent_id, ) ).fetchone()[ 0 ]
    except:
        current_pfsp = None
    
    for z in pfsp.zones:
        if z[ "desc" ] == current_pfsp:
            current_pfsp = ",".join( z[ "sel" ] )
Marco De Donno's avatar
Marco De Donno committed
    return my_render_template( 
        "submission/latent_pfsp.html",
        submission_id = submission_id,
        nickname = nickname,
        pfsp_zones = pfsp.zones,
Marco De Donno's avatar
Marco De Donno committed
@app.route( baseurl + "/submission/<submission_id>/latent/<latent_id>/set/pfsp", methods = [ "POST" ] )
Marco De Donno's avatar
Marco De Donno committed
def submission_latent_pfsp_set( submission_id, latent_id ):
    """
        Save the PFSP information relative to a latent.
    """