def generate_csrf_token(length=64, valid_seconds=3600, html=False): """Generate a CSRF token.""" now = utils.utcnow() valid_token = None # Clean up expired tokens to prevent junk from building up in the datastore. tokens = data_types.CSRFToken.query( data_types.CSRFToken.user_email == helpers.get_user_email()) tokens_to_delete = [] for token in tokens: if token.expiration_time > now: valid_token = token continue tokens_to_delete.append(token.key) ndb_utils.delete_multi(tokens_to_delete) # Generate a new token. if not valid_token: valid_token = data_types.CSRFToken() valid_token.value = base64.b64encode(os.urandom(length)) valid_token.expiration_time = ( now + datetime.timedelta(seconds=valid_seconds)) valid_token.user_email = helpers.get_user_email() valid_token.put() value = valid_token.value if html: return '<input type="hidden" name="csrf_token" value="%s" />' % value return value
def render(self, path, values=None, status=200): """Write HTML response.""" if values is None: values = {} values['menu_items'] = _MENU_ITEMS values['is_oss_fuzz'] = utils.is_oss_fuzz() values['is_development'] = ( environment.is_running_on_app_engine_development()) values['is_logged_in'] = bool(helpers.get_user_email()) # Only track analytics for non-admin users. values['ga_tracking_id'] = ( local_config.GAEConfig().get('ga_tracking_id') if not auth.is_current_user_admin() else None) if values['is_logged_in']: values['switch_account_url'] = make_login_url(self.request.url) values['logout_url'] = make_logout_url(dest_url=self.request.url) template = _JINJA_ENVIRONMENT.get_template(path) self._add_security_response_headers() self.response.headers['Content-Type'] = 'text/html' self.response.out.write(template.render(values)) self.response.set_status(status)
def post(self): """Handles a post request.""" if not helpers.get_user_email(): raise helpers.AccessDeniedException() result, _ = get_result(self) self.render_json(result)
def handle_exception(self, exception): """Catch exception and format it properly.""" try: status = 500 values = { 'message': str(exception), 'email': helpers.get_user_email(), 'traceDump': traceback.format_exc(), 'status': status, 'type': exception.__class__.__name__ } if isinstance(exception, helpers.EarlyExitException): status = exception.status values = exception.to_dict() # 4XX is not our fault. Therefore, we hide the trace dump and log on # the INFO level. if 400 <= status <= 499: logging.info(json.dumps(values, cls=JsonEncoder)) del values['traceDump'] else: # Other error codes should be logged with the EXCEPTION level. logging.exception(exception) if self.should_render_json(): return self.render_json(values, status) if status in (403, 401): return self.render_forbidden(str(exception)) return self.render('error.html', values, status) except Exception: self.handle_exception_exception()
def post(self, testcase): """Queue redo tasks.""" testcase_tasks = self.request.get('tasks') user_email = helpers.get_user_email() self.redo(testcase, testcase_tasks, user_email) self.render_json(show.get_testcase_detail(testcase))
def get(self): """Handle a GET request.""" # Create a list of externally contributed fuzzers. user_email = helpers.get_user_email() if access.has_access(): # User is an internal user of ClusterFuzz (eg: ClusterFuzz developer). fuzzers_list = ( data_handler.get_all_fuzzer_names_including_children( include_parents=True)) jobs_list = data_handler.get_all_job_type_names() else: # User is an external user of ClusterFuzz (eg: non-Chrome dev who # submitted a fuzzer or someone with a project in OSS-Fuzz). fuzzers_list = external_users.allowed_fuzzers_for_user( user_email, include_from_jobs=True, include_parents=True) if not fuzzers_list: # User doesn't actually have access to any fuzzers. raise helpers.AccessDeniedException() jobs_list = external_users.allowed_jobs_for_user(user_email) fuzzers_list.sort() jobs_list.sort() result = { 'info': { 'fuzzers': fuzzers_list, 'jobs': jobs_list, } } self.render('fuzzer-stats.html', result)
def can_user_access_testcase(testcase): """Checks if the current user can access the testcase.""" if has_access( fuzzer_name=testcase.fuzzer_name, job_type=testcase.job_type, need_privileged_access=testcase.security_flag): return True user_email = helpers.get_user_email() if testcase.uploader_email and testcase.uploader_email == user_email: return True # Allow owners of bugs to see associated test cases and test case groups. issue_id = testcase.bug_information or testcase.group_bug_information if not issue_id: return False itm = issue_tracker_utils.get_issue_tracker_manager(testcase) issue = itm.get_issue(int(issue_id)) if not issue: return False config = db_config.get() if config.relax_testcase_restrictions or _is_domain_allowed(user_email): return (any(utils.emails_equal(user_email, cc) for cc in issue.cc) or utils.emails_equal(user_email, issue.owner) or utils.emails_equal(user_email, issue.reporter)) return utils.emails_equal(user_email, issue.owner)
def render_forbidden(self, message): """Write HTML response for 403.""" login_url = make_login_url(dest_url=request.url) user_email = helpers.get_user_email() if not user_email: return self.redirect(login_url) contact_string = db_config.get_value('contact_string') template_values = { 'message': message, 'user_email': helpers.get_user_email(), 'login_url': login_url, 'switch_account_url': login_url, 'logout_url': make_logout_url(dest_url=request.url), 'contact_string': contact_string, } return self.render('error-403.html', template_values, 403)
def get_result(this): """Get the result.""" params = {k: v for k, v in this.request.iterparams()} page = helpers.cast( this.request.get('page') or 1, int, "'page' is not an int.") query = datastore_query.Query(data_types.TestcaseUploadMetadata) query.order('timestamp', is_desc=True) if not access.has_access(need_privileged_access=True): query.filter('uploader_email', helpers.get_user_email()) params['permission'] = {'uploaderEmail': helpers.get_user_email()} entities, total_pages, total_items, has_more = query.fetch_page( page=page, page_size=PAGE_SIZE, projection=None, more_limit=MORE_LIMIT) items = [] for entity in entities: items.append({ 'timestamp': utils.utc_datetime_to_timestamp(entity.timestamp), 'testcaseId': entity.testcase_id, 'uploaderEmail': entity.uploader_email, 'filename': entity.filename, 'bundled': entity.bundled, 'pathInArchive': entity.path_in_archive, 'status': entity.status }) attach_testcases(items) result = { 'hasMore': has_more, 'items': items, 'page': page, 'pageSize': PAGE_SIZE, 'totalItems': total_items, 'totalPages': total_pages, } return result, params
def get_result(this): """Get the result.""" params = {k: v for k, v in this.request.iterparams()} page = helpers.cast( this.request.get("page") or 1, int, "'page' is not an int.") query = datastore_query.Query(data_types.TestcaseUploadMetadata) query.order("timestamp", is_desc=True) if not access.has_access(need_privileged_access=True): query.filter("uploader_email", helpers.get_user_email()) params["permission"] = {"uploaderEmail": helpers.get_user_email()} entities, total_pages, total_items, has_more = query.fetch_page( page=page, page_size=PAGE_SIZE, projection=None, more_limit=MORE_LIMIT) items = [] for entity in entities: items.append({ "timestamp": utils.utc_datetime_to_timestamp(entity.timestamp), "testcaseId": entity.testcase_id, "uploaderEmail": entity.uploader_email, "filename": entity.filename, "bundled": entity.bundled, "pathInArchive": entity.path_in_archive, "status": entity.status, }) attach_testcases(items) result = { "hasMore": has_more, "items": items, "page": page, "pageSize": PAGE_SIZE, "totalItems": total_items, "totalPages": total_pages, } return result, params
def get(self): """Handles get request.""" email = helpers.get_user_email() if not email: raise helpers.AccessDeniedException() is_privileged_or_domain_user = access.has_access( need_privileged_access=False) if is_privileged_or_domain_user or _is_uploader_allowed(email): # Privileged, domain and upload users can see all job and fuzzer names. allowed_jobs = data_handler.get_all_job_type_names() allowed_fuzzers = data_handler.get_all_fuzzer_names_including_children( include_parents=True) else: # Check if this is an external user with access to certain fuzzers/jobs. allowed_jobs = external_users.allowed_jobs_for_user(email) allowed_fuzzers = external_users.allowed_fuzzers_for_user( email, include_from_jobs=True) if not allowed_fuzzers and not allowed_jobs: raise helpers.AccessDeniedException() has_issue_tracker = bool(data_handler.get_issue_tracker_name()) result, params = get_result() return self.render( 'upload.html', { 'fieldValues': { 'blackboxFuzzers': filter_blackbox_fuzzers(allowed_fuzzers), 'jobs': allowed_jobs, 'libfuzzerTargets': filter_target_names(allowed_fuzzers, 'libFuzzer'), 'aflTargets': filter_target_names(allowed_fuzzers, 'afl'), 'honggfuzzTargets': filter_target_names(allowed_fuzzers, 'honggfuzz'), 'isChromium': utils.is_chromium(), 'sandboxedJobs': data_types.INTERNAL_SANDBOXED_JOB_TYPES, 'csrfToken': form.generate_csrf_token(), 'isExternalUser': not is_privileged_or_domain_user, 'uploadInfo': gcs.prepare_blob_upload()._asdict(), 'hasIssueTracker': has_issue_tracker, }, 'params': params, 'result': result })
def get_permission_names(entity_kind): """Get scoped fuzzer names.""" # pylint: disable=protected-access permissions = external_users._get_permissions_query_for_user( helpers.get_user_email(), entity_kind) names = [] for permission in permissions: suffix = '*' if permission.is_prefix else '' names.append(permission.entity_name + suffix) return names
def render_forbidden(self, message): """Write HTML response for 403.""" contact_string = db_config.get_value('contact_string') template_values = { 'message': message, 'user_email': helpers.get_user_email(), 'login_url': users.create_login_url(dest_url=self.request.url), 'switch_account_url': make_switch_account_url(self.request.url), 'logout_url': users.create_logout_url(dest_url=self.request.url), 'contact_string': contact_string, } self.render('error-403.html', template_values, 403)
def get(self): """Handles get request.""" email = helpers.get_user_email() if not email: raise helpers.AccessDeniedException() is_privileged_or_domain_user = access.has_access( need_privileged_access=False) if is_privileged_or_domain_user or _is_uploader_allowed(email): # Privileged, domain and upload users can see all job and fuzzer names. allowed_jobs = data_handler.get_all_job_type_names() allowed_fuzzers = data_handler.get_all_fuzzer_names_including_children( include_parents=True) else: # Check if this is an external user with access to certain fuzzers/jobs. allowed_jobs = external_users.allowed_jobs_for_user(email) allowed_fuzzers = external_users.allowed_fuzzers_for_user( email, include_from_jobs=True) if not allowed_fuzzers and not allowed_jobs: raise helpers.AccessDeniedException() has_issue_tracker = bool(data_handler.get_issue_tracker_name()) result, params = get_result(self) self.render( "upload.html", { "fieldValues": { "jobs": allowed_jobs, "libfuzzerTargets": filter_target_names(allowed_fuzzers, "libFuzzer"), "aflTargets": filter_target_names(allowed_fuzzers, "afl"), "isChromium": utils.is_chromium(), "sandboxedJobs": data_types.INTERNAL_SANDBOXED_JOB_TYPES, "csrfToken": form.generate_csrf_token(), "isExternalUser": not is_privileged_or_domain_user, "uploadInfo": gcs.prepare_blob_upload()._asdict(), "hasIssueTracker": has_issue_tracker, }, "params": params, "result": result, }, )
def get(self, extra=None): """Handle a GET request.""" if not access.has_access(): # User is an external user of ClusterFuzz (eg: non-Chrome dev who # submitted a fuzzer or someone with a project in OSS-Fuzz). user_email = helpers.get_user_email() fuzzers_list = external_users.allowed_fuzzers_for_user( user_email, include_from_jobs=True, include_parents=True) if not fuzzers_list: # User doesn't actually have access to any fuzzers. raise helpers.AccessDeniedException( "You don't have access to any fuzzers.") return self.render('fuzzer-stats.html', {})
def get_user_job_type(): """Return the job_type that is assigned to the current user. None means one can access any job type. You might want to invoke get_access(..) with the job type afterward.""" email = helpers.get_user_email() privileged_user_emails = (db_config.get_value('privileged_users') or '').splitlines() for privileged_user_email in privileged_user_emails: if ';' in privileged_user_email: tokens = privileged_user_email.split(';') privileged_user_real_email = tokens[0] privileged_user_job_type = tokens[1] if utils.emails_equal(email, privileged_user_real_email): return privileged_user_job_type return None
def can_user_access_testcase(testcase): """Checks if the current user can access the testcase.""" config = db_config.get() need_privileged_access = ( testcase.security_flag and not config.relax_security_bug_restrictions) if has_access( fuzzer_name=testcase.fuzzer_name, job_type=testcase.job_type, need_privileged_access=need_privileged_access): return True user_email = helpers.get_user_email() if testcase.uploader_email and testcase.uploader_email == user_email: return True # Allow owners of bugs to see associated test cases and test case groups. issue_id = testcase.bug_information or testcase.group_bug_information if not issue_id: return False itm = issue_tracker_utils.get_issue_tracker_manager(testcase) issue_id = int(issue_id) associated_issue = itm.get_issue(issue_id) if not associated_issue: return False # Look at both associated issue and original issue (if the associated one # is a duplicate of the original issue). issues_to_check = [associated_issue] if associated_issue.merged_into: original_issue = itm.get_original_issue(issue_id) if original_issue: issues_to_check.append(original_issue) relaxed_restrictions = ( config.relax_testcase_restrictions or _is_domain_allowed(user_email)) for issue in issues_to_check: if relaxed_restrictions: if (any(utils.emails_equal(user_email, cc) for cc in issue.cc) or utils.emails_equal(user_email, issue.owner) or utils.emails_equal(user_email, issue.reporter)): return True elif utils.emails_equal(user_email, issue.owner): return True return False
def check_access_and_get_testcase(testcase_id): """Check the failed attempt count and get the testcase.""" if not helpers.get_user_email(): raise helpers.UnauthorizedException() if not testcase_id: raise helpers.EarlyExitException('No test case specified!', 404) try: testcase = data_handler.get_testcase_by_id(testcase_id) except errors.InvalidTestcaseError: raise helpers.EarlyExitException('Invalid test case!', 404) if not can_user_access_testcase(testcase): raise helpers.AccessDeniedException() return testcase
def _check_user_access_and_get_job_filter(self, fuzzer, job): """Check whether the current user has access to stats for the fuzzer or job. Returns a job filter that should be applied to the query.""" access_by_fuzzer_or_job = access.has_access(fuzzer_name=fuzzer, job_type=job) if access_by_fuzzer_or_job: # User has full access to the fuzzer, or the specified job. # None means no filters => all jobs. return _get_filter_from_job(job) if not job: # Job not specified and user doesn't have full access to the fuzzer. Check # if the user has any allowed jobs and use that as a filter. allowed_jobs = external_users.allowed_jobs_for_user( helpers.get_user_email()) if allowed_jobs: return allowed_jobs raise helpers.AccessDeniedException()
def get(self): """Handle a GET request.""" project = request.get('project') if access.has_access(): # User is an internal user of ClusterFuzz (eg: ClusterFuzz developer). # Show all projects in the list, since this allows user to pick another # project as needed. projects_list = data_handler.get_all_project_names() # Filter fuzzers and job list if a project is provided. fuzzers_list = ( data_handler.get_all_fuzzer_names_including_children( include_parents=True, project=project)) jobs_list = data_handler.get_all_job_type_names(project=project) else: # User is an external user of ClusterFuzz (eg: non-Chrome dev who # submitted a fuzzer or someone with a project in OSS-Fuzz). user_email = helpers.get_user_email() # TODO(aarya): Filter fuzzer and job if |project| is provided. fuzzers_list = sorted( external_users.allowed_fuzzers_for_user(user_email, include_from_jobs=True, include_parents=True)) if not fuzzers_list: # User doesn't actually have access to any fuzzers. raise helpers.AccessDeniedException( "You don't have access to any fuzzers.") jobs_list = sorted( external_users.allowed_jobs_for_user(user_email)) projects_list = sorted( {data_handler.get_project_name(job) for job in jobs_list}) result = { 'projects': projects_list, 'fuzzers': fuzzers_list, 'jobs': jobs_list, } return self.render_json(result)
def get_scope(): """Get the scope object for the user.""" user_email = helpers.get_user_email() is_privileged = access.has_access(need_privileged_access=True) everything = (is_privileged or access.has_access()) # pylint: disable=protected-access job_types = external_users._allowed_entities_for_user( user_email, data_types.PermissionEntityKind.JOB) allowed_job_type = access.get_user_job_type() if allowed_job_type: job_types.append(allowed_job_type) # pylint: disable=protected-access fuzzer_names = external_users._allowed_entities_for_user( user_email, data_types.PermissionEntityKind.FUZZER) return Scope(everything, is_privileged, job_types, fuzzer_names, allowed_job_type)
def update_issue(testcase, issue_id, needs_summary_update): """Associate (or update) an existing issue with the testcase.""" issue_id = helpers.cast(issue_id, int, 'Issue ID (%s) is not a number!' % issue_id) itm = helpers.get_issue_tracker_manager(testcase) issue = helpers.get_or_exit( lambda: itm.get_issue(issue_id), 'Issue (id=%d) is not found!' % issue_id, 'Failed to get the issue (id=%s).' % issue_id, Exception) if not issue.open: raise helpers.EarlyExitException( ('The issue (%d) is already closed and further updates are not' ' allowed. Please file a new issue instead!') % issue_id, 400) # Create issue parameters. issue.comment = data_handler.get_issue_description( testcase, helpers.get_user_email()) issue_summary = data_handler.get_issue_summary(testcase) # NULL states leads to unhelpful summaries, so do not update in that case. if needs_summary_update and testcase.crash_state != 'NULL': issue.summary = issue_summary # Add label on memory tool used. issue_filer.add_memory_tool_label_if_needed(issue, testcase) # Add view restrictions for internal job types. issue_filer.add_view_restrictions_if_needed(issue, testcase) # Don't enforce security severity label on an existing issue. itm.save(issue) testcase.bug_information = str(issue_id) testcase.put() data_handler.update_group_bug(testcase.group_id) helpers.log('Updated issue %sd' % issue_id, helpers.MODIFY_OPERATION)
def update_issue(testcase, issue_id, needs_summary_update): """Associate (or update) an existing issue with the testcase.""" issue_id = helpers.cast(issue_id, int, 'Issue ID (%s) is not a number!' % issue_id) issue_tracker = helpers.get_issue_tracker_for_testcase(testcase) issue = helpers.get_or_exit( lambda: issue_tracker.get_issue(issue_id), 'Issue (id=%d) is not found!' % issue_id, 'Failed to get the issue (id=%s).' % issue_id, Exception) if not issue.is_open: raise helpers.EarlyExitException( ('The issue (%d) is already closed and further updates are not' ' allowed. Please file a new issue instead!') % issue_id, 400) if not testcase.is_crash(): raise helpers.EarlyExitException( 'This is not a crash testcase, so issue update is not applicable.', 400) issue_comment = data_handler.get_issue_description( testcase, helpers.get_user_email()) if needs_summary_update: issue.title = data_handler.get_issue_summary(testcase) policy = issue_tracker_policy.get(issue_tracker.project) properties = policy.get_existing_issue_properties() for label in properties.labels: for result in issue_filer.apply_substitutions( policy, label, testcase): issue.labels.add(result) issue.save(new_comment=issue_comment) testcase.bug_information = str(issue_id) testcase.put() data_handler.update_group_bug(testcase.group_id) helpers.log('Updated issue %sd' % issue_id, helpers.MODIFY_OPERATION)
def post(self): """Handle a post request.""" name = self.request.get('name') if not name: raise helpers.EarlyExitException('Please give this corpus a name!', 400) if not data_types.DataBundle.VALID_NAME_REGEX.match(name): raise helpers.EarlyExitException( 'Name can only contain letters, numbers, dashes and underscores.', 400) user_email = helpers.get_user_email() bucket_name = data_handler.get_data_bundle_bucket_name(name) bucket_url = data_handler.get_data_bundle_bucket_url(name) is_local = not self.request.get('nfs', False) if not data_handler.create_data_bundle_bucket_and_iams( name, [user_email]): raise helpers.EarlyExitException( 'Failed to create bucket %s.' % bucket_name, 400) data_bundle = data_types.DataBundle.query( data_types.DataBundle.name == name).get() if not data_bundle: data_bundle = data_types.DataBundle() data_bundle.name = name data_bundle.bucket_name = bucket_name data_bundle.is_local = is_local data_bundle.put() template_values = { 'title': 'Success', 'message': ('Upload data to the corpus using: ' 'gsutil -d -m rsync -r <local_corpus_directory> %s' % bucket_url), } self.render('message.html', template_values)
def create_issue(testcase, severity, cc_me): """Create an issue.""" issue_tracker = helpers.get_issue_tracker_for_testcase(testcase) user_email = helpers.get_user_email() if severity is not None: severity = helpers.cast( severity, int, 'Invalid value for security severity (%s).' % severity) additional_ccs = [] if cc_me == 'true': additional_ccs.append(user_email) issue_id = issue_filer.file_issue( testcase, issue_tracker, security_severity=severity, user_email=user_email, additional_ccs=additional_ccs) if not issue_id: raise helpers.EarlyExitException('Unable to create new issue.', 500)
def get_results(): """Return results.""" is_user = access.has_access() user_email = helpers.get_user_email() external_jobs = external_users.allowed_jobs_for_user(user_email) is_external_user = not is_user and external_jobs if not is_user and not is_external_user: raise helpers.AccessDeniedException() if is_user: projects = _get_all_project_results() else: projects = _get_project_results_for_external_user(external_jobs) results = { 'info': { 'projects': projects, 'is_internal_user': is_user, }, } return results
def handle_exception(self, exception, _): """Catch exception and format it properly.""" try: status = 500 values = { 'message': exception.message, 'email': helpers.get_user_email(), 'traceDump': traceback.format_exc(), 'status': status, 'type': exception.__class__.__name__ } if isinstance(exception, helpers.EarlyExitException): status = exception.status values = exception.to_dict() values['params'] = self.request.params.dict_of_lists() # 4XX is not our fault. Therefore, we hide the trace dump and log on # the INFO level. if status >= 400 and status <= 499: logging.info(json.dumps(values, cls=JsonEncoder)) del values['traceDump'] else: # Other error codes should be logged with the EXCEPTION level. logging.exception(exception) if helpers.should_render_json( self.request.headers.get('accept', ''), self.response.headers.get('Content-Type')): self.render_json(values, status) else: if status == 403 or status == 401: self.render_forbidden(exception.message) else: self.render('error.html', values, status) except Exception: self.handle_exception_exception()
def do_post(self): """Upload a testcase.""" testcase_id = self.request.get('testcaseId') uploaded_file = self.get_upload() if testcase_id and not uploaded_file: testcase = helpers.get_testcase(testcase_id) if not access.can_user_access_testcase(testcase): raise helpers.AccessDeniedException() # Use minimized testcase for upload (if available). key = (testcase.minimized_keys if testcase.minimized_keys and testcase.minimized_keys != 'NA' else testcase.fuzzed_keys) uploaded_file = blobs.get_blob_info(key) # Extract filename part from blob. uploaded_file.filename = os.path.basename( uploaded_file.filename.replace('\\', os.sep)) job_type = self.request.get('job') if not job_type: raise helpers.EarlyExitException('Missing job name.', 400) if (not data_types.Job.VALID_NAME_REGEX.match(job_type) or not data_types.Job.query( data_types.Job.name == job_type).get()): raise helpers.EarlyExitException('Invalid job name.', 400) fuzzer_name = '' job_type_lowercase = job_type.lower() if 'libfuzzer' in job_type_lowercase: fuzzer_name = 'libFuzzer' elif 'afl' in job_type_lowercase: fuzzer_name = 'afl' target_name = self.request.get('target') if not fuzzer_name and target_name: raise helpers.EarlyExitException( 'Target name is not applicable to non-engine jobs (AFL, libFuzzer).', 400) if fuzzer_name and not target_name: raise helpers.EarlyExitException( 'Missing target name for engine job (AFL, libFuzzer).', 400) if (target_name and not data_types.Fuzzer.VALID_NAME_REGEX.match(target_name)): raise helpers.EarlyExitException('Invalid target name.', 400) fully_qualified_fuzzer_name = '' if fuzzer_name and target_name: fully_qualified_fuzzer_name, target_name = find_fuzz_target( fuzzer_name, target_name, job_type) if not fully_qualified_fuzzer_name: raise helpers.EarlyExitException('Target does not exist.', 400) if not access.has_access(need_privileged_access=False, job_type=job_type, fuzzer_name=(fully_qualified_fuzzer_name or fuzzer_name)): raise helpers.AccessDeniedException() multiple_testcases = bool(self.request.get('multiple')) http_flag = bool(self.request.get('http')) high_end_job = bool(self.request.get('highEnd')) bug_information = self.request.get('issue') crash_revision = self.request.get('revision') timeout = self.request.get('timeout') retries = self.request.get('retries') bug_summary_update_flag = bool(self.request.get('updateIssue')) additional_arguments = self.request.get('args') app_launch_command = self.request.get('cmd') platform_id = self.request.get('platform') testcase_metadata = self.request.get('metadata') if testcase_metadata: try: testcase_metadata = json.loads(testcase_metadata) if not isinstance(testcase_metadata, dict): raise helpers.EarlyExitException( 'Metadata is not a JSON object.', 400) except Exception: raise helpers.EarlyExitException('Invalid metadata JSON.', 400) archive_state = 0 bundled = False file_path_input = '' email = helpers.get_user_email() # If we have a AFL or libFuzzer target, use that for arguments. # Launch command looks like # python launcher.py {testcase_path} {target_name} if target_name: additional_arguments = '%%TESTCASE%% %s' % target_name # Certain modifications such as app launch command, issue updates are only # allowed for privileged users. privileged_user = access.has_access(need_privileged_access=True) if not privileged_user: if bug_information or bug_summary_update_flag: raise helpers.EarlyExitException( 'You are not privileged to update existing issues.', 400) need_privileged_access = utils.string_is_true( data_handler.get_value_from_job_definition( job_type, 'PRIVILEGED_ACCESS')) if need_privileged_access: raise helpers.EarlyExitException( 'You are not privileged to run this job type.', 400) if app_launch_command: raise helpers.EarlyExitException( 'You are not privileged to run arbitary launch commands.', 400) if testcase_metadata: raise helpers.EarlyExitException( 'You are not privileged to set testcase metadata.', 400) if crash_revision and crash_revision.isdigit(): crash_revision = int(crash_revision) else: crash_revision = 0 if bug_information and not bug_information.isdigit(): raise helpers.EarlyExitException('Bug is not a number.', 400) if not timeout: timeout = 0 elif not timeout.isdigit() or timeout == '0': raise helpers.EarlyExitException( 'Testcase timeout must be a number greater than 0.', 400) else: timeout = int(timeout) if timeout > 120: raise helpers.EarlyExitException( 'Testcase timeout may not be greater than 120 seconds.', 400) if retries: if retries.isdigit(): retries = int(retries) else: retries = None if retries is None or retries > MAX_RETRIES: raise helpers.EarlyExitException( 'Testcase retries must be a number less than %d.' % MAX_RETRIES, 400) else: retries = None try: gestures = ast.literal_eval(self.request.get('gestures')) except: gestures = [] if not gestures: gestures = [] job_queue = tasks.queue_for_job(job_type, is_high_end=high_end_job) if uploaded_file is not None: filename = ''.join([ x for x in uploaded_file.filename if x not in ' ;/?:@&=+$,{}|<>()\\' ]) key = str(uploaded_file.key()) if archive.is_archive(filename): archive_state = data_types.ArchiveStatus.FUZZED if archive_state: if multiple_testcases: if testcase_metadata: raise helpers.EarlyExitException( 'Testcase metadata not supported with multiple testcases.', 400) # Create a job to unpack an archive. metadata = data_types.BundledArchiveMetadata() metadata.blobstore_key = key metadata.timeout = timeout metadata.job_queue = job_queue metadata.job_type = job_type metadata.http_flag = http_flag metadata.archive_filename = filename metadata.uploader_email = email metadata.gestures = gestures metadata.crash_revision = crash_revision metadata.additional_arguments = additional_arguments metadata.bug_information = bug_information metadata.platform_id = platform_id metadata.app_launch_command = app_launch_command metadata.fuzzer_name = fuzzer_name metadata.overridden_fuzzer_name = fully_qualified_fuzzer_name metadata.fuzzer_binary_name = target_name metadata.put() tasks.add_task('unpack', str(metadata.key.id()), job_type, queue=tasks.queue_for_job(job_type)) # Create a testcase metadata object to show the user their upload. upload_metadata = data_types.TestcaseUploadMetadata() upload_metadata.timestamp = datetime.datetime.utcnow() upload_metadata.filename = filename upload_metadata.blobstore_key = key upload_metadata.original_blobstore_key = key upload_metadata.status = 'Pending' upload_metadata.bundled = True upload_metadata.uploader_email = email upload_metadata.retries = retries upload_metadata.bug_summary_update_flag = bug_summary_update_flag upload_metadata.put() helpers.log('Uploaded multiple testcases.', helpers.VIEW_OPERATION) self.render_json({'multiple': True}) return file_path_input = guess_input_file(uploaded_file, filename) if not file_path_input: raise helpers.EarlyExitException(( "Unable to detect which file to launch. The main file\'s name " 'must contain either of %s.' % str(RUN_FILE_PATTERNS)), 400) else: raise helpers.EarlyExitException('Please select a file to upload.', 400) testcase_id = data_handler.create_user_uploaded_testcase( key, key, archive_state, filename, file_path_input, timeout, job_type, job_queue, http_flag, gestures, additional_arguments, bug_information, crash_revision, email, platform_id, app_launch_command, fuzzer_name, fully_qualified_fuzzer_name, target_name, bundled, retries, bug_summary_update_flag, additional_metadata=testcase_metadata) testcase = data_handler.get_testcase_by_id(testcase_id) issue = issue_tracker_utils.get_issue_for_testcase(testcase) if issue: report_url = data_handler.TESTCASE_REPORT_URL.format( domain=data_handler.get_domain(), testcase_id=testcase_id) comment = ('ClusterFuzz is analyzing your testcase. ' 'Developers can follow the progress at %s.' % report_url) issue.save(new_comment=comment) helpers.log('Uploaded testcase %s' % testcase_id, helpers.VIEW_OPERATION) self.render_json({'id': '%s' % testcase_id})
def test_get_user_email_failure(self): """Ensure it gets empty string when a user is invalid.""" self.mock.get_current_user.side_effect = Exception() self.assertEqual(helpers.get_user_email(), '')
def test_get_user_email_success(self): """Ensure it gets the email when a user is valid.""" self.mock.get_current_user.return_value = (auth.User('*****@*****.**')) self.assertEqual(helpers.get_user_email(), '*****@*****.**')