Skip to content
module.py 98.4 KiB
Newer Older
        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 = pbkdf2( email, 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( 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 = 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( {
        "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
        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 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 = 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()
    
    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" ]
    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 = 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" ]
    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.
    return my_render_template(
################################################################################
#    Data decryption

def do_decrypt( data ):
    """
        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 )
        
        if data.startswith( encryption_prefix ):
            return data[ len( encryption_prefix ): ]
        else:
            return "-"
    

def do_encrypt( data ):
    """
        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 )
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"
                upload_uuid = request.form.get( "submission_id" )
                sql = "SELECT id FROM submissions WHERE uuid = %s"
                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( 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 )
                
                    n = NISTf_auto( fp )
                except:
                        "msg": "Error while loading the NIST file"
                # Save the NIST file in the DB
                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 )
                        
                        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."
                        } )
                    
                    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" ]:
                        create_thumbnail( file_uuid, img )
                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, ( 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 = 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.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 = sql_insert_generate( "cf", [ "uuid", "data", "email" ] )
                        data = ( file_uuid, file_data, pbkdf2( email, iterations = 100000 ).hash(), )
                        config.db.query( sql , data )
                        
                        sql = "UPDATE submissions SET consent_form = true WHERE uuid = %s"
                        config.db.query( sql, ( upload_uuid, ) )
                        
                        config.db.commit()
                    
                else:
                    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.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 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_aes = do_encrypt( email )
            email_hash = pbkdf2( email, iterations = config.EMAIL_NB_ITERATIONS ).hash()
            
            upload_nickname = request.form.get( "upload_nickname", None )
            upload_nickname = do_encrypt( upload_nickname )
            submitter_id = session[ "user_id" ]
            
            status = "pending"
            
            sql = sql_insert_generate( "submissions", [ "uuid", "email_aes", "email_hash", "nickname", "status", "submitter_id" ] )
            data = ( donor_uuid, email_aes, email_hash, upload_nickname, status, submitter_id, )
            config.db.query( sql, data )
            config.db.commit()
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 = sql_insert_generate( "users", [ "username", "email", "type" ] )
            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" ]:
            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[ key ] )
        
        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:
            nickname = do_encrypt( nickname )
            
            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( donor.get( "email_aes", None ) ),
            "nickname": do_decrypt( donor.get( "nickname", None ) ),
            "uuid": donor.get( "uuid", None )
    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 AND submitter_id = %s"
        r = config.db.query( sql, ( submission_id, session[ "user_id" ], ) )
        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 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( v[ "filename" ] )
            v[ "size" ] = round( ( float( v[ "size" ] ) / ( 1024 * 1024 ) ) * 100 ) / 100
        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()
    nickname = do_decrypt( nickname )
            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( latent[ "filename" ] )
    latent[ "note" ] = do_decrypt( latent[ "note" ] )
    latent[ "file_type" ] = latent[ "file_type" ].replace( "latent_", "" )
    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()
    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
    """
    
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( latent[ "filename" ] )
    latent[ "note" ] = do_decrypt( 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" ] )
    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.
    """
    pfsp = request.form.get( "pfsp" )
    
    sql = "SELECT id FROM latent_info WHERE uuid = %s"
Marco De Donno's avatar
Marco De Donno committed
    q = config.db.query( sql, ( latent_id, ) ).fetchone()
        sql = sql_insert_generate( "latent_info", [ "uuid", "pfsp" ] )
Marco De Donno's avatar
Marco De Donno committed
        config.db.query( sql, ( latent_id, pfsp, ) )
    
    else:
        sql = "UPDATE latent_info SET pfsp = %s WHERE uuid = %s"
Marco De Donno's avatar
Marco De Donno committed
        config.db.query( sql, ( pfsp, latent_id, ) )
        "error": False
Marco De Donno's avatar
Marco De Donno committed
@app.route( baseurl + "/submission/<submission_id>/latent/<latent_id>/delete" )
Marco De Donno's avatar
Marco De Donno committed
def submission_latent_delete( submission_id, latent_id ):
    """
        Delete a latent from the database.
    """
    sql = "SELECT id FROM submissions WHERE submitter_id = %s AND uuid = %s"
    q = config.db.query( sql, ( session[ "user_id" ], submission_id, ) )
    
    if q != None:
        sql = "DELETE FROM files WHERE creator = %s AND uuid = %s"
Marco De Donno's avatar
Marco De Donno committed
        config.db.query( sql, ( session[ "user_id" ], latent_id, ) )
            "error": False
            "error": True
################################################################################
#    Image processing

@app.route( baseurl + "/image/file/<file_id>/preview" )
def image_file_serve( file_id ):
    """
        Function to get an image from the database and return it as PNG preview image.
    """
        img, _ = image_serve( "thumbnails", file_id )
            img, _ = image_serve( "files", file_id )
            img = create_thumbnail( file_id, img )
        buff = pil2buffer( img, "PNG" )
        return send_file( buff, mimetype = "image/png" )
    except:
        img = Image.new( "L", ( 210, 297 ), 255 )
        draw = ImageDraw.Draw( img )
        font = ImageFont.truetype( "arial.ttf", 18 )
        draw.text( ( 0, 0 ), "No preview", 0, font = font )
        
        buff = pil2buffer( img, "PNG" )
        return send_file( buff, mimetype = "image/png" )
@app.route( baseurl + "/image/segment/<tenprint_id>/<pc>" )