def save(request): """ :param request: :return: """ result = False name = request.POST.get('name', None) module_name = request.POST.get('module_name', '') group_id = request.POST.get('group', None) project_id = request.POST.get('project', None) branch_id = request.POST.get('branch', None) if not all((name, project_id, branch_id)): raise ParameterIsEmptyException('"name, project_id, branch_id" parameter cannot be empty.') app_obj = get_app_by_app_name(name=name, project_id=project_id) if app_obj: raise Exception('"{0}" 应用已存在!'.format(name)) app_obj = get_app_by_module_name(name=name, project_id=project_id) if app_obj: raise Exception('"{0}" 应用模块名称已存在!'.format(module_name)) project_obj = get_project_by_id(project_id=project_id) repo_obj = get_repository_by_id(repo_id=branch_id) app = create_app_obj( project_obj=project_obj, module_name=module_name, app_name=name, repo_obj=repo_obj, ) if app: result = True return result
def create_t_group_obj(**kwargs): """ :param kwargs: :return: """ profile_obj = kwargs.get('profile_obj', None) periodic_obj = kwargs.get('periodic_obj', None) sched_obj = kwargs.get('sched_obj', None) is_default = kwargs.get('is_default', False) name = kwargs.get('name', '') branch = kwargs.get('branch', '') args = kwargs.get('args', '') if not all((name, )): raise ParameterIsEmptyException(u'"name" parameters cannot be empty !') if name and len(name) > 255: name = name[:255] close_old_connections() grp = TaskGroupInfo(profile=profile_obj, periodic=periodic_obj, sched=sched_obj, name=name.strip(), branch=branch, is_default=is_default, args=args.strip()) grp.save() return grp
def create_task_by_app_id(**kwargs): """ :param kwargs: :return: """ app_id = kwargs.get('app_id', None) group_id = kwargs.get('group_id', None) is_force_scan = kwargs.get('is_force_scan', False) scan_way = kwargs.get('scan_way', 1) version = kwargs.get('version', '') if not all((app_id, group_id)): raise ParameterIsEmptyException('"app_id, group_id" parameters cannot be empty.') app_obj = get_app_by_id(app_id=app_id) group_obj = get_t_group_by_id(group_id=group_id) task = create_task_obj( app_obj=app_obj, group_obj=group_obj, is_force_scan=is_force_scan, scan_way=scan_way, version=version, ) return task
def create_pro_history_obj(**kwargs): """ :param kwargs: :return: """ project_obj = kwargs.get('project_obj', None) htype = kwargs.get('type', 1) user = kwargs.get('user', None) title = kwargs.get('title', None) is_first = kwargs.get('is_first', False) description = kwargs.get('description', None) if not description: raise ParameterIsEmptyException( u'Project description cannot be empty!') if len(title) > 128: title = title[:128] close_old_connections() his = ProjectHistoryInfo(project=project_obj, type=htype, user=user, title=title, is_first=is_first, description=description) his.save() return his
def create_tag_obj(**kwargs): """ :param kwargs: :return: """ name = kwargs.get('name', None) description = kwargs.get('description', None) parent = kwargs.get('parent', None) if not all((name, )): raise ParameterIsEmptyException(u'"name" parameters cannot be empty !') if name and len(name) > 64: name = name[:64] close_old_connections() tag = TagInfo( name=name, description=description, parent=parent, ) tag.save() return tag
def create_member_obj(**kwargs): """ :param kwargs: :return: """ git_id = kwargs.get('git_id', None) name = kwargs.get('name', None) username = kwargs.get('username', None) state = kwargs.get('state', None) web_url = kwargs.get('web_url', None) email = kwargs.get('email', None) gitlab_created_at = kwargs.get('gitlab_created_at', None) if not name: raise ParameterIsEmptyException(u'Member name cannot be empty!') if name and len(name) > 64: name = name[:64] if git_id: git_id = int(git_id) close_old_connections() member = MemberInfo( git_id=git_id, name=name, username=username, state=state, web_url=web_url, email=email, gitlab_created_at=gitlab_created_at, ) member.save() return member
def create_service_obj(**kwargs): """ :param kwargs: :return: """ name = kwargs.get('name', None) key = kwargs.get('key', None) role = kwargs.get('role', None) process_keyword = kwargs.get('process_keyword', None) description = kwargs.get('description', None) if not all(( name, key, process_keyword, )): raise ParameterIsEmptyException( u'"name, key, process_keyword" parameters cannot be empty !') close_old_connections() service = ServiceInfo( name=name, key=key, process_keyword=process_keyword, description=description, role=role, ) service.save() return service
def create_host_obj(**kwargs): """ :param kwargs: :return: """ hostname = kwargs.get('hostname', None) ipv4 = kwargs.get('ipv4', None) ipv6 = kwargs.get('ipv6', None) role = kwargs.get('role', 'client') ui_version = kwargs.get('ui_version', None) client_version = kwargs.get('client_version', None) if not all(( hostname, role, )): raise ParameterIsEmptyException( u'"hostname, role" parameters cannot be empty !') close_old_connections() host = HostInfo( hostname=hostname, ipv4=ipv4, ) if role and role.lower() == 'ui': host.is_role_ui = True else: host.is_role_client = True if ui_version: host.ui_version = ui_version.strip() if client_version: host.client_version = client_version.strip() host.save() return host
def create_node_service_obj(**kwargs): """ :param kwargs: :return: """ node_obj = kwargs.get('node_obj', None) service_obj = kwargs.get('service_obj', None) ppid = kwargs.get('ppid', None) pid = kwargs.get('pid', None) status = kwargs.get('status', None) if not all((node_obj, service_obj, ppid, pid)): raise ParameterIsEmptyException(u'"node_obj, service_obj, ppid, pid" parameters cannot be empty !') close_old_connections() node = HostServiceInfo( node=node_obj, service=service_obj, ppid=ppid, pid=pid, status=status, ) node.save() return node
def create_flow_obj(**kwargs): """ :param kwargs: :return: """ issue_obj = kwargs.get('issue_obj', None) user = kwargs.get('user', None) comment = kwargs.get('comment', None) if not all((issue_obj, comment)): raise ParameterIsEmptyException(u'"issue_obj, comment" parameters cannot be empty !') close_old_connections() issue = IssueFlowInfo( issue=issue_obj, user=user, behavior=behavior_type.HUMAN if user else behavior_type.SYS, status=issue_obj.status, comment=comment, ) issue.save() return issue
def create_whitelist_obj(**kwargs): """ :param kwargs: :return: """ issue_obj = kwargs.get('issue_obj', None) operator = kwargs.get('operator', None) app_obj = kwargs.get('app_obj', None) comment = kwargs.get('comment', None) is_active = kwargs.get('is_active', True) if not all((issue_obj, operator, app_obj)): raise ParameterIsEmptyException( u'"issue_obj, operator, app_obj" parameters cannot be empty !') if comment: comment = comment[:500] close_old_connections() whitelist = IssueWhiteListInfo( issue=issue_obj, operator=operator, app=app_obj, comment=comment, is_active=is_active, ) whitelist.save() return whitelist
def create_lang_obj(**kwargs): """ :param kwargs: :return: """ name = kwargs.get('name', None) key = kwargs.get('key', None) if not name: raise ParameterIsEmptyException(u'Language name cannot be empty!') if name and len(name) > 16: name = name[:16] if not key: key = name.lower() else: key = key.lower() close_old_connections() lang = LanguageInfo( name=name, key=key, ) lang.save() cache.set(CONFIG_LANG_CACHE[2], None, 0) return lang
def create_app_obj(**kwargs): """ :param kwargs: :return: """ project_obj = kwargs.get('project_obj', None) repo_obj = kwargs.get('repo_obj', None) lang_obj = kwargs.get('lang_obj', None) module_name = kwargs.get('module_name', None) app_name = kwargs.get('app_name', None) version = kwargs.get('version', '') code_total = parse_int(kwargs.get('code_total', 0)) size = parse_int(kwargs.get('size', 0)) report_url = kwargs.get('report_url', '') ignore_count = parse_int(kwargs.get('ignore_count', 0)) critical = parse_int(kwargs.get('critical', 0)) high = parse_int(kwargs.get('high', 0)) medium = parse_int(kwargs.get('medium', 0)) low = parse_int(kwargs.get('low', 0)) info = parse_int(kwargs.get('info', 0)) status = kwargs.get('info', 1) if not all(( project_obj, module_name, app_name, )): raise ParameterIsEmptyException( u'"project_obj, module_name, app_name" parameters cannot be empty !' ) module_name = module_name.lower() close_old_connections() app = ApplicationInfo( project=project_obj, repo=repo_obj, lang=lang_obj, module_name=module_name.strip(), app_name=app_name.strip(), version=version, code_total=code_total, size=size, report_url=report_url, ignore_count=ignore_count, critical=critical, high=high, medium=medium, low=low, info=info, risk_scope=0, status=status, ) app.save() return app
def create_sched_obj(**kwargs): """ :param kwargs: :return: """ name = kwargs.get('name', None) sched_type = kwargs.get('type', None) value = kwargs.get('value', None) if not all(( name, value, sched_type, )): raise ParameterIsEmptyException( u'"name, value, sched_type" parameters cannot be empty !') if sched_type: sched_type = int(sched_type) close_old_connections() if sched_type == 1: tri = _get_interval(seconds=value) if not tri: value = int(value) tri = IntervalSchedule(every=value * 60, period='seconds') tri.save() else: tri = _get_crontab(cron_expression=value) if not tri: minute, hour, day_of_week, day_of_month, month_of_year = value.split( ' ') tri = CrontabSchedule( minute=minute, hour=hour, day_of_week=day_of_week, day_of_month=day_of_month, month_of_year=month_of_year, timezone='Asia/Shanghai', ) tri.save() sched = SchedInfo( name=name, type=int(sched_type), ) if sched_type == 1: sched.interval = tri else: sched.crontab = tri sched.save() return sched
def create_vuln_obj(**kwargs): """ :param kwargs: :return: """ cate_obj = kwargs.get('cate_obj', None) title = kwargs.get('title', None) description = kwargs.get('description', '') solution = kwargs.get('solution', '') reference = kwargs.get('reference', '') risk = kwargs.get('risk', None) hit = kwargs.get('hit', 0) impact_version = kwargs.get('impact_version', '') cnnvd = kwargs.get('cnnvd', '') cnvd = kwargs.get('cnvd', '') cve = kwargs.get('cve', '') bugtraq = kwargs.get('bugtraq', False) origin = kwargs.get('origin', None) find_time = kwargs.get('find_time', None) if not all(( cate_obj, title, risk, )): raise ParameterIsEmptyException( u'"cate_obj, title, risk" parameters cannot be empty !') if title and len(title) > 255: title = title[:255] if risk: risk = int(risk) close_old_connections() vuln = VulnInfo(cate=cate_obj, title=title, description=description, solution=solution, reference=reference, risk=risk, hit=hit, impact_version=impact_version, cnnvd=cnnvd, cnvd=cnvd, cve=cve, bugtraq=bugtraq, origin=origin, find_time=find_time) vuln.save() return vuln
def get_statistics_by_language(language, app_id): """ :param language: :param app_id: :return: """ if not all(( language, app_id, )): raise ParameterIsEmptyException( u'"language, app_id" parameters cannot be empty !') return _get_statistics_obj(language=language, app_id=app_id)
def get_app_by_module_name(name, project_id): """ :param name: :param project_id: :return: """ if not all(( name, project_id, )): raise ParameterIsEmptyException( u'"project_id, name" parameters cannot be empty !') return _get_app_obj(module_name=name, project_id=project_id)
def update_member_obj(**kwargs): """ :param kwargs: :return: """ git_id = kwargs.get('git_id', None) member_id = kwargs.get('member_id', None) name = kwargs.get('name', None) username = kwargs.get('username', None) state = kwargs.get('state', None) web_url = kwargs.get('web_url', None) email = kwargs.get('email', None) gitlab_created_at = kwargs.get('gitlab_created_at', None) if not name: raise ParameterIsEmptyException(u'Member name cannot be empty!') if name and len(name) > 64: name = name[:64] sql_where = {} if git_id: sql_where['git_id'] = int(git_id) if member_id: sql_where['id'] = int(member_id) if not sql_where: raise QueryConditionIsEmptyException(u'Missing "member_id, git_id" key parameters!') close_old_connections() member = MemberInfo.objects.filter(**sql_where).first() if member: if name: member.name = name if username: member.username = username if state: member.state = state if web_url: member.web_url = web_url if email: member.email = email if gitlab_created_at: member.gitlab_created_at = gitlab_created_at member.save() cache.set('{0}:{1}'.format(PROJECT_MEMBER_CACHE[1], member_id), None, 0) cache.set('{0}:{1}'.format(PROJECT_MEMBER_CACHE[2], member.git_id), None, 0) return member else: return None
def get_app_by_branch(name, project_id): """ :param name: :param project_id: :return: """ if not all(( name, project_id, )): raise ParameterIsEmptyException( u'"project_id, name" parameters cannot be empty !') sql_where = {'repo__name': name.strip(), 'project__id': project_id} return ApplicationInfo.objects.filter( **sql_where).order_by('-created_at').first()
def create_profile_obj(**kwargs): """ :param kwargs: :return: """ host_obj = kwargs.get('host_obj', None) name = kwargs.get('name', None) description = kwargs.get('description', '') exclude_dir = kwargs.get('exclude_dir', '') exclude_ext = kwargs.get('exclude_ext', '') exclude_file = kwargs.get('exclude_file', '') exclude_java_package = kwargs.get('exclude_java_package', '') config = kwargs.get('config', '') enable_commit_issue = parse_bool(kwargs.get('enable_commit_issue', False)) enable_auto_ignore = parse_bool(kwargs.get('enable_auto_ignore', False)) task_timeout = parse_int(kwargs.get('task_timeout', 60 * 60 * 2)) if not all((name, )): raise ParameterIsEmptyException(u'"name" parameters cannot be empty !') if name and len(name) > 32: name = name[:32] close_old_connections() profile = ScanProfileInfo( host=host_obj, name=name, description=description, exclude_dir=exclude_dir, exclude_ext=exclude_ext, exclude_file=exclude_file, exclude_java_package=exclude_java_package, config=config, enable_commit_issue=enable_commit_issue, enable_auto_ignore=enable_auto_ignore, task_timeout=task_timeout, ) profile.save() update_client_minor_version(action=1, module=3, description='添加“{0}”扫描模板'.format(name)) return profile
def create_group_obj(**kwargs): """ :param kwargs: :return: """ git_id = kwargs.get('git_id', None) name = kwargs.get('name', '') path = kwargs.get('path', '') description = kwargs.get('description', None) web_url = kwargs.get('web_url', '') full_name = kwargs.get('full_name', '') full_path = kwargs.get('full_path', None) parent_id = kwargs.get('parent_id', None) visibility_level = kwargs.get('visibility_level', 0) group_type = kwargs.get('type', 1) user = kwargs.get('user', None) if not name: raise ParameterIsEmptyException(u'Group name cannot be empty!') if name and len(name) > 128: name = name[:128] if group_type: group_type = int(group_type) close_old_connections() group = GroupInfo( git_id=git_id, name=name, description=description, web_url=web_url, full_name=full_name, path=path, full_path=full_path, parent_id=parent_id, visibility_level=visibility_level, type=group_type, user=user, ) group.save() return group
def create_task_by_project(**kwargs): """ :param kwargs: :return: """ project_id = kwargs.get('project_id', None) branch = kwargs.get('branch', None) group_id = kwargs.get('group_id', None) is_force_scan = kwargs.get('is_force_scan', False) scan_way = kwargs.get('scan_way', 1) version = kwargs.get('version', '') if not all((project_id, branch, group_id)): raise ParameterIsEmptyException('"project_id, branch, group_id" parameters cannot be empty.') project_obj = get_project_by_id(project_id=project_id) repo_obj = get_repository_by_name(name=branch.strip(), project_id=project_obj.id) # FIXME verify project_obj, profile_obj is not None if not repo_obj: raise NotFoundBranchException('"{0}" branch not found.'.format(branch)) app_obj = get_app_by_branch(name=branch, project_id=project_id) if not app_obj: # create application app_obj = create_app_obj( project_obj=project_obj, module_name=branch, app_name=branch, repo_obj=repo_obj, ) task = create_task_obj( app_obj=app_obj, scan_way=scan_way, is_force_scan=is_force_scan, version=version, ) return task
def create_node_obj(**kwargs): """ :param kwargs: :return: """ hostname = kwargs.get('hostname', None) ipv4 = kwargs.get('ipv4', None) ipv6 = kwargs.get('ipv6', None) if not all(( hostname, ipv4, )): raise ParameterIsEmptyException( u'"hostname, ipv4" parameters cannot be empty !') close_old_connections() node = HostInfo(hostname=hostname, ipv4=ipv4, ipv6=ipv6) node.save() return node
def create_periodic_obj(**kwargs): """ :param kwargs: :return: """ name = kwargs.get('name', None) task = kwargs.get('task', 'seecode.services.scan.sched_start') interval = kwargs.get('interval', None) crontab = kwargs.get('crontab', None) args = kwargs.get('args', '') kkwargs = kwargs.get('kkwargs', '') queue = kwargs.get('queue', 'sched') exchange = kwargs.get('exchange', None) priority = kwargs.get('priority', 1) if not all(( name, task, queue, )): raise ParameterIsEmptyException( u'"name, task, queue" parameters cannot be empty !') obj = PeriodicTask( name=name, task=task, interval=interval, crontab=crontab, args=args, kwargs=kkwargs, queue=queue, exchange=exchange, priority=priority, ) obj.save() PeriodicTasks.changed(obj) return obj
def create_changelog_obj(**kwargs): """ 插入历史 :return: """ action = kwargs.get('action', None) module = kwargs.get('module', None) description = kwargs.get('description', '') if not all((action, module, description,)): raise ParameterIsEmptyException(u'"action, module, description" parameters cannot be empty !') close_old_connections() changelog = UpgradeChangelogInfo.objects.filter(action=action, module=module, description=description).first() if not changelog: changelog = UpgradeChangelogInfo( action=action, module=module, description=description, ) changelog.save() return changelog
def create_statistics_obj(**kwargs): """ :param kwargs: :return: """ app = kwargs.get('app_obj', None) language = kwargs.get('language', None) files = kwargs.get('files', None) blank = kwargs.get('blank', None) comment = kwargs.get('comment', None) code = kwargs.get('code', None) if not all((app, language)): raise ParameterIsEmptyException(u'Language name cannot be empty!') close_old_connections() if files: files = int(files) if blank: blank = int(blank) if comment: comment = int(comment) if code: code = int(code) stat = FileStatisticsInfo( app=app, language=language.strip(), files=files, blank=blank, comment=comment, code=code, ) stat.save() return stat
def create_task_obj(**kwargs): """ :param kwargs: :return: """ app_obj = kwargs.get('app_obj', None) group_obj = kwargs.get('group_obj', None) executor_ip = kwargs.get('executor_ip', '') status = kwargs.get('status', 2) is_force_scan = kwargs.get('is_force_scan', False) log_file = kwargs.get('log_file', '') scan_way = kwargs.get('scan_way', 1) error_title = kwargs.get('title', '等待扫描任务被调度') error_reason = kwargs.get('reason', '') version = kwargs.get('version', '') if not all((app_obj, group_obj,)): raise ParameterIsEmptyException(u'"app_obj, group_obj" parameters cannot be empty !') close_old_connections() task = TaskInfo( app=app_obj, group=group_obj, executor_ip=executor_ip, template_name=group_obj.profile.name, log_file=log_file, status=int(status), scan_way=int(scan_way), is_force_scan=is_force_scan, error_title=error_title, error_reason=error_reason, version=version, ) task.save() return task
def create_dependent_obj(**kwargs): """ :param kwargs: :return: """ app_obj = kwargs.get('app_obj', None) name = kwargs.get('name', None) file_name = kwargs.get('file_name', None) full_path = kwargs.get('full_path', '') group_id = kwargs.get('group_id', None) artifact_id = kwargs.get('artifact_id', '') version = kwargs.get('version', '') new_version = kwargs.get('new_version', '') description = kwargs.get('description', '') language = kwargs.get('language', '') if not all((app_obj, name, file_name)): raise ParameterIsEmptyException( u'"app_obj, name, file_name" parameters cannot be empty !') close_old_connections() dep = DependentInfo( app=app_obj, name=name, file_name=file_name, full_path=full_path, group_id=group_id, artifact_id=artifact_id, version=version, new_version=new_version, description=description, language=language, ) dep.save() return dep
def create_project_obj(**kwargs): """ :param kwargs: :return: """ group = kwargs.get('group_obj', None) git_id = kwargs.get('git_id', None) git_created_at = kwargs.get('git_created_at', None) ssh_url_to_repo = kwargs.get('ssh_url_to_repo', None) http_url_to_repo = kwargs.get('http_url_to_repo', None) web_url = kwargs.get('web_url', '') default_branch = kwargs.get('default_branch', '') name = kwargs.get('name', None) path = kwargs.get('path', None) path_with_namespace = kwargs.get('path_with_namespace', '') creator_id = kwargs.get('creator_id', None) description = kwargs.get('description', '') star_count = kwargs.get('star_count', 0) forks_count = kwargs.get('forks_count', 0) open_issues_count = kwargs.get('open_issues_count', 0) visibility_level = kwargs.get('visibility_level', 0) project_type = kwargs.get('type', 1) is_new = kwargs.get('is_new', True) git_last_activity_at = kwargs.get('git_last_activity_at', None) issues_enabled = kwargs.get('issues_enabled', True) user = kwargs.get('user', None) file_hash = kwargs.get('file_hash', None) file_size = kwargs.get('file_size', 0) file_origin_name = kwargs.get('file_origin_name', None) if not name: raise ParameterIsEmptyException(u'Project name cannot be empty!') if git_created_at: git_created_at = git_created_at if creator_id is not None: creator_id = int(creator_id) if star_count is not None: star_count = int(star_count) if forks_count is not None: forks_count = int(forks_count) if open_issues_count is not None: open_issues_count = int(open_issues_count) if visibility_level is not None: visibility_level = int(visibility_level) if project_type is not None: project_type = int(project_type) close_old_connections() project = ProjectInfo(group=group, git_id=git_id, git_created_at=git_created_at, git_last_activity_at=git_last_activity_at, issues_enabled=issues_enabled, ssh_url_to_repo=ssh_url_to_repo, http_url_to_repo=http_url_to_repo, web_url=web_url, default_branch=default_branch, name=name, path=path, path_with_namespace=path_with_namespace, creator_id=creator_id, description=description, star_count=star_count, forks_count=forks_count, open_issues_count=open_issues_count, visibility_level=visibility_level, type=project_type, user=user, is_new=is_new, file_hash=file_hash, file_size=file_size, file_origin_name=file_origin_name) project.save() return project
def create_tactic_obj(**kwargs): """ :param kwargs: :return: """ lang_obj = kwargs.get('lang_obj', None) engine_obj = kwargs.get('engine_obj', None) vuln_obj = kwargs.get('vuln_obj', None) user = kwargs.get('user', None) is_active = kwargs.get('is_active', True) key = kwargs.get('key', '') name = kwargs.get('name', '') description = kwargs.get('description', '') tactic_type = kwargs.get('type', 2) risk = kwargs.get('risk', 3) nature_type = kwargs.get('nature_type', 1) attribution_type = kwargs.get('attribution_type', 1) rule_match_type = kwargs.get('rule_match_type', 1) rule_value = kwargs.get('rule_value', '') rule_regex = kwargs.get('rule_regex', '') rule_regex_flag = kwargs.get('rule_regex_flag', '') component_name = kwargs.get('component_name', '') plugin_name = kwargs.get('plugin_name', '') plugin_path = kwargs.get('plugin_path', '') plugin_content = kwargs.get('plugin_content', '') if not all(( engine_obj, name, key, )): raise ParameterIsEmptyException( u'"engine_obj, name, key" parameters cannot be empty !') if name and len(name) > 255: name = name[:255] # scope r_s = get_tactic_type_scope(tactic_type) r_r = get_tactic_risk_scope(risk) r_t = 0 # (类型权重+风险因素)* (标签系数*N) + (类型权重+风险因素) risk_scope = (r_s + r_r) * r_t + (r_s + r_r) close_old_connections() tactic = TacticInfo(lang=lang_obj, engine=engine_obj, user=user, vuln=vuln_obj, is_active=is_active, key=key, name=name, description=description, type=tactic_type, risk=risk, nature_type=nature_type, attribution_type=attribution_type, rule_match_type=rule_match_type, rule_value=rule_value, rule_regex=rule_regex, component_name=component_name, rule_regex_flag=rule_regex_flag, plugin_name=plugin_name, plugin_module_name=plugin_path, plugin_content=plugin_content, revision=0.01, risk_scope=risk_scope) tactic.save() update_client_minor_version(action=1, module=2, description='添加“{0}”扫描规则。'.format(key)) return tactic