Пример #1
0
    def start_action(self):
        """запуск сканирования"""
        log.info("запуск сканирования")
        vpath = self.edit_path.text()
        vname = self.edit_vname.text()

        vicon_index = self.edit_icon.currentIndex()
        vicon = VOLUME_TYPE[vicon_index]

        vdescription = self.edit_description.toPlainText()

        print(vpath, vname, vicon, vdescription)

        log.info("каталог для сканирования: " + vpath)
        log.info("название: " + vname)

        #--- проверка входных данных
        self.__update_err("")
        result, err = self.__prepare_scan(vpath, vname)

        if result is False:
            log.warning(err)
            self.__update_err(err)
            return False

        #--- обновляем статус
        self.__update_state(is_started=True)

        stime_start = datetime.now()
        log.info("начало сканирования: " + now_date())

        #--- create volume record
        self.__create_volume_record()
Пример #2
0
def user_delete(user_id, user_id_hash):
    logging_prefix = logger_prefix + "user_delete({},{}) - ".format(user_id, user_id_hash)
    log.info(logging_prefix + "Starting")

    redirect_url = "/admin/"
    try:
        redirect_url = request.args.get("_r")
        if not redirect_url:
            log.warning(logging_prefix + "redirect_url not set, using default")
            redirect_url = "/admin/"

        #check user_id against user_id_hash and perform delete if match
        if user_id_hash == sha256( SALTS['user'], user_id ):
            #now delete the user
            conn=get_mysql().cursor(DictCursor)
            conn.execute("DELETE FROM users WHERE id=%s", (user_id,))
            conn.close()
            flash("The user has been deleted", "success")
        else:
            flash("Unable to delete user", "danger")
        
    except Exception as e:
        error = "There was an error completing your request. Details: {}".format(e)
        flash(error,'danger')
        log.exception(logging_prefix + error)
        
    return redirect(redirect_url)
Пример #3
0
    def move_node_up(self, node_uuid):
        """перемещение ноды вверх
			!!!! не реализовано перемещение поддерева !!!!
		"""

        parent_node = self.find_parent_node(node_uuid)
        childrens = self.get_childrens(parent_node)
        childrens_uuid = [node.uuid for node in childrens]

        index = childrens_uuid.index(node_uuid)

        node_a = childrens[index]
        if node_a.tree_rk - node_a.tree_lk > 1:
            log.warning("невозможно переместить ноду - она имеет потомков!!!")
            return False

        if index > 0:

            node_b = childrens[index - 1]

            self.__swap_nodes(node_a, node_b)

            return True

        return False
Пример #4
0
def edit_user_permissions(action,value,user_id,user_c):
    logging_prefix = logger_prefix + "edit_user_permissions({},{},{},{}) - ".format(action,value,user_id,user_c)
    log.info(logging_prefix + "Starting") 

    action_column_map = {}
    action_column_map['approve'] = "approved"
    action_column_map['write_perm'] = "write_permission"
    action_column_map['delete_perm'] = "delete_permission"
    action_column_map['admin_perm'] = "admin"

    success = 1
    try:
        #make sure the value is valid
        if value not in ["0","1"]:
            raise Exception("Invald value: {}".format(value))

        #make sure the action is valid    
        try:
            column = action_column_map[action]
        except Exception as f:
            log.warning(logging_prefix + "Action '{}' not found in action_column_map".format(action))
            raise f

        #check the hash
        if user_c == sha256(SALTS['user'], str(user_id)):

            #if action is approve, emails need to be sent
            if action == "approve":
                conn = get_mysql().cursor(DictCursor)
                conn.execute("SELECT name, email FROM users WHERE id = %s", (user_id,))
                user = conn.fetchone()
                conn.close()

                if value == "1":
                    log.info(logging_prefix + "Setting approved=1 for user {}".format(user_id))
                    sendAccountApprovedEmail(user['email'])
                else:
                    log.info(logging_prefix + "Setting approved=0 for user {}".format(user_id))
                    sendAccountDisapprovedEmail(user['email'])

            #now update the desired setting
            conn = get_mysql().cursor()
            conn.execute("UPDATE users SET "+column+" = %s WHERE id = %s", (value,user_id))
            get_mysql().commit()
            conn.close()
            log.info(logging_prefix + "Successfully update {} to {} for user id {}".format(column,value,user_id))

        else:
            log.warning(logging_prefix + "Hash mismatch {} {}".format(user_id, user_c))
    except Exception as e:
        success = 0
        error = "There was an error completing your request. Details: {}".format(e)
        log.exception(logging_prefix + error)

    return jsonify({ "success" : success, "new_value" : value })
Пример #5
0
 def verificaNivel(self, alerta=True):
     """Verifica o nível do usuário logado se é admin ou gerente. Por padrão exibe mensagem
     se não tiver permissão e salva no log."""
     if current_user.nivel < 1:
         if alerta:
             log.warning(
                 'verificaNivel, Não tem nível de acesso. Usuário: ' +
                 current_user.login)
             flash('Não tem nível de acesso!', 'alert-danger')
         return False
     else:
         return True
Пример #6
0
def fetch_related_choices(t):
    logging_prefix = logger_prefix + "fetch_related_choices({}) - ".format(t)

    choices = [('_NONE_', 'n/a')]

    if t == 'actor':
        index = ES_PREFIX + "threat_actors"
        doc_type = "actor"
    elif t == 'report':
        index = ES_PREFIX + "threat_reports"
        doc_type = "report"
    elif t == 'ttp':
        index = ES_PREFIX + "threat_ttps"
        doc_type = "ttp"
    else:
        raise Exception("Invalid type '{}'. Expected 'actor', 'ttp' or 'report'")

    es_query = {
            "query": {
                "match_all": {}
            },
            "size": 1000,
            "fields" : ["name"],
            "sort": {
                "name": {
                    "order": "asc"
                }
            }
        }

    try:
        results = get_es().search(index, doc_type, es_query)
        for r in results['hits']['hits']:
            choices.append((r['_id'] + ":::" + r['fields']['name'][0],r['fields']['name'][0]))

    except TransportError as te:

        #if the index was not found, this is most likely becuase theres no data there
        if te.status_code == 404:
            log.warning("Index '{}' was not found".format(index))
        else:
            error = "There was an error fetching related {}s. Details: {}".format(t, te)
            flash(error,'danger')
            log.exception(logging_prefix + error)

    except Exception as e:
        error = "There was an error fetching related {}s. Details: {}".format(t, e)
        flash(error,'danger')
        log.exception(logging_prefix + error)

    return choices
Пример #7
0
 def comparaNivel(self, id):
     """Verifica se nível do usuário logado é maior que o usuario do id do parametro.
     Retorna True se usuário logado for maior, ou se for ele mesmo.
     """
     usuario = self.dao.buscarID(Usuario, id)
     if (current_user.id
             == usuario.id) or (current_user.nivel > usuario.nivel):
         return True
     else:
         log.warning('comparaNivel, Não tem nível de acesso. Usuário: ' +
                     current_user.login)
         flash('Não tem nível de acesso para alterar este usuário!',
               'alert-danger')
         return False
Пример #8
0
 def PostInit(self):
     log.debug('{}.PostInit started'.format(self.name))
     _UIM = UIManager()
     controller = _UIM.get(self._controller_uid)
     parent_controller_uid = _UIM._getparentuid(self._controller_uid)
     parent_controller = _UIM.get(parent_controller_uid)
     if controller.pos == -1:
         # Appending - Not needed to declare pos
         controller.pos = parent_controller.view.GetMenuItemCount()
     if controller.pos > parent_controller.view.GetMenuItemCount():
         # If pos was setted out of range for inserting in parent Menu
         msg = 'Invalid menu position for MenuItem with text={}. Position will be setting to {}'.format(
             controller.label, parent_controller.view.GetMenuItemCount())
         log.warning(msg)
         controller.pos = parent_controller.view.GetMenuItemCount()
     log.debug('{}.PostInit ended'.format(self.name))
Пример #9
0
    def migrate(self):
        log.info("проверка версии базы")
        db_version = self.get_version()
        log.info("тек. версия: {}, необходима: {}".format(db_version, VERSION))

        if db_version == VERSION:
            log.info("версия базы подходящая")
            return True

        log.warning("необходима миграция")

        migration_steps = []

        if db_version == "1.0":
            migration_steps.append(migrations.up1_to_2)
            migration_steps.append(migrations.up2_to_3)
            migration_steps.append(migrations.up3_to_4)

        elif db_version == "2":
            migration_steps.append(migrations.up2_to_3)
            migration_steps.append(migrations.up3_to_4)

        elif db_version == "3":
            migration_steps.append(migrations.up3_to_4)

        else:
            log.warning(
                "не найдены инструкции для миграции на новую версию базы")
            return False

        for action in migration_steps:
            action(self.connection)

        self.update_version()
        self.update_db_timestamp()
        self.commit()

        db_version = self.get_version()
        log.info("тек. версия: {}".format(db_version))

        sbus.emit(sbus.DB_MIGRATED)
Пример #10
0
def verify(email, verification_hash):
    logging_prefix = logger_prefix + "verify() - "
    log.info(logging_prefix + "Starting")

    email = unquote_plus(email)

    print(email)
    print(verification_hash)

    try:
        conn=get_mysql().cursor(DictCursor)
        conn.execute("SELECT id, email, name, company, justification FROM users WHERE verification_hash = %s", (verification_hash,))
        r = conn.fetchone()
        if not r:
            log.error(logging_prefix + "User with email '{}' was not found".format(email))
            flash("Your user was not found, try registering again",'danger')
        elif r['email'] == email:
            log.info(logging_prefix + "Successful validation of {}".format(email))
            flash("Your email address have been verified, you will not be able to log in until you get approved by the Site Admin",'success')

            #update the database marking this user active
            user_id = r['id']
            conn.execute("UPDATE users SET email_verified=1 WHERE id = %s", (user_id,))
            get_mysql().commit()

            #id, email, name, company
            sendNewUserToApproveEmail(r['id'], r['email'],r['name'],r['company'],r['justification'])
        else:
            log.warning(logging_prefix + "Unsuccessful validation of {}".format(email))
            flash("We were unable to verify your account",'danger')

    except Exception as e:
        error = "There was an error completing your request. Details: {}".format(e)
        flash(error,'danger')
        log.exception(logging_prefix + error)
    finally:
        conn.close()

    return redirect("/", code=307)
Пример #11
0
    def __start_scan(self):
        """запуск сканирования"""
        log.info("запуск сканирования")

        self.volume_path = self.scan_path_entry.get()
        self.volume_name = self.volume_name_entry.get()
        self.volume_description = self.description.get(1.0, tkinter.END)

        log.info("каталог для сканирования: " + self.volume_path)
        log.info("название: " + self.volume_name)

        #--- проверка входных данных
        self.__update_err("")
        result, err = self.__prepare_scan()

        if result is False:
            log.warning(err)
            self.__update_err(err)
            return False

        #--- обновляем статус
        self.is_started = True
        self.__update_state()

        self.stime_start = datetime.now()
        log.info("начало сканирования: " + now_date())

        #--- create volume record
        self.__create_volume_record()

        self.files_counter = 0

        #--- запуск канала чтения потока
        self.__start_chan_reader()

        # t = threading.Thread(target=scan_dir, args=(self.volume_path, self.chan))
        t = threading.Thread(target=scaner.start_scan,
                             args=(self.volume_path, self.chan))
        t.start()
Пример #12
0
    def __on_select_node(self, uuid):
        """select node from tree..."""

        if uuid is None:
            log.warning("uuid = None")
            return

        log.debug("on select: " + uuid)

        # storage = smanager.get_storage()

        #--- show node data
        self.current_node = storage.get_node(uuid)
        # print(self.current_node)

        # shared.set_current_flag(self.current_node)

        # self.node_info.update_node(self.current_node)
        # self.node_editor.update_node(self.current_node)
        # self.node_files.update_node(self.current_node)

        #--- update tree node(in project.json)
        storage.project.set_current_flag(self.current_node.uuid)
Пример #13
0
 def PostInit(self):
     #        log.debug('{}.PostInit started'.format(self.name))
     UIM = UIManager()
     controller = UIM.get(self._controller_uid)
     parent_controller_uid = UIM._getparentuid(self._controller_uid)
     parent_controller = UIM.get(parent_controller_uid)
     #
     if isinstance(parent_controller, MenuController):
         if controller.pos == -1:
             # Appending - Not needed to declare pos
             controller.pos = parent_controller.view.GetMenuItemCount()
         if controller.pos > parent_controller.view.GetMenuItemCount():
             # If pos was setted out of range for inserting in parent Menu
             msg = 'Invalid position for Menu with label={}. Position will be setting to {}'.format(
                 controller.label,
                 parent_controller.view.GetMenuItemCount())
             log.warning(msg)
             controller.pos = parent_controller.view.GetMenuCount()
         parent_controller.view.Insert(controller.pos, controller.id,
                                       controller.label, self,
                                       controller.help)
     elif isinstance(parent_controller, MenuBarController):
         if controller.pos == -1:
             # Appending - Not needed to declare pos
             controller.pos = parent_controller.view.GetMenuCount()
         if controller.pos > parent_controller.view.GetMenuCount():
             # If pos was setted out of range for inserting in parent Menu
             msg = 'Invalid position for Menu with label={}. Position will be setting to {}'.format(
                 controller.label, parent_controller.view.GetMenuCount())
             log.warning(msg)
             controller.pos = parent_controller.view.GetMenuCount()
         ret_val = parent_controller.view.Insert(controller.pos, self,
                                                 controller.label)
         if not ret_val:
             raise Exception()
     else:
         raise Exception()
Пример #14
0
def view_all(t, page=1):
    if t == 'favicon.ico':
        return jsonify({}), 404

    page = int(page)

    logging_prefix = logger_prefix + "view_all() - "
    log.info(logging_prefix +
             "Loading view all page {} for {}".format(page, t))

    form = forms.searchForm(request.form)
    error = None
    page_size = 50
    offset = (page - 1) * page_size
    url = "/{}/{}/".format(t, page)
    search_url = ""
    results_text = ""
    try:

        #this is the default query for actors in ES, i'd imagine this will be recently added/modified actors
        es_query = {
            "query": {
                "match_all": {}
            },
            "size": page_size,
            "from": offset,
            "sort": {
                "last_updated_s": {
                    "order": "desc"
                }
            }
        }

        #pull the query out of the url
        query_string = request.args.get("q")

        #someone is searching for something
        if request.method == 'POST' and not query_string:
            if form.validate():
                print("VALID SEARCH OPERATION DETECTED, redirecting...")

                #get the value
                value = form.query.data

                log.info(value)

                #redirect to this same page, but setting the query value in the url
                return redirect("/{}/1/?q={}".format(t, quote_plus(value)),
                                code=307)

            else:
                #if there was an error print the error dictionary to the console
                #   temporary help, these should also appear under the form field
                print(form.errors)

        elif query_string:
            #now that the query_string is provided as ?q=, perform the search
            print("VALID SEARCH OPERATION DETECTED")

            #do some searching...
            es_query = {
                "query": {
                    "query_string": {
                        "query": query_string
                    }
                },
                "size": page_size,
                "from": offset
            }

            search_url = "?q=" + query_string
            #set the form query value to what the user is searching for
            form.query.data = query_string
        '''
        Fetch the data from ES
        '''

        data = {}
        data['hits'] = {}
        data['hits']['hits'] = []

        if t == 'actor':
            index = ES_PREFIX + 'threat_actors'
            doc_type = 'actor'
            salt = SALTS['actor']
            link_prefix = 'actor'
            data_header = 'Actors'
            field_header = 'Actor Name'
        elif t == 'report':
            index = ES_PREFIX + 'threat_reports'
            doc_type = 'report'
            salt = SALTS['report']
            link_prefix = 'report'
            data_header = 'Reports'
            field_header = 'Report Title'
        elif t == 'ttp':
            index = ES_PREFIX + 'threat_ttps'
            doc_type = 'ttp'
            salt = SALTS['ttp']
            link_prefix = 'ttp'
            data_header = 'TTPs'
            field_header = 'TTP Name'
        else:
            raise Exception("Unknown type {}".format(t))

        try:
            data = get_es().search(index, doc_type, es_query)
            num_hits = len(data['hits']['hits'])

            #set up previous link
            if page == 1:
                prev_url = None
            else:
                prev_url = "/{}/{}/{}".format(t, (page - 1), search_url)

            if ((page - 1) * page_size) + num_hits < data['hits']['total']:
                next_url = "/{}/{}/{}".format(t, (page + 1), search_url)
            else:
                next_url = None

            url += search_url

            for d in data['hits']['hits']:
                s = salt + d['_id']
                hash_object = hashlib.sha256(s.encode('utf-8'))
                hex_dig = hash_object.hexdigest()
                d["_source"]['id_hash'] = hex_dig

            if num_hits == 0:
                results_text = ""
            else:
                f = ((page - 1) * page_size) + 1
                l = f + (num_hits - 1)
                results_text = "Showing {} to {} of {} total results".format(
                    f, l, data['hits']['total'])

        except TransportError as te:

            #if the index was not found, this is most likely becuase theres no data there
            if te.status_code == 404:
                log.warning("Index '{}' was not found".format(index))
            else:
                error = "There was an error fetching {}. Details: {}".format(
                    t, te)
                flash(error, 'danger')
                log.exception(logging_prefix + error)

        except Exception as e:
            error = "The was an error fetching {}. Error: {}".format(t, e)
            log.exception(error)
            flash(error, "danger")

    except Exception as e:
        error = "There was an error completing your request. Details: {}".format(
            e)
        log.exception(error)
        flash(error, "danger")
        return redirect("/")

    return render_template("view_all.html",
                           page_title="View All",
                           form=form,
                           data_header=data_header,
                           results_text=results_text,
                           field_header=field_header,
                           data=data,
                           link_prefix=link_prefix,
                           prev_url=prev_url,
                           next_url=next_url,
                           url=quote_plus(url))
Пример #15
0
def index():
    logging_prefix = logger_prefix + "index() - "
    log.info(logging_prefix + "Loading home page")

    form = forms.searchForm(request.form)
    error = None
    url = "/"
    query_string_url = ""
    try:
        #this is the default query for actors in ES, i'd imagine this will be recently added/modified actors
        es_query = {
            "query": {
                "match_all": {}
            },
            "size": 10,
            "sort": {
                "last_updated_s": {
                    "order": "desc"
                }
            }
        }

        #pull the query out of the url
        query_string = request.args.get("q")

        #someone is searching for something
        if request.method == 'POST' and not query_string:
            if form.validate():

                #get the value
                value = form.query.data

                #redirect to this same page, but setting the query value in the url
                return redirect("/?q={}".format(quote_plus(value)), code=307)

            else:
                #if there was an error print the error dictionary to the console
                #   temporary help, these should also appear under the form field
                print(form.errors)

        elif query_string:
            #now that the query_string is provided as ?q=, perform the search
            print("VALID SEARCH OPERATION DETECTED")

            #do some searching...
            es_query = {
                "query": {
                    "query_string": {
                        "query": query_string
                    }
                },
                "size": 10
            }

            url += "?q=" + query_string
            query_string_url = "?q=" + query_string

            #set the form query value to what the user is searching for
            form.query.data = query_string
        '''
        Fetch the data from ES
        '''

        actors = {}
        actors['hits'] = {}
        actors['hits']['hits'] = []

        reports = dict(actors)

        ttps = dict(actors)

        try:
            actors = get_es().search(ES_PREFIX + 'threat_actors', 'actor',
                                     es_query)
        except TransportError as te:
            #if the index was not found, this is most likely becuase theres no data there
            if te.status_code == 404:
                log.warning("Index 'threat_actors' was not found")
            else:
                error = "There was an error fetching actors. Details: {}".format(
                    te)
                flash(error, 'danger')
                log.exception(logging_prefix + error)
        except Exception as e:
            error = "The was an error fetching Actors. Error: {}".format(e)
            log.exception(error)
            flash(error, "danger")

        try:
            reports = get_es().search(ES_PREFIX + 'threat_reports', 'report',
                                      es_query)
        except TransportError as te:
            #if the index was not found, this is most likely becuase theres no data there
            if te.status_code == 404:
                log.warning("Index 'threat_reports' was not found")
            else:
                error = "There was an error fetching reports. Details: {}".format(
                    te)
                flash(error, 'danger')
                log.exception(logging_prefix + error)
        except Exception as e:
            error = "The was an error fetching Reports. Error: {}".format(e)
            log.exception(error)
            flash(error, "danger")

        try:
            ttps = get_es().search(ES_PREFIX + 'threat_ttps', 'ttp', es_query)
        except TransportError as te:
            #if the index was not found, this is most likely becuase theres no data there
            if te.status_code == 404:
                log.warning("Index 'threat_ttps' was not found")
            else:
                error = "There was an error ttps. Details: {}".format(te)
                flash(error, 'danger')
                log.exception(logging_prefix + error)
        except Exception as e:
            error = "The was an error fetching TTPs. Error: {}".format(e)
            log.exception(error)
            flash(error, "danger")
        '''
        Modify the data as needed
        '''

        for actor in actors['hits']['hits']:
            s = SALTS['actor'] + actor['_id']
            hash_object = hashlib.sha256(s.encode('utf-8'))
            hex_dig = hash_object.hexdigest()
            actor["_source"]['id_hash'] = hex_dig

        for report in reports['hits']['hits']:
            s = SALTS['report'] + report['_id']
            hash_object = hashlib.sha256(s.encode('utf-8'))
            hex_dig = hash_object.hexdigest()
            report["_source"]['id_hash'] = hex_dig

        for ttp in ttps['hits']['hits']:
            s = SALTS['ttp'] + ttp['_id']
            hash_object = hashlib.sha256(s.encode('utf-8'))
            hex_dig = hash_object.hexdigest()
            ttp["_source"]['id_hash'] = hex_dig

    except Exception as e:
        error = "There was an error completing your request. Details: {}".format(
            e)
        log.exception(error)
        flash(error, "danger")

    #render the template, passing the variables we need
    #   templates live in the templates folder
    return render_template("index.html",
                           page_title="ActorTrackr",
                           form=form,
                           query_string_url=query_string_url,
                           actors=actors,
                           reports=reports,
                           ttps=ttps,
                           url=quote_plus(url))
Пример #16
0
 def check_password(self, supplied_pw):
     log.warning(supplied_pw)
     return check_password_hash(self.password, supplied_pw)
Пример #17
0
def login():
    logging_prefix = logger_prefix + "login() - "
    log.info(logging_prefix + "Starting")

    try:
        form = forms.loginForm(request.form)
        search_form = forms.searchForm()

        if request.method == 'POST':
            if form.validate():
                email = form.user_email.data 
                password = form.user_password.data

                hashed_password = sha256(SALTS['user'], password)

                conn=get_mysql().cursor(DictCursor)
                #since email is unique
                conn.execute("SELECT id, password, name, email_verified, approved, write_permission, delete_permission, admin FROM users WHERE email = %s", (email,))
                user = conn.fetchone()
                

                if user:
            
                    if user['password'] == hashed_password:
                        #we have found a valid user

                        if user['email_verified']==0:
                            #the user has not verified their email address
                            flash("Your email address has not been verified. Click here if you did not receive a verification email", "danger")

                        elif user['approved']==0:
                            #the user has not been approved, or their access has been disapproved
                            flash("Your account has been approved yet, you will receive an email when your account has been approved", "danger")

                        else:
                            #woohoo successful login, set up session variables
                            session['logged_in']    =   True
                            session['id']           =   user['id']
                            session['name']         =   user['name']
                            session['email']        =   email
                            session['approved']     =   (user['approved'] == 1)
                            session['write']        =   (user['write_permission'] == 1)
                            session['delete']       =   (user['delete_permission'] == 1)
                            session['admin']        =   (user['admin'] == 1)
                            session['expires']      =   math.ceil(time.time()) + SESSION_EXPIRE
                                                                            #now + 10 minutes of inactivity
                                                                            #each time this user loads a page
                                                                            #the expiration time gets now + 10m

                            #update last login timestamp
                            conn=get_mysql().cursor(DictCursor)
                            conn.execute("UPDATE users SET last_login=%s WHERE id = %s", (datetime.now(), user['id']))
                            get_mysql().commit()
                            conn.close()

                            flash("You have been logged in", "success")

                            if request.args.get("r"):
                                return redirect(request.args.get("r"))
                            else:
                                return redirect("/") 
                    else:
                        log.warning(logging_prefix + "Invalid login attempt for {}".format(email))
                        flash("The username or password is incorrect", "danger")
                else:
                    log.warning(logging_prefix + "Invalid login attempt for {}".format(email))
                    flash("The username or password is incorrect", "danger")
            else:
                print(form.errors)

    except Exception as e:
        error = "There was an error completing your request. Details: {}".format(e)
        flash(error,'danger')
        log.exception(logging_prefix + error)

    return render_template("login.html",
                        page_title="Login",
                        form=form,
                        search_form=search_form
            )