def get(self, email_validation_code): with backend.transaction(): try: user = backend.get( User, {'email_validation_code': email_validation_code} ) if not user.get('new_email'): if not user.get('email'): return {'message': 'no valid e-mail set'}, 403 backend.update(user, {'email_validated': True}, unset_fields=['email_validation_code']) settings.hooks.call("user.email.validated", user) logger.warning("Hooray, user {0} has verified his email address".format(user.name)) else: if backend.filter(User, {'email': user.new_email}): return {'message': 'A user with this e-mail already exists'}, 403 old_email = user.email new_email = user.new_email backend.update(user, {'email_validated': True, 'email': user.new_email}, unset_fields=['new_email', 'email_validation_code']) settings.hooks.call("user.email.updated", user) except User.DoesNotExist: return {'message': 'Unknown validation code'}, 404 return {'message': 'success'}, 200
def auto_tag(project): """ First step of the analysis for Git projects. Determines if automatic tags have already been added and, if not adds them from the requirements.txt file, if one exists in the repository. :param project: project to auto tag :return: """ logger.debug("Adding automatic tags to {}".format(project.pk)) repository = project.git.eager.repository # auto tag the project (but only once) if project.get('automatic_tags_added'): return default_branch = project.git.get_default_branch() if default_branch is None: return tags = extract_tags_from_requirements_txt(repository, default_branch) with backend.transaction(): for tag_name in tags: try: tag = backend.get(Tag, {'name': tag_name}) except Tag.DoesNotExist: tag = Tag({'name': tag_name}) backend.save(tag) project.tags.append(tag) project.automatic_tags_added = True with backend.transaction(): backend.update(project, ['automatic_tags_added'])
def post(self): form = PasswordResetRequestForm(request.form) if form.validate(): with backend.transaction(): try: user = backend.get(User, {'email': form.email.data}) if 'email_validated' in user and user.email_validated: backend.update( user, {'password_reset_code': uuid.uuid4().hex} ) reset_url = "{}{}/user/password-reset?reset_code={}".format( settings.get('url'), settings.get('frontend.url'), user.password_reset_code ) # Reset Email send_mail( email_to=form.email.data, template="reset_password", template_context={ "user_name": user.name, "reset_url": reset_url } ) return {'message': 'Email with reset link was sent.'}, 200 return {'message': "Your email is not validated, so we cannot send you " "a password-reset token."}, 403 except User.DoesNotExist: return {'message': 'Unknown user'}, 404 return {'message': 'Invalid data', 'errors': form.errors}, 403
def generate_key_pair(project): """ Generates a new SSH key pair for the given remote in the given project. Effect: saves the public and private keys on the appropriate remote in the project object :param project: project to generate the key pair for """ tempdir = tempfile.mkdtemp() try: subprocess.call([ "ssh-keygen", "-t", "rsa", "-q", "-f", "testkey", "-N", "", "-C", project.pk ], cwd=tempdir) with open(os.path.join(tempdir, "testkey"), 'r') as private_key_file: private_key = private_key_file.read() with open(os.path.join(tempdir, "testkey.pub"), 'r') as public_key_file: public_key = public_key_file.read() with backend.transaction(): backend.update(project.git, { 'private_key': private_key, 'public_key': public_key }) finally: shutil.rmtree(tempdir)
def post(self, password_reset_code): form = PasswordResetForm(request.form) if form.validate(): with backend.transaction(): try: user = backend.get( User, {'password_reset_code': password_reset_code} ) except User.DoesNotExist: return {'message': 'Unknown user'}, 404 user.set_password(form.password.data) backend.update(user, ['password', 'password_set'], unset_fields=['password_reset_code']) access_token = user.get_access_token() backend.save(access_token) send_mail( email_to=user.email, template="password_reset_successful", template_context={ "user_name": user.name } ) return {'message': 'success'}, 200 return ({'message': 'Invalid data', 'errors': form.errors}, 403)
def delete(self, project_id): with backend.transaction(): backend.update( request.project, {'reset': True, 'reset_requested_at': datetime.datetime.now()}) return ({'message': 'Success, project scheduled for reset. Please be patient.'}, 200)
def delete(self, project_id, issue_class_id): """ Delete an issue class from a project. """ with backend.transaction(): project_issue_class = self._get_project_issue_class() backend.update(project_issue_class, {'enabled': False}) return {'message': 'success'}, 200
def fetch_remote(project, branch=None, git_config=None, git_credentials=None, report_error=True): """ Fetches the remote for the given project :param project: :param git_config: :param branch: :param git_credentials: :param report_error: :return: """ repository = project.git.eager.repository remote_name = "origin" # do not fetch the directory directly but instead use a copy. # This mitigates the risk of being unable to serve a simultaneous request # which relies on the git repository, f.e. to get a list of branches. tmp_repo_path = repository.path + "_temp" if os.path.exists(tmp_repo_path): # we should have an exclusive task log on the project, so deleting the temporary copy is ok. shutil.rmtree(tmp_repo_path) try: shutil.copytree(repository.path, tmp_repo_path) tmp_repository = Repository(tmp_repo_path) tmp_repository.update_remote_url(remote_name, project.git.url) # actually fetch the repository with tempfile.NamedTemporaryFile(delete=False) as tf: tf.write(project.git.private_key) tf.close() rc = tmp_repository.fetch(remote_name, branch=branch, ssh_identity_file=tf.name, git_config=git_config, git_credentials=git_credentials) # move the repository back if the fetch was successful if rc == 0: shutil.rmtree(repository.path) shutil.move(tmp_repo_path, repository.path) finally: if os.path.isdir(tmp_repo_path): shutil.rmtree(tmp_repo_path) with backend.transaction(): backend.update( project, { 'fetch_status': 'failed' if rc != 0 else 'succeeded', 'fetched_at': datetime.datetime.utcnow(), 'fetch_error': '' if rc == 0 else repository.stderr }) if rc != 0: raise IOError("Cannot fetch git repository!")
def delete(self): """ Marks a user for deletion (will be done in a backend task) """ logger.warning("Ouch, we lost user {0}".format(request.user.name)) with backend.transaction(): backend.update(request.user, {'delete': True}) return ({'message': 'We\'re sad to see you leave! Your account will be fully deleted within a few minutes.', 'user_id': request.user.pk}, 200)
def update_analysis_status(project, status, extra=None): """ Updates the analysis status of the given project to the given status. :param project: project to update the analysis status for :param status: new analysis status :param extra: dictionary with extra data to update """ extra = extra if extra is not None else {} with backend.transaction(): data = extra.copy() data['analyzed_at'] = datetime.datetime.now() data['analysis_status'] = status backend.update(project, data)
def _schedule_analysis(project_id, analysis_priority=Project.AnalysisPriority.high): if not (request.project.get('analyze') and request.project.get('analysis_priority', Project.AnalysisPriority.low) >= analysis_priority): with backend.transaction(): backend.update(request.project, {'analyze': True, 'analysis_requested_at': datetime.datetime.now(), 'analysis_priority': analysis_priority}) return ({'message': 'Success, project scheduled for analysis. Please be patient.'}, 200) else: return ({'message': 'Project was already scheduled for analysis. Please be patient.'}, 200)
def post(self, project_id, issue_class_id): """ Add a new issue class to a project """ with backend.transaction(): project_issue_class = self._get_project_issue_class() if project_issue_class.pk: backend.update(project_issue_class, {'enabled': True}) else: project_issue_class.enabled = True backend.save(project_issue_class) return {'message': 'success'}, 201
def reset_pending_project(): try: pending_project = backend.filter(Project, { 'reset': True }).sort('reset_requested_at', 1).limit(1)[0] except IndexError: logger.debug("No projects left to reset....") return with backend.transaction(): backend.update(pending_project, {'reset_requested_at': datetime.datetime.now()}) return reset_project(pending_project.pk, task_id=reset_pending_project.request.id)
def put(self, project_id, issue_id): form = IssueStatusForm(request.form) if not form.validate(): return { 'message' : 'Please correct the errors mentioned below.', 'errors' : form.errors }, 400 with backend.transaction(): backend.update(request.issue,form.data) return {'message' : 'success'}, 200
def put(self): """ Enable or disable email notifications :return: message with status, status code """ user = request.user form = EmailNotificationsForm(request.form) if form.validate(): with backend.transaction(): email_settings = user.get('email_settings', {}) email_settings.update({'notifications': form.email_notifications_enabled.data}) user.email_settings = email_settings backend.update(user, ["email_settings"]) return {'message': "success"}, 200 return {'message': "error", 'errors': form.errors}, 400
def update_project_statistics(project): """ After analysis hook. Updates the project statistics :param project: project that is being analyzed """ checkmate_settings = settings.checkmate_settings update_stats_command = UpdateStatsCommand(project, checkmate_settings, backend) update_stats_command.run() if 'stats' in project: with backend.transaction(): backend.update(project, ['stats'])
def put(self, project_id): form = EditProjectForm(request.form) project = request.project if project.git is None: return {'message': 'not a Git project!'}, 400 if not form.validate(): return ({ 'message': 'Please correct the errors mentioned below.', 'errors': form.errors }, 400) with backend.transaction(): backend.update(project.git, form.data) return {'message': 'success'}, 200
def put(self): form = UserProfileForm(request.form) if not form.validate(): return {u'errors': form.errors}, 403 user = request.user data = form.data email = data.get(u'email') if email: if (user.get('email_change_requested_at') and datetime.datetime.utcnow() - user.email_change_requested_at < datetime.timedelta(minutes=30)): return {'message': "Please wait at least 30 minutes before requesting another e-mail change."}, 403 with backend.transaction(): backend.update(user, {'new_email': email, 'email_validation_code': uuid.uuid4().hex, 'email_change_requested_at': datetime.datetime.utcnow()}) activation_url = "{}{}/user/validate/{}".format( settings.get('url'), settings.get('frontend.url'), request.user.email_validation_code ) # activate email send_mail( email_to=user.new_email, template="verify_email", template_context={ "user_name": user.name, "activation_url": activation_url } ) email_settings = data.get(u'email_settings') with backend.transaction(): if email_settings: email_settings = user.get('email_settings', {}) email_settings.update(data[u'email_settings']) user.email_settings = email_settings backend.update(user, ['email_settings']) return {'user': self.export(user)}, 200
def analyze_pending_project(): """ Get all projects that are marked for analysis and sort them by priority and request date. Then go through the list and check if the project has been recently analyzed, if not, analyze the first project. """ logger.debug("Retrieving projects pending analysis...") pending_projects_query = { '$and': [{ 'analyze': True }, { '$or': [{ 'deleted': { '$exists': False } }, { 'deleted': False }] }] } pending_projects = backend.filter(Project, pending_projects_query) pending_projects.sort([['analysis_priority', -1], ['analysis_requested_at', 1]]).limit(100) timestamp = datetime.datetime.now() max_allowed_runtime = datetime.timedelta(minutes=120) for pending_project in pending_projects: # skip projects currently being analyzed unless the analysis has been running for too long if (pending_project.analysis_status == pending_project.AnalysisStatus.in_progress and timestamp - pending_project.analyzed_at < max_allowed_runtime): continue # move the project back in the queue with backend.transaction(): backend.update(pending_project, {'analysis_requested_at': datetime.datetime.now()}) analyze_project(pending_project.pk, task_id=analyze_pending_project.request.id) break
def put(self): form = ChangePasswordForm(request.form) if not form.validate(): return ({u'errors': form.errors}, 400) password = form.password.data request.user.set_password(password) if request.user.email_validated: send_mail( email_to=request.user.email, template="change_password", template_context={ "user_name": request.user.name } ) with backend.transaction(): backend.update(request.user, ['password', 'password_set']) return ({u'user': self.export(request.user)}, 200)
def reset_project(project_id, task_id=None): if not task_id: task_id = reset_project.request.id try: project = backend.get(Project, {'pk': project_id}) except Project.DoesNotExist: logger.warning( "Project %s does not exist and thus cannot be deleted." % project_id) return try: with ExclusiveTask( backend, { 'project.pk': project.pk, 'type': { '$in': ['analysis', 'reset', 'delete'] } }, { 'type': 'reset', 'project': project }, task_id) as reset_task: with TaskLogger(reset_task, backend=backend, ping=True): _reset_project(project) except ExclusiveTask.LockError: # We were unable to acquire a lock for this project. logger.info( "Project %s (%s) is currently being processed, aborting..." % (project.name, project.pk)) with backend.transaction(): backend.update(project, { 'last_reset': { 'dt': datetime.datetime.now(), 'status': 'blocked' } }) finally: logger.info("Done.")
def put(self, project_id): form = ProjectForm(request.form) if not form.validate(): return ({'message': 'Please correct the errors mentioned below.', 'errors': form.errors}, 400) data = {} if form.description.data: data['description'] = form.description.data if form.public.data is not None: data['public'] = form.public.data for key, value in data.items(): request.project[key] = value with backend.transaction(): backend.update(request.project, data.keys()) return ({'message': 'success!', 'project': self.export(request.project)}, 200)
def _reset_project(project): try: settings.hooks.call("project.reset.before", project) reset_command = ResetCommand(project, settings.checkmate_settings, backend) reset_command.run() with backend.transaction(): backend.update(project, { 'last_reset': { 'dt': datetime.datetime.now(), 'status': 'succeeded' }, 'analyze': True, 'analysis_priority': Project.AnalysisPriority.high, 'analysis_requested_at': datetime.datetime.now() }, unset_fields=["first_analysis_email_sent"]) settings.hooks.call("project.reset.after", project) except: with backend.transaction(): backend.update( project, { 'last_reset': { 'dt': datetime.datetime.now(), 'status': 'failed' }, }) logger.error( "Reset of project {project.name} (pk={project.pk}) failed!".format( project=project)) raise finally: with backend.transaction(): backend.update(project, {'reset': False}, unset_fields=['reset_requested_at'])
def delete(self, project_id): with backend.transaction(): backend.update(request.project, {'delete': True}) return ({'message': 'success! Your project will be fully deleted within a few minutes.', 'project_id': request.project.pk}, 200)