def test_should_only_get_audit_event_with_correct_object_type(self): self.add_audit_events_with_db_object() with self.app.app_context(): # Create a second AuditEvent with the same object_id but with a # different object_type to check that we're not filtering based # on object_id only supplier = Supplier.query.filter(Supplier.supplier_id == 1).first() event = AuditEvent( audit_type=AuditTypes.supplier_update, db_object=supplier, user='******', data={'request': "data"} ) event.object_type = 'Service' db.session.add(event) db.session.commit() response = self.client.get('/audit-events?object-type=suppliers&object-id=1') data = json.loads(response.get_data()) assert_equal(response.status_code, 200) assert_equal(len(data['auditEvents']), 1) assert_equal(data['auditEvents'][0]['user'], 'rob')
def audit_events(app, suppliers_service): with app.app_context(): db.session.add( AuditEvent( audit_type=audit_types.sent_expiring_documents_email, data={ 'campaign_title': ('Expiring documents - {}'.format( date.today().isoformat())), 'sellers': suppliers_service.get_suppliers_with_expiring_documents() }, db_object=None, user=None)) db.session.add( AuditEvent(audit_type=audit_types.sent_expiring_licence_email, data={ 'campaign_title': ('Expiring labour hire licence - {}'.format( date.today().isoformat())), 'sellers': suppliers_service. get_suppliers_with_expiring_labour_hire_licences() }, db_object=None, user=None)) yield db.session.query(AuditEvent).all()
def test_should_only_get_audit_event_with_correct_object_type(self): self.add_audit_events_with_db_object() with self.app.app_context(): # Create a second AuditEvent with the same object_id but with a # different object_type to check that we're not filtering based # on object_id only supplier = Supplier.query.filter(Supplier.code == 1).first() event = AuditEvent( audit_type=AuditTypes.supplier_update, db_object=supplier, user='******', data={'request': "data"} ) event.object_type = 'Service' db.session.add(event) db.session.commit() response = self.client.get('/audit-events?object-type=suppliers&object-id=1') data = json.loads(response.get_data()) assert_equal(response.status_code, 200) assert_equal(len(data['auditEvents']), 1) assert_equal(data['auditEvents'][0]['user'], 'rob')
def audit_event(user=0, type=AuditTypes.supplier_update, db_object=None): return AuditEvent( audit_type=type, db_object=db_object, user=user, data={'request': "data"} )
def create_application(): application_json = get_application_json() json_payload = get_json_from_request() application = Application() application.update_from_json(application_json) save_application(application) name = json_payload.get('name') updated_by = json_payload.get('updated_by') db.session.add(AuditEvent( audit_type=AuditTypes.create_application, user=updated_by, data={}, db_object=application )) db.session.commit() publish_tasks.application.delay( publish_tasks.compress_application(application), 'created', name=name, email_address=updated_by, from_expired=True ) return jsonify(application=application.serializable), 201
def revert_application(application_id): updater_json = validate_and_return_updater_request() json_payload = request.get_json(force=True) message = json_payload.get('message', None) if not message: message = None if message is not None and message.isspace(): message = None application = Application.query.get(application_id) if application is None: abort(404, "Application '{}' does not exist".format(application_id)) if application.status != 'submitted': abort( 400, "Application '{}' is not in submitted state for reverting ".format( application_id)) db.session.add( AuditEvent(audit_type=AuditTypes.revert_application, user=updater_json['updated_by'], data={}, db_object=application)) application.status = 'saved' db.session.commit() # post request from react RevertNotification form sends message as empty string # to indicate we should run the revert logic but not send an email if message is not None: send_revert_notification(application_id, message) return jsonify(application=application.serializable), 200
def delete_case_study(case_study_id): """ Delete a case study :param case_study_id: :return: """ updater_json = validate_and_return_updater_request() casestudy = CaseStudy.query.filter( CaseStudy.id == case_study_id).first_or_404() audit = AuditEvent(audit_type=AuditTypes.delete_casestudy, user=updater_json['updated_by'], data={"caseStudyId": case_study_id}, db_object=None) db.session.delete(casestudy) db.session.add(audit) try: db.session.commit() except IntegrityError as e: db.session.rollback() abort(400, "Database Error: {0}".format(e)) return jsonify(message="done"), 200
def delete_application(application_id): """ Delete a Application :param application_id: :return: """ updater_json = validate_and_return_updater_request() application = Application.query.options(noload('supplier')).filter( Application.id == application_id).first_or_404() db.session.add( AuditEvent(audit_type=AuditTypes.delete_application, user=updater_json['updated_by'], data={}, db_object=application)) application.status = 'deleted' users = User.query.filter(User.application_id == application_id).all() # this should go back to previous application id, not just none. for user in users: user.application = None try: db.session.commit() publish_tasks.application.delay( publish_tasks.compress_application(application), 'deleted') except IntegrityError as e: db.session.rollback() abort(400, "Database Error: {0}".format(e)) return jsonify(message="done"), 200
def reset_password(): json_payload = get_json_from_request() email_address = json_payload.get('email_address', None) if not email_address: abort(400, "Must supply the email address of the account to reset") user = user_service.get_by_email(email_address.lower()) if not user: abort(404, "User not found") user_data = {'user_id': user.id} claim = user_claims_service.make_claim(type='password_reset', email_address=email_address, data=user_data) if not claim: abort(500, "There was an issue completing the password reset process.") result = {'token': claim.token, 'email_address': email_address} try: audit = AuditEvent(audit_type=AuditTypes.update_user, user=email_address.lower(), data={}, db_object=user) db.session.add(audit) db.session.commit() except Exception: pass return jsonify(result)
def update_application(application_id): application_json = get_application_json() application = Application.query.options( noload('supplier.*')).get(application_id) if application is None: abort(404, "Application '{}' does not exist".format(application_id)) if application.status == 'submitted' and application_json.get( 'status') == 'saved': db.session.add( AuditEvent(audit_type=AuditTypes.revert_application, user='', data={}, db_object=application)) publish_tasks.application.delay( publish_tasks.compress_application(application), 'reverted') application.update_from_json(application_json) save_application(application) errors = ApplicationValidator(application).validate_all() agreement = get_current_agreement() return (jsonify(application=application.serializable, agreement=agreement.serialize() if agreement else None, application_errors=errors), 200)
def update_application_admin(application_id): application_json = get_application_json() application = Application.query.get(application_id) if application is None: abort(404, "Application '{}' does not exist".format(application_id)) if application.status == 'submitted' or application.status == 'saved': db.session.add( AuditEvent(audit_type=AuditTypes.update_application_admin, user='', data={ 'old': application.serialize(), 'new': application_json }, db_object=application)) application.update_from_json(application_json) save_application(application) publish_tasks.application.delay( publish_tasks.compress_application(application), 'updated') return jsonify(application=application.serializable), 200 else: return jsonify( application=application.serializable, errors=[{ 'serverity': 'error', 'message': 'Application can only be updated when the status is submitted or saved' }]), 200
def update_supplier(supplier_code, **kwargs): if supplier_code is None: raise ValueError( "supplier_code was not provided in kwargs to update supplier function" ) supplier = Supplier.query.filter(Supplier.code == supplier_code).first() if supplier is None: raise ValueError( "Unable to modify supplier. supplier with code {} does not exist". format(supplier_code)) supplier.update_from_json(kwargs) audit = AuditEvent(audit_type=AuditTypes.supplier_update, user=None, data={ 'supplier': supplier.code, 'update': kwargs }, db_object=supplier) db.session.add(supplier) db.session.add(audit) db.session.commit() return supplier
def test_get_service_returns_correct_unavailability_audit_if_disabled_but_framework_is_expired(self): # create an audit event for the disabled service with self.app.app_context(): # get expired framework framework = Framework.query.filter( Framework.id == 123 ).first() # create an audit event for the framework status change audit_event = AuditEvent( audit_type=AuditTypes.framework_update, db_object=framework, user='******', data={ "update": { "status": "expired", "clarificationQuestionsOpen": "true" } } ) # make a disabled service use the expired framework Service.query.filter( Service.service_id == '123-disabled-456' ).update({ 'framework_id': 123 }) db.session.add(audit_event) db.session.commit() response = self.client.get('/services/123-disabled-456') data = json.loads(response.get_data()) assert_equal(data['serviceMadeUnavailableAuditEvent']['type'], 'framework_update') assert_equal(data['serviceMadeUnavailableAuditEvent']['user'], 'joeblogs') assert_in('createdAt', data['serviceMadeUnavailableAuditEvent']) assert_equal(data['serviceMadeUnavailableAuditEvent']['data']['update']['status'], 'expired')
def test_get_service_returns_unavailability_audit_if_disabled(self): # create an audit event for the disabled service with self.app.app_context(): service = Service.query.filter( Service.service_id == '123-disabled-456' ).first() audit_event = AuditEvent( audit_type=AuditTypes.update_service_status, db_object=service, user='******', data={ "supplierId": 1, "newArchivedServiceId": 2, "new_status": "disabled", "supplierName": "Supplier 1", "serviceId": "123-disabled-456", "old_status": "published", "oldArchivedServiceId": 1 } ) db.session.add(audit_event) db.session.commit() response = self.client.get('/services/123-disabled-456') data = json.loads(response.get_data()) assert_equal(data['serviceMadeUnavailableAuditEvent']['type'], 'update_service_status') assert_equal(data['serviceMadeUnavailableAuditEvent']['user'], 'joeblogs') assert_in('createdAt', data['serviceMadeUnavailableAuditEvent']) assert_equal(data['serviceMadeUnavailableAuditEvent']['data']['serviceId'], '123-disabled-456') assert_equal(data['serviceMadeUnavailableAuditEvent']['data']['old_status'], 'published') assert_equal(data['serviceMadeUnavailableAuditEvent']['data']['new_status'], 'disabled')
def create_application(email_address=None, name=None): application = Application(status='saved', data={ 'framework': 'digital-marketplace', 'email': email_address }) db.session.add(application) db.session.flush() audit = AuditEvent(audit_type=AuditTypes.create_application, user='', data={}, db_object=application) db.session.add(audit) db.session.commit() publish_tasks.application.delay( publish_tasks.compress_application(application), 'created', name=name, email_address=email_address, from_expired=False) return application
def add_audit_events_with_db_object(self): self.setup_dummy_suppliers(3) with self.app.app_context(): suppliers = Supplier.query.all() for supplier in suppliers: event = AuditEvent(AuditTypes.contact_update, "rob", {}, supplier) db.session.add(event) db.session.commit()
def duplicate_audit_event(email_address, data): audit = AuditEvent(audit_type=AuditTypes.duplicate_supplier, user=email_address, data=data, db_object=None) db.session.add(audit) db.session.commit()
def update_user_details(**kwargs): """ Update a user. Looks user up in DB, and updates where necessary. """ user_id = kwargs.get('user_id', None) user = User.query.filter(User.id == user_id).first() if user is None: raise ValueError("Unable to modify user. User with id {} does not exist".format(user_id)) if kwargs.get('password', None) is not None: user.password = encryption.hashpw(kwargs['password']) user.password_changed_at = datetime.utcnow() if kwargs.get('active', None) is not None: user.active = kwargs['active'] if kwargs.get('name', None) is not None: user.name = kwargs['name'] if kwargs.get('email_address', None) is not None: user.email_address = kwargs['email_address'] if kwargs.get('role', None) is not None: if user.role == 'supplier' and kwargs['role'] != user.role: user.supplier_code = None kwargs.pop('supplierCode', None) user.role = kwargs['role'] if kwargs.get('supplierCode', None) is not None: user.supplier_code = kwargs['supplierCode'] if kwargs.get('application_id', None) is not None: user.application_id = kwargs['application_id'] if kwargs.get('locked', None) and not kwargs['locked']: user.failed_login_count = 0 if kwargs.get('termsAcceptedAt', None) is not None: user.terms_accepted_at = kwargs['termsAcceptedAt'] check_supplier_role(user.role, user.supplier_code) update_data = { "user_id": user_id, "email_address": kwargs.get('email_address', None) } audit = AuditEvent( audit_type=AuditTypes.update_user, user=kwargs.get('updated_by', 'no user data'), data={ 'user': user.email_address, 'update': update_data }, db_object=user ) db.session.add(user) db.session.add(audit) db.session.commit() return user
def log_event(): if request.method == "POST": data = request.form.to_dict() action_taken = ( f"Generated a {data.get('report-type')} report on {data.get('attribute')} for {data.get('scheme')} " f"{data.get('year')} intake" ) db.session.add(AuditEvent(user_id=current_user.id, action_taken=action_taken)) db.session.commit()
def log_audit_event(self, **kwargs): try: audit = AuditEvent(audit_type=kwargs['audit_type'], user=kwargs['user'], data=kwargs['data'], db_object=kwargs['db_object']) self.save(audit) except Exception: rollbar.report_exc_info(extra_data={ 'audit_type': kwargs['audit_type'], 'id': kwargs['db_object'].id })
def notify_callback(): notify_data = get_json_from_request() email_address = notify_data["to"] hashed_email = hash_string(email_address) reference = notify_data["reference"] status = notify_data["status"] # remove PII from response for logging # according to docs only "to" has PII # https://docs.notifications.service.gov.uk/rest-api.html#delivery-receipts clean_notify_data = notify_data.copy() del clean_notify_data["to"] current_app.logger.info( f"Notify callback: {status}: {reference} to {hashed_email}", extra={"notify_delivery_receipt": clean_notify_data}, ) if status == "permanent-failure": user = User.query.filter(User.email_address == email_address).first() if user and user.active: user.active = False db.session.add(user) audit_event = AuditEvent( audit_type=AuditTypes.update_user, user='******', data={ "user": { "active": False }, "notify_callback_data": notify_data }, db_object=user, ) db.session.add(audit_event) db.session.commit() current_app.logger.info( f"User account disabled for {hashed_email} after Notify reported permanent delivery " "failure.") elif status.endswith("failure"): current_app.logger.warning( f"Notify failed to deliver {reference} to {hashed_email}") return jsonify(status='ok'), 200
def create_framework_interest_audit_event(self, framework_id, supplier_ids): with self.app.app_context(): for supplier_id in supplier_ids: db.session.add( AuditEvent( audit_type=AuditTypes.register_framework_interest, user='******', data='{}', db_object=Supplier.query.filter( Supplier.supplier_id == supplier_id ).first() ) ) db.session.commit()
def audit_event(self, app, brief_responses): with app.app_context(): now = pendulum.now('utc') brief_response = brief_responses_service.get(2) db.session.add( AuditEvent( audit_type=audit_types.create_brief_response, data={}, db_object=brief_response, user='******' ) ) db.session.commit() yield db.session.query(AuditEvent).first()
def application_approval(application_id, result): updater_json = validate_and_return_updater_request() application = Application.query.get(application_id) if application is None: abort(404, "Application '{}' does not exist".format(application_id)) db.session.add( AuditEvent(audit_type=(AuditTypes.approve_application if result else AuditTypes.reject_application), user=updater_json['updated_by'], data={}, db_object=application)) application.set_approval(approved=result) db.session.commit() publish_tasks.application.delay( publish_tasks.compress_application(application), 'approved' if result else 'approval_rejected') return jsonify(application=application.serializable), 200
def notify_callback(): notify_data = get_json_from_request() if notify_data['status'] == 'permanent-failure': user = User.query.filter( User.email_address == notify_data['to']).first() if user and user.active: user.active = False db.session.add(user) audit_event = AuditEvent( audit_type=AuditTypes.update_user, user='******', data={ "user": { "active": False }, "notify_callback_data": notify_data }, db_object=user, ) db.session.add(audit_event) db.session.commit() current_app.logger.info( "User account disabled for {hashed_email} after Notify reported permanent delivery " "failure.".format(hashed_email=hash_string(notify_data['to']))) elif notify_data['status'] == 'technical-failure': current_app.logger.warning( "Notify failed to deliver {reference} to {hashed_email}".format( reference=notify_data['reference'], hashed_email=hash_string(notify_data['to']), )) return jsonify(status='ok'), 200
def test_send_new_briefs_email_fails_no_new_briefs_since_last_run( app, briefs, mocker): mailchimp = mocker.patch('app.tasks.mailchimp.MailChimp') client = MagicMock() mailchimp.return_value = client environ['MAILCHIMP_SELLER_EMAIL_LIST_ID'] = '123456' with app.app_context(): audit = AuditEvent( audit_type=AuditTypes.send_seller_opportunities_campaign, user=None, data={}, db_object=None) db.session.add(audit) db.session.commit() send_new_briefs_email() assert not client.campaigns.create.called assert not client.campaigns.content.update.called assert not client.campaigns.actions.schedule.called
def create_application(email_address=None, name=None): application = Application(status='saved', data={ 'framework': 'digital-marketplace', 'email': email_address, 'name': name }) db.session.add(application) db.session.flush() audit = AuditEvent(audit_type=AuditTypes.create_application, user='', data={}, db_object=application) db.session.add(audit) db.session.commit() notify_team_new_applicant(application_id=application.id, name=name, email_address=email_address) return application
def send_new_briefs_email(): client = get_client() list_id = getenv('MAILCHIMP_SELLER_EMAIL_LIST_ID') if not list_id: raise MailChimpConfigException( 'Failed to get MAILCHIMP_SELLER_EMAIL_LIST_ID from the environment variables.' ) # determine the age of the briefs being requested, defaulting to 24 hours ago last_run_audit = (db.session.query(AuditEvent).filter( AuditEvent.type == AuditTypes.send_seller_opportunities_campaign.value).order_by( AuditEvent.created_at.desc()).first()) if last_run_audit: last_run_time = last_run_audit.created_at else: last_run_time = pendulum.now().subtract(hours=24) # gather the briefs open_briefs = briefs.get_open_briefs_published_since(since=last_run_time) briefs_atm = [x for x in open_briefs if x.lot.slug == 'atm'] briefs_professionals = [ x for x in open_briefs if x.lot.slug == 'specialist' ] briefs_training = [x for x in open_briefs if x.lot.slug == 'training'] if len(open_briefs) < 1: current_app.logger.info( 'No briefs found for daily seller email - the campaign was not sent' ) return # create a campaign today = pendulum.today() recipients = {'list_id': list_id} campaign = create_campaign( client, recipients, { 'subject_line': 'New opportunities in the Digital Marketplace' if len(open_briefs) > 1 else 'A new opportunity in the Digital Marketplace', 'title': 'New opportunities - DMP sellers %s-%s-%s' % (today.year, today.month, today.day) }) # add content to the campaign template = template_env.get_template( 'mailchimp_new_seller_opportunities.html') email_body = template.render(brief_count=len(open_briefs), briefs_atm=briefs_atm, briefs_professionals=briefs_professionals, briefs_training=briefs_training, current_year=pendulum.today().year) update_campaign_content(client, campaign['id'], email_body) # schedule the campaign to send at least an hour from runtime, rounded up to the nearest 15 minute mark schedule_time = pendulum.now('UTC').add(hours=1) current_minute = schedule_time.minute if current_minute % 15 != 0: new_minute = current_minute while new_minute % 15 != 0: new_minute = new_minute + 1 delta = new_minute - current_minute schedule_time = schedule_time.add(minutes=delta) schedule_campaign(client, campaign['id'], schedule_time) # record the audit event try: audit = AuditEvent( audit_type=AuditTypes.send_seller_opportunities_campaign, user=None, data={ 'briefs_sent': len(open_briefs), 'email_body': email_body }, db_object=None) db.session.add(audit) db.session.commit() except Exception: rollbar.report_exc_info()
def submit_application(application_id): current_time = pendulum.now('UTC').to_iso8601_string(extended=True) application = Application.query.get(application_id) if application is None: abort(404, "Application '{}' does not exist".format(application_id)) if application.status == 'submitted': abort(400, 'Application is already submitted') errors = ApplicationValidator(application).validate_all() if errors: abort(400, 'Application has errors') json_payload = get_json_from_request() json_has_required_keys(json_payload, ['user_id']) user_id = json_payload['user_id'] user = User.query.get(user_id) if application.type != 'edit': if user.application_id != application.id: abort(400, 'User is not authorized to submit application') else: if user.supplier_code != application.supplier_code: abort( 400, 'User supplier code does not match application supplier code') agreement = get_current_agreement() if agreement is None: abort(404, 'Current master agreement not found') db.session.add( AuditEvent(audit_type=AuditTypes.submit_application, user=user_id, data={}, db_object=application)) application.submit_for_approval() application.update_from_json({'submitted_at': current_time}) signed_agreement = None if application.type != 'edit': # only create signed agreements on initial applications signed_agreement = SignedAgreement() signed_agreement.user_id = user_id signed_agreement.agreement_id = agreement.id signed_agreement.signed_at = current_time signed_agreement.application_id = application_id db.session.add(signed_agreement) if application.supplier_code: send_submitted_existing_seller_notification(application.id) else: send_submitted_new_seller_notification(application.id) db.session.commit() publish_tasks.application.delay( publish_tasks.compress_application(application), 'submitted') return jsonify(application=application.serializable, signed_agreement=signed_agreement)
def audit_event(user, type, db_object=None): return AuditEvent(audit_type=type, db_object=db_object, user=user, data={'request': "data"})
def add_user(data): if data is None: raise DataError('create_user requires a data arg') name = data.get('name') password = data.get('password') role = data.get('user_type') email_address = data.get('email_address', None) framework_slug = data.get('framework', 'digital-marketplace') if email_address is None: email_address = data.get('emailAddress', None) if 'hashpw' in data and not data['hashpw']: password = password else: password = encryption.hashpw(password) if role == 'seller': role = 'applicant' now = datetime.utcnow() user = User(email_address=email_address.lower(), phone_number=data.get('phoneNumber', None), name=name, role=role, password=password, active=True, created_at=now, updated_at=now, password_changed_at=now) audit_data = {} if "supplier_code" in data: user.supplier_code = data['supplier_code'] audit_data['supplier_code'] = user.supplier_code if user.role == 'supplier' and user.supplier_code is None: raise ValueError( "'supplier_code' is required for users with 'supplier' role") if user.role != 'supplier' and user.supplier_code is not None: raise ValueError( "'supplier_code' is only valid for users with 'supplier' role, not '{}'" .format(user.role)) if "application_id" in data: user.application_id = data['application_id'] elif user.supplier_code is not None: appl = Application.query.filter_by( supplier_code=user.supplier_code).first() user.application_id = appl and appl.id or None if user.role == 'applicant' and user.application_id is None: raise ValueError( "'application id' is required for users with 'applicant' role") elif user.role != 'applicant' and user.role != 'supplier' and user.application_id is not None: raise ValueError( "'application_id' is only valid for users with applicant' or 'supplier' role, not '{}'" .format(user.role)) db.session.add(user) db.session.flush() framework = Framework.query.filter( Framework.slug == framework_slug).first() db.session.add(UserFramework(user_id=user.id, framework_id=framework.id)) audit = AuditEvent(audit_type=AuditTypes.create_user, user=email_address.lower(), data=audit_data, db_object=user) db.session.add(audit) db.session.commit() return user