Skip to content
module.py 106 KiB
Newer Older
#!/usr/bin/python
# -*- coding: UTF-8 -*-

from cStringIO import StringIO
from datetime import datetime
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from logging.config import dictConfig
from uuid import uuid4
import base64
import hashlib
Marco De Donno's avatar
Marco De Donno committed
from PIL import Image
from flask import Flask
from flask import jsonify
from flask import request, has_request_context
from flask import send_file
from flask import session
from flask import url_for
from flask_compress import Compress
from flask_session import Session
from pyzbar import pyzbar
from werkzeug import abort, redirect
from werkzeug.http import http_date
Marco De Donno's avatar
Marco De Donno committed
from werkzeug.middleware.proxy_fix import ProxyFix
import gnupg
import pdf2image
import webauthn
from NIST.fingerprint import NISTf_auto
from PiAnoS import caseExistsInDB
from const import pfsp
import utils
from utils.decorator import admin_required, login_required, submission_has_access
from utils.template import my_render_template

from functions import dek_generate, do_encrypt_dek, do_decrypt_dek, dek_check
from functions import do_encrypt_user_session, do_decrypt_user_session
from functions import no_preview_image
from functions import mySMTP
import config
################################################################################

logrequestre = re.compile( "(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*\[[^\]]+\]\s(.*)" )

class RequestFormatter( logging.Formatter ):
    def format( self, record ):
        if has_request_context():
            try:
                username = session[ "username" ] 
            except:
                username = "-"
            
            record.msg = "{REMOTE_ADDR} (" + username + ") - " + record.msg
            record.msg = record.msg.format( **request.headers.environ )
        
        m = logrequestre.match( record.msg )
        if m:
            record.msg = m.group( 2 )
        
        return super( RequestFormatter, self ).format( record )

class myFilter( object ):
    def filter( self, record ):
        if "{}/ping".format( config.baseurl ) in record.msg and " 200 " in record.msg:
            return 0
        else:
            return 1

class myStreamHandler( logging.StreamHandler ):
    def __init__( self ):
        logging.StreamHandler.__init__( self )
        self.addFilter( myFilter() )

Marco De Donno's avatar
Marco De Donno committed
    "version": 1,
    "formatters": {
        "default": {
            "()": "module.RequestFormatter",
            "format": "[%(asctime)s] %(levelname)s: \t%(message)s",
Marco De Donno's avatar
Marco De Donno committed
    "handlers": {
        "console": {
            "class": "module.myStreamHandler",
            "formatter": "default"
Marco De Donno's avatar
Marco De Donno committed
    "root": {
        "level": "INFO",
        "handlers": [ "console" ]
    }
} )

################################################################################

app = Flask( __name__ )
app.config.from_pyfile( "config.py" )
Compress( app )
Session( app )
Marco De Donno's avatar
Marco De Donno committed
if config.PROXY:
    app.wsgi_app = ProxyFix( app.wsgi_app )

################################################################################
#    Import the views
from views.base import base
app.register_blueprint( base, url_prefix = "/" )
app.register_blueprint( base, url_prefix = config.baseurl )
from views.files import files
app.register_blueprint( files, url_prefix = config.baseurl )
from views.login import login_view
app.register_blueprint( login_view, url_prefix = config.baseurl )

################################################################################
#    Headers

@app.after_request
def add_header( r ):
    for c in [ "/cdn", "/static" ]:
        if request.path.startswith( config.baseurl + c ):
        r.headers[ "Last-Modified" ] = http_date( datetime.now() )
        r.headers[ "Cache-Control" ] = "no-cache, no-store, must-revalidate, max-age=0, s-maxage=0"
        r.headers[ "Pragma" ] = "no-cache"
        r.headers[ "Expires" ] = "0"
    
################################################################################
#    New user

@app.route( config.baseurl + "/signin" )
def new_user():
    """
        Serve the page to register to ICNML.
    """
    app.logger.info( "New user registration page open" )
    
    r = config.db.query_fetchall( "SELECT id, name FROM account_type WHERE can_singin = true" )
    
    account_type = []
    for rr in r:
        account_type.append( dict( rr ) )
    
Marco De Donno's avatar
Marco De Donno committed
    return my_render_template( 
        "users/signin.html",
@app.route( config.baseurl + "/do/signin", methods = [ "POST" ] )
def add_account_request_to_db():
    """
        Add the new user request to the database.
    """
    app.logger.info( "Registrer new user request to the database" )
    
        first_name = request.form[ "first_name" ]
        last_name = request.form[ "last_name" ]
        email = request.form[ "email" ]
        account_type = int( request.form[ "account_type" ] )
        uuid = str( uuid4() )
        
        email = email.lower()
        
        app.logger.debug( "First name: {}".format( first_name ) )
        app.logger.debug( "Last name:  {}".format( last_name ) )
        app.logger.debug( "Email:      {}".format( email ) )
        app.logger.debug( "Account:    {}".format( account_type ) )
        
        sql = "SELECT name FROM account_type WHERE id = %s"
        account_type_name = config.db.query_fetchone( sql, ( account_type, ) )[ "name" ]
        account_type_name = account_type_name.lower()
Marco De Donno's avatar
Marco De Donno committed

        sql = "SELECT nextval( 'username_{}_seq' ) as id".format( account_type_name ) 
        username_id = config.db.query_fetchone( sql )[ "id" ]
        app.logger.debug( "Username id:{}".format( username_id ) )
        
        config.db.query( 
            utils.sql.sql_insert_generate( "signin_requests", [ "first_name", "last_name", "email", "account_type", "uuid", "username_id" ] ),
            ( first_name, last_name, email, account_type, uuid, username_id, )
        )
        config.db.commit()
        
        return jsonify( {
            "error": False,
            "uuid": uuid
        app.logger.error( "New user request database error" )
        
        return jsonify( {
            "error": True
@app.route( config.baseurl + "/validate_signin" )
@admin_required
def validate_signin():
    """
        Serve the page to admins regarding the validation of new users.
    """
    app.logger.info( "Serve the signin valiation page" )
    
    q = config.db.query( """
        SELECT signin_requests.*, account_type.name as account_type 
        FROM signin_requests
        LEFT JOIN account_type ON signin_requests.account_type = account_type.id
        WHERE signin_requests.status = 'pending'
    
    try:
        r = q.fetchall()
        for rr in r:
            users.append( dict( rr ) )
            app.logger.debug( "Add '{}' to the validation list".format( rr[ "email" ] ) )
    
    app.logger.info( "{} users added to the validation list".format( len( users ) ) )
    
Marco De Donno's avatar
Marco De Donno committed
    return my_render_template( 
        "users/validate_signin.html",
@app.route( config.baseurl + "/do/validate_signin", methods = [ "POST" ] )
@admin_required
def do_validate_signin():
    """
        Prepare the new user data to be signed by the admin, and serve the page.
    """
    request_id = request.form.get( "id" )
    
    app.logger.info( "Signin validation begin for request_id '{}'".format( request_id ) )
    
    s = config.db.query_fetchone( "SELECT * FROM signin_requests WHERE id = %s", ( request_id, ) )
    r[ "user" ] = s
    r[ "user" ][ "request_time" ] = str( r[ "user" ][ "request_time" ] )
    r[ "user" ][ "validation_time" ] = str( r[ "user" ][ "validation_time" ] )
    r[ "acceptance" ] = {}
    r[ "acceptance" ][ "username" ] = session[ "username" ]
    r[ "acceptance" ][ "time" ] = str( datetime.now() )
    
    j = json.dumps( r )
    challenge = base64.b64encode( j )
    challenge = challenge.replace( "=", "" )
    session[ "validation_user_challenge" ] = challenge
    
    ############################################################################
    
    user_id = session[ "user_id" ]
    key = config.db.query_fetchone( "SELECT * FROM webauthn WHERE user_id = %s AND usage_counter > 0 ORDER BY last_usage DESC LIMIT 1", ( user_id, ) )
    
    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( config.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.
    """
    app.logger.info( "Verification of the validated new user" )
    
    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" )
    user = config.db.query_fetchone( "SELECT * FROM webauthn WHERE credential_id = %s", ( credential_id, ) )
    
    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()
        app.logger.info( "Webauthn assertion verified" )
        
    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( config.EMAIL_SALT_LENGTH ), config.EMAIL_NB_ITERATIONS ).hash()
    request_id = newuser[ "user" ][ "id" ]
    username_id = newuser[ "user" ][ "username_id" ]
    n = config.db.query_fetchone( "SELECT name FROM account_type WHERE id = %s", ( user_type, ) )[ "name" ]
    username = "{}_{}".format( n, username_id )
    username = username.lower()
    
    app.logger.info( "Creation of the new user '{}'".format( username ) )
    
        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()
        
        app.logger.debug( "User '{}' created".format( username ) )
        app.logger.error( "Error while creating the user account for '{}'".format( username ) )
        
        return jsonify( {
            "error": True,
            "message": "Can not insert into database."
        } )
    
    ############################################################################
    
    try:
        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
        
        app.logger.info( "Sending the email to the user '{}'".format( username ) )
        
        with mySMTP() as s:
            s.sendmail( config.sender, [ email ], msg.as_string() )
        
        return jsonify( {
            "error": False
        } )
    except:
        return jsonify( {
            "error": True,
            "message": "Error while sending the email"
        } )
@app.route( config.baseurl + "/do/validation_reject", methods = [ "POST" ] )
def do_validation_reject():
    """
        Reject the request for a new user.
    """
    request_id = request.form.get( "id" )
    
    app.logger.info( "Reject the new user request {}".format( request_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( config.baseurl + "/config/<uuid>" )
def config_new_user( uuid ):
    """
        Serve the first page to the new user to configure his account.
    """
    app.logger.info( "Serve user account configuration page" )
    
    r = config.db.query_fetchone( "SELECT email FROM signin_requests WHERE uuid = %s", ( uuid, ) )
        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( config.baseurl + "/do/config", methods = [ "POST" ] )
def do_config_new_user():
    """
        Save the configuration of the new user to the database.
    """
    app.logger.info( "Start user account configuration" )
    
    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
    
    ############################################################################
    
    r = config.db.query_fetchone( "SELECT count(*) FROM signin_requests WHERE uuid = %s AND email = %s", ( uuid, email, ) )
    r = r[ 0 ]
    
    if r == 0:
        return jsonify( {
            "error": True,
            "message": "no signin request"
    user = config.db.query_fetchone( "SELECT * FROM users WHERE username = %s", ( username, ) )
        app.logger.error( "No user found in the databse for '{}'".format( username ) )
        
        return jsonify( {
            "error": True,
            "message": "no user"
        } )
    
    elif not password.startswith( "pbkdf2" ):
        app.logger.error( "Password not hashed with PBKDF2" )
        
        return jsonify( {
            "error": True,
            "message": "password not in the correct format"
    elif not utils.hash.pbkdf2( email, user[ "email" ] ).verify():
        app.logger.error( "Email not corresponding to the stored email in the database" )
        
        return jsonify( {
            "error": True,
            "message": "email not corresponding to the request form"
    elif user.get( "password", None ) != None:
        app.logger.error( "Password already set for this user" )
        return jsonify( {
            "error": True,
            "message": "password already set"
        } )
    
    ############################################################################
    
    app.logger.debug( "Storing the new password to the databse" )
    
    password = utils.hash.pbkdf2( password, utils.rand.random_data( config.EMAIL_SALT_LENGTH ), 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( config.baseurl + "/config/donor/<h>" )
def config_new_user_donor( h ):
    """
        Serve the configuration page for a new donor.
    """
    app.logger.info( "Start new donor configuration" )
    
    app.logger.debug( "Searching in the database for hash value '{}'".format( h ) )
Marco De Donno's avatar
Marco De Donno committed
    sql = """
        SELECT users.id, users.username, users.email
        FROM users
        LEFT JOIN account_type ON users.type = account_type.id
Marco De Donno's avatar
Marco De Donno committed
        WHERE account_type.name = 'Donor' AND password IS NULL
    """
    for r in config.db.query_fetchall( sql ):
        if h == hashlib.sha512( r[ "email" ] ).hexdigest():
            app.logger.info( "User '{}' found".format( r[ "username" ] ) )
        app.logger.error( "The hash '{}' is not present in the users database".format( h ) )
        return redirect( url_for( "home" ) )
    
    session[ "email_hash" ] = h
    session[ "user_id" ] = user[ "id" ]
    app.logger.info( "Serving the config new donor page" )
    
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( config.baseurl + "/do/config/donor", methods = [ "POST" ] )
    """
        Save the donor configuration to the database.
    """
    app.logger.info( "Start donor configuration" )
    
    username = request.form.get( "username" )
    
    app.logger.debug( "Username: {}".format( username ) )
    
    password = request.form.get( "password" )
    password = utils.hash.pbkdf2( password, utils.rand.random_data( config.PASSWORD_SALT_LENGTH ), 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, ) )
        app.logger.info( "Password set" )
        
        app.logger.error( "Error while updating the password dor {}".format( username ) )
        return jsonify( {
            "error": True,
            "message": "Invalid parameters"
        } )

################################################################################
#    DEK encryption/decryption

@app.route( config.baseurl + "/dek/reconstruct", methods = [ "POST" ] )
@login_required
def dek_regenerate():
    app.logger.info( "DEK reconstruction" )
    try:
        email_hash = request.form.get( "email_hash" )
        username = session.get( "username" )
        
        app.logger.debug( "User: {}".format( username ) )
        
        sql = "SELECT * FROM donor_dek WHERE donor_name = %s"
        user = config.db.query_fetchone( sql, ( username, ) )
        
        if user[ "salt" ] == None:
            app.logger.error( "No DEK salt for user '{}'".format( session[ "username" ] ) )
            raise
        
        _, dek, _ = dek_generate( username = username, email_hash = email_hash, salt = user[ "salt" ] )
        
        app.logger.debug( "DEK reconstructed: {}...".format( dek[ 0:10 ] ) )
        
        check = utils.aes.do_decrypt( user[ "dek_check" ], dek )
        check = json.loads( check )
        
        if check[ "value" ] != "ok":
            app.logger.error( "DEK check error for user '{}'".format( session[ "username" ] ) )
        app.logger.debug( "DEK check OK" )
        
        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()
        
        app.logger.info( "Reconstructed DEK saved to the database" )
        
        return jsonify( {
            "error": False
        } )
    
        return jsonify( {
            "error": True
@app.route( config.baseurl + "/dek/delete" )
@login_required
def dek_delete():
    try:
        username = session.get( "username" )
        
        app.logger.info( "Delete DEK for user {}".format( 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( config.baseurl + "/dek/fulldelete" )
@login_required
def dek_delete_fully():
    try:
        username = session.get( "username" )
        
        app.logger.info( "Fully delete the DEK of user {}".format( 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( config.baseurl + "/upload", methods = [ "POST" ] )
@login_required
def upload_file():
        Main function dealing with the upload of files (tenprint, mark and consent forms).
        This function accept traditionals images and NIST files for the fingerprint data,
        and PDFs for the consent forms.
    """
    app.logger.info( "Processing of the uploaded file" )
    
    upload_type = request.form.get( "upload_type", None )
    app.logger.debug( "Upload type: {}".format( upload_type ) )
    file_extension = request.form.get( "extension", None )
    if isinstance( file_extension, str ):
        file_extension = file_extension.lower()
    
    app.logger.debug( "File extension: {}".format( file_extension ) )
    
Marco De Donno's avatar
Marco De Donno committed
        return jsonify( {
            "error": True,
            "message": "Must specify a file type to upload a file"
    if "file" not in request.files:
        app.logger.error( "No file in the upload request" )
        return jsonify( {
            "error": True,
            "message": "No file in the POST request"
        } )
    
    elif "submission_id" not in request.form:
        app.logger.error( "No submission identification number" )
        return jsonify( {
            "error": True,
            "message": "No submission_id"
        } )
    
    else:
        try:
            submission_uuid = request.form.get( "submission_id" )
            sql = "SELECT id FROM submissions WHERE uuid = %s"
            submission_id = config.db.query_fetchone( sql, ( submission_uuid, ) )[ "id" ]
            
            app.logger.debug( "Submission UUID: {}".format( submission_uuid ) )
            
        except:
            return jsonify( {
                "error": True,
                "message": "upload not related to a submission form"
Marco De Donno's avatar
Marco De Donno committed
        
        uploaded_file = request.files[ "file" ]
        file_name = do_encrypt_user_session( uploaded_file.filename )
        file_uuid = str( uuid4() )
Marco De Donno's avatar
Marco De Donno committed
        
        app.logger.debug( "File uuid: {}".format( file_uuid ) )
        
        fp = StringIO()
        
        uploaded_file.save( fp )
        file_size = fp.tell()
        
        app.logger.debug( "File size: {}".format( file_size ) )
        
        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
                app.logger.info( "NIST file loaded correctly" )
                app.logger.debug( "Records: " + ", ".join( [ "Type-%02d" % x for x in n.get_ntype() ] ) )
            
                app.logger.error( "Error while loading the NIST file" )
                return jsonify( {
                    "message": "Error while loading the NIST file"
            # Save the NIST file in the DB
            app.logger.info( "Saving the NIST file to the database" )
            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
            app.logger.info( "Segmenting the NIST file" )
            fpc_in_file = []
            for fpc in config.all_fpc:
                app.logger.debug( "FPC {}".format( fpc ) )
                    try:
                        img = n.get_print( fpc = fpc )
                    except:
                        img = n.get_palmar( fpc = fpc )
                    
                    buff = StringIO()
                    img.save( buff, format = "TIFF" )
                    app.logger.debug( str( img ) )
                    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 )
                    
                    app.logger.info( "Image saved to the database" )
                    
                    fpc_in_file.append( fpc )
                except:
                    app.logger.debug( "    No data" )
                    pass
            
            app.logger.info( "FPC processed ({}): {}".format( len( fpc_in_file ), fpc_in_file ) )
            
            config.db.commit()
            
            return jsonify( {
                "error": False,
                "fpc": fpc_in_file
            } )
                
        else:
            if upload_type in [ "mark_target", "mark_incidental", "tenprint_card_front", "tenprint_card_back" ]:
                app.logger.info( "Image file type: {}".format( upload_type ) )
                
                img = Image.open( fp )
                img_format = img.format
                width, height = img.size
                
                app.logger.debug( str( img ) )
                
                try:
                    res = int( img.info[ "dpi" ][ 0 ] )
                    app.logger.debug( "Resolution: {}".format( res ) )
                except:
                    app.logger.error( "No resolution found in the image" )
                        "message": "No resolution found in the image. Upload not possible at the moment."
                try:
                    img = utils.images.rotate_image_upon_exif( img )
                    app.logger.debug( "Rotation of the image" )
                except:
                    pass
                
                if img_format.upper() in [ "TIFF", "TIF" ]:
                    img.save( buff, format = img_format, compression = "raw" )
                else:
                    img.save( buff, format = img_format )
                
                buff.seek( 0 )
                file_data = buff.getvalue()
                if upload_type in [ "tenprint_card_front", "tenprint_card_back" ]:
                    app.logger.debug( "Creation of the thumbnail" )
                    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_fetchone( sql, ( upload_type, ) )[ "id" ]
            
            ####################################################################
            
            if upload_type == "consent_form":
                app.logger.info( "Processing of the consent form" )
                sql = "SELECT email_aes FROM submissions WHERE uuid = %s"
                email = config.db.query_fetchone( sql, ( submission_uuid, ) )[ "email_aes" ]
                email = do_decrypt_user_session( email )
Marco De Donno's avatar
Marco De Donno committed
                sql = """
                    SELECT users.username, users.email
                    FROM users
                    LEFT JOIN account_type ON users.type = account_type.id
                    WHERE account_type.name = 'Donor'
                    ORDER BY users.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()
                        app.logger.info( "Donor: {}".format( username ) )
                        break
                    app.logger.error( "User not found" )
                    return jsonify( {
                        "error": True,
                        "message": "user not found"
                    } )
                # Check that the PDF contains the QRCODE
                qrcode_checked = False
                
                try:
                    pages = pdf2image.convert_from_bytes( 
                        file_data_r,
                        poppler_path = config.POPPLER_PATH
                    )
                    
                    for page in pages:
                        decoded = pyzbar.decode( page )
                        for d in decoded:
                            if d.data == "ICNML CONSENT FORM":
                                qrcode_checked = True
                
                except:
                    qrcode_checked = False
                
                # Email for the donor
                app.logger.info( "Sending the email to 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:
                    with mySMTP() as s:
                        s.sendmail( config.sender, [ email ], msg.as_string() )
                    app.logger.info( "Email sended" )
                
                except:
                    app.logger.error( "Can not send the email to the donor" )
                    return jsonify( {
                        "error": True,
                        "message": "Can not send the email to the user"
                    } )
                
                    # Consent form save
                    app.logger.info( "Saving the consent form to the database" )
                    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 )
                    email_hash = utils.hash.pbkdf2( email, iterations = config.CF_NB_ITERATIONS ).hash()
                    
                    sql = utils.sql.sql_insert_generate( "cf", [ "uuid", "data", "email", "has_qrcode" ] )
                    data = ( file_uuid, file_data, email_hash, qrcode_checked, )
                    config.db.query( sql , data )
                    
                    sql = "UPDATE submissions SET consent_form = true WHERE uuid = %s"
                    config.db.query( sql, ( submission_uuid, ) )
            else:
                app.logger.info( "Save the file to the databse" )
                
                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"
                ] )
                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 )
                config.db.commit()
            
            return jsonify( {
                "error": False,
                "uuid": file_uuid
            } )
Marco De Donno's avatar
Marco De Donno committed

################################################################################