def post_enter_email(): email = request.form.get('email') current_app.logger.info( "Endpoint called with customer email as '{}'".format(email)) validator = CustomerEmailValidator.validate(email) if validator.errors: current_app.logger.warning("Validation errors found") return render_template( 'enter_email.html', validation_errors=validator.errors, validation_summary_heading=validator.summary_heading_text, error_heading_message=validator.summary_heading_text, request_body=request.form, submit_url=url_for("send_payment_link.post_enter_email")), 400 # Send email NotificationAPIService.send_message_notify( email, config.NOTIFY_PAYMENT_LINK_TEMPLATE_ID, {}) # Clear down the session information for payment g.session.send_payment_link_info = None g.session.commit() AuditAPIService.audit_event( "LON payment link sent to user {}".format(email)) return render_template('email_confirmation.html')
def view_lon(charge_id): current_app.logger.info("Endpoint called - Clearing session charge") local_land_charge_service = LocalLandChargeService(current_app.config) # Clear these session values if someone quit an edit back to the view page g.session.add_lon_charge_state = None g.session.edited_fields = None g.session.commit() current_app.logger.info("Charge cleared from session") display_id, charge_item = get_lon_by_charge_id(charge_id, local_land_charge_service) # Let the LLC blueprint handle LLC's if charge_item.charge_type != LonDefaults.charge_type: return redirect( url_for('view_land_charge.view_land_charge', local_land_charge=charge_id)) updated, updated_date = get_history_update_info_by_charge_id( charge_id, local_land_charge_service) AuditAPIService.audit_event("User viewing LON: {}".format(charge_id)) return render_template( 'view_lon.html', charge_id=display_id, charge_item=charge_item, updated=updated, updated_date=updated_date, geometry=json.dumps(charge_item.geometry), document_urls=get_external_url_for_docs(charge_item))
def test_audit_event_success(self, mock_socket): with main.app.test_request_context(): mock_socket.gethostbyname.return_value = "1.1.1.1" self.mock_request() g.requests.post.return_value = self.mock_response(status_code=201) AuditAPIService.audit_event("A thing") g.requests.post.assert_called()
def view_land_charge(local_land_charge): current_app.logger.info("Endpoint called - Clearing session charge") local_land_charge_service = LocalLandChargeService(current_app.config) # Clear these session values if someone quit an edit back to the view page g.session.add_charge_state = None g.session.edited_fields = None g.session.charge_added_outside_users_authority = None g.session.other_authority_update_permission = None g.session.other_authority_cancel_permission = None g.session.commit() current_app.logger.info("Charge cleared from session") validate_charge_id(local_land_charge) current_app.logger.info( "Retrieving charge for local_land_charge='{}'".format( local_land_charge)) response = local_land_charge_service.get_by_charge_number( local_land_charge) if response.status_code == 404: current_app.logger.info( "Search service reports '{}' not found - Returning error".format( local_land_charge)) raise ApplicationError(404) response.raise_for_status() current_app.logger.info( "Retrieved charge for local_land_charge='{}'".format( local_land_charge)) charge_data = response.json()[0]['item'] charge_item = LocalLandChargeItem.from_json(charge_data) # Let the LON blueprint handle LONS if charge_item.charge_type == LonDefaults.charge_type: return redirect( url_for('view_lon.view_lon', charge_id=local_land_charge)) current_app.logger.info( "Retrieving charge history for local_land_charge='{}'".format( local_land_charge)) updated, updated_date = get_history_update_info_by_charge_id( local_land_charge, local_land_charge_service) current_app.logger.info( "Rendering template for local_land_charge='{}'".format( local_land_charge)) AuditAPIService.audit_event( "User viewing charge: {}".format(local_land_charge)) return render_template('view_charge.html', charge_item=charge_item, charge_id=response.json()[0]['display_id'], updated_date=updated_date, updated=updated, geometry=json.dumps(charge_item.geometry))
def update_charge(land_charge): current_app.logger.info("Attempting to update a charge") try: # Update Author Information land_charge.author = g.session.user.get_author_info() charge_json = land_charge.to_json() headers = { 'Content-Type': 'application/json', 'X-Trace-ID': g.trace_id } current_app.logger.info( "Putting to maintain-api/local-land-charge/{}".format( charge_json['local-land-charge'])) response = g.requests.put('{}/local-land-charge/{}'.format( MAINTAIN_API_URL, charge_json['local-land-charge']), json=charge_json, headers=headers) except Exception as ex: error_message = 'Failed to send land charge to maintain-api. ' \ 'TraceID : {} - Exception - {}' \ .format(g.trace_id, ex) current_app.logger.exception(error_message) AuditAPIService.audit_event( "Failed to send land charge to maintain-api", supporting_info={ 'id': calc_display_id(land_charge.local_land_charge) }) raise ApplicationError(500) if response.status_code != 202: current_app.logger.exception( 'Failed to send land charge to maintain-api. ' 'TraceID : {} - Status: {}, Message: {}'.format( g.trace_id, response.status_code, response.text)) AuditAPIService.audit_event( "Failed to send land charge to maintain-api", supporting_info={ 'id': calc_display_id(land_charge.local_land_charge) }) raise ApplicationError(500) result = response.json() current_app.logger.info( "User ID '{}' updated charge {}. Entry number: {}, registration date: {}. TraceID={}" .format(g.session.user.id, result['land_charge_id'], result['entry_number'], result['registration_date'], g.trace_id)) last_charge = LastCreatedCharge() last_charge.charge_id = result['land_charge_id'] last_charge.entry_number = result['entry_number'] last_charge.registration_date = datetime.strptime( result['registration_date'], "%Y-%m-%d").strftime("%d/%m/%Y") g.session.last_created_charge = last_charge g.session.commit()
def test_audit_event_machine_ip_without_supporting_info(self, mock_socket): with main.app.test_request_context(): mock_socket.gethostbyname.return_value = "1.1.1.1" self.mock_request() g.requests.post.return_value = self.mock_response(status_code=201) AuditAPIService.audit_event("A thing") g.requests.post.assert_called() message = json.loads(g.requests.post.call_args[1]['data']) self.assertIn('machine_ip', message['supporting_info']) self.assertEqual("1.1.1.1", message['supporting_info']['machine_ip']) self.assertNotIn('another', message['supporting_info'])
def test_audit_event_exception(self, mock_app, mock_socket): with main.app.test_request_context(): mock_socket.gethostbyname.return_value = "1.1.1.1" self.mock_request() g.trace_id = '123' g.requests.post.side_effect = Exception('Test exception') try: AuditAPIService.audit_event("A thing") except ApplicationError as e: self.assertEqual(e.http_code, 500) return self.fail()
def cancel_charge(charge_id): current_app.logger.info("Endpoint called with charge_id='{}'".format(charge_id)) local_land_charge_service = LocalLandChargeService(current_app.config) current_app.logger.info("Retrieving charge information from charge_id='{}'".format(charge_id)) response = local_land_charge_service.get_by_charge_number(charge_id) if response.status_code == 404: current_app.logger.warning("Charge not found for charge_id='{}' - Returning not found".format(charge_id)) raise ApplicationError(404) response.raise_for_status() charge_data = response.json()[0]['item'] charge_item = LocalLandChargeItem.from_json(charge_data) # add the charge to the session for checking the geometry is within the user's authority g.session.add_charge_state = charge_item g.session.commit() current_app.logger.info("Charge information retrieved for charge_id='{}'".format(charge_id)) if request.method == 'GET': # Check if the charge is outside of the user's authority extent = build_extents_from_features(charge_item.geometry) result = LocalAuthorityService(current_app.config).get_authorities_by_extent(extent) if should_show_confirmation_warning(result): # In this case the charge is outside of the user's authority if Permissions.add_extent_anywhere in g.session.user.permissions: # If the user has permission to add a charge anywhere, give them cancel permission without asking g.session.other_authority_cancel_permission = True g.session.commit() if not g.session.other_authority_cancel_permission: # if the user has not confirmed that they can edit charges outside of their authority, make them return redirect(url_for('cancel_land_charge.get_cancel_location_confirmation')) current_app.logger.info("Rendering response for charge_id='{}'".format(charge_id)) return render_template('cancel.html', charge_id=charge_id, charge_item=charge_item, geometry=json.dumps(charge_item.geometry)) else: charge_item.end_date = date.today() current_app.logger.info("Updating charge with cancellation date for charge_id='{}'".format(charge_id)) AuditAPIService.audit_event("Cancelling charge", supporting_info={'id': charge_id}) if g.session.other_authority_cancel_permission: AuditAPIService.audit_event( "Charge cancelled outside users authority.", supporting_info={'originating-authority': g.session.user.organisation}) MaintainApiService.update_charge(charge_item) current_app.logger.info("Rendering response for charge_id='{}'".format(charge_id)) # This is required because if the render_template is called from this post method then the flow won't be able # to return to the confirmation page if the user goes to the feedback form from the confirmation page return redirect(url_for('cancel_land_charge.cancel_confirmation', charge_id=charge_id))
def test_audit_event_non201(self, mock_app, mock_socket): with main.app.test_request_context(): mock_socket.gethostbyname.return_value = "1.1.1.1" self.mock_request() g.trace_id = '123' g.requests.post.return_value = self.mock_response( status_code=500, text="Something went wrong") try: AuditAPIService.audit_event("A thing") except ApplicationError as e: self.assertEqual(e.http_code, 500) return self.fail()
def get_check_your_email(): if g.session.two_factor_authentication_code is None and config.ENABLE_TWO_FACTOR_AUTHENTICATION: g.session.two_factor_authentication_code = two_factor_authentication_code_generator.generate_code( ) g.session.two_factor_authentication_generation_time = get_current_timestamp_minutes( ) g.session.two_factor_authentication_invalid_attempts = 0 g.session.commit_2fa_state() NotificationAPIService.send_message_notify( g.session.user.email, config.NOTIFY_TWO_FACTOR_AUTH_TEMPLATE_ID, {'code': g.session.two_factor_authentication_code}) AuditAPIService.audit_event("2FA code sent to user") return render_template('check_your_email.html')
def modify_land_charge_confirm(charge_id): current_app.logger.info("Endpoint called - Updating charge") AuditAPIService.audit_event("Vary request submitted", supporting_info={'id': charge_id}) MaintainApiService.update_charge(g.session.add_lon_charge_state) g.session.add_lon_charge_state = None g.session.edited_fields = None g.session.commit() current_app.logger.info("Charge removed from session - Rendering Template") # This is required because if the render_template is called from this post method then the flow won't be able to # return to the confirmation page if the user goes to the feedback form from the confirmation page return redirect( url_for('modify_lon.modify_confirmation', charge_id=charge_id))
def test_audit_event_timestamps(self, mock_socket): with main.app.test_request_context(): mock_socket.gethostbyname.return_value = "1.1.1.1" self.mock_request() g.requests.post.return_value = self.mock_response(status_code=201) AuditAPIService.audit_event("A thing") call1 = g.requests.post.call_args args, kwargs = call1 call1_timestamp = json.loads(kwargs['data'])['activity_timestamp'] time.sleep(0.1) AuditAPIService.audit_event("A thing") call2 = g.requests.post.call_args args, kwargs = call2 call2_timestamp = json.loads(kwargs['data'])['activity_timestamp'] self.assertNotEqual(call1_timestamp, call2_timestamp)
def confirm(charge_id): local_land_charge_service = LocalLandChargeService(current_app.config) display_id, charge_item = get_lon_by_charge_id(charge_id, local_land_charge_service) if request.method == 'GET': return render_template('cancel_lon_confirm.html', charge_id=display_id, charge_item=charge_item, geometry=json.dumps(charge_item.geometry)) if request.method == 'POST': current_app.logger.info("Cancelling Charge - {}".format(display_id)) AuditAPIService.audit_event("Cancelling charge", supporting_info={'id': display_id}) MaintainApiService.update_charge(g.session.add_lon_charge_state) # This is required because if the render_template is called from this post method then the flow won't be able # to return to the confirmation page if the user goes to the feedback form from the confirmation page return redirect( url_for('cancel_lon.charge_cancelled', charge_id=display_id))
def modify_land_charge_confirm(): current_app.logger.info("Endpoint called - Updating charge") AuditAPIService.audit_event( "Vary request submitted.", supporting_info={ 'id': calc_display_id(g.session.add_charge_state.local_land_charge) }) if g.session.charge_added_outside_users_authority: AuditAPIService.audit_event( "Charge location varied, extent(s) modified to be outside users authority.", supporting_info={ 'originating-authority': g.session.user.organisation, 'id': calc_display_id(g.session.add_charge_state.local_land_charge) }) if g.session.other_authority_update_permission: AuditAPIService.audit_event( "Charge location varied, extent(s) modified on a charge outside users authority.", supporting_info={ 'originating-authority': g.session.user.organisation, 'id': calc_display_id(g.session.add_charge_state.local_land_charge) }) MaintainApiService.update_charge(g.session.add_charge_state) charge_id = calc_display_id(g.session.add_charge_state.local_land_charge) g.session.add_charge_state = None g.session.edited_fields = None g.session.charge_added_outside_users_authority = None g.session.other_authority_update_permission = None g.session.other_authority_cancel_permission = None g.session.commit() current_app.logger.info("Charge removed from session - Rendering Template") return render_template('modify_confirmation.html', charge_id=charge_id)
def post_review(): current_app.logger.info('Endpoint called') g.session.redirect_route = None g.session.edited_fields = {} g.session.commit() if g.session.add_lon_charge_state is None: current_app.logger.info('Redirecting to: {}'.format(url_for('add_lon.new'))) return redirect(url_for('add_lon.new')) redirect_url = url_for('add_lon.get_confirmation') current_app.logger.info('Redirecting to next step: {}'.format(redirect_url)) submit_token = request.form.get('csrf_token') if submit_token != g.session.submit_token: g.session.submit_token = submit_token g.session.commit() AuditAPIService.audit_event("Submitting the charge", supporting_info=g.session.add_lon_charge_state.to_json()) MaintainApiService.add_charge(g.session.add_lon_charge_state) AuditAPIService.audit_event("Charge created", supporting_info={'id': g.session.last_created_charge.charge_id}) # Audit the LON payment method '''if g.session.payment_info.payment_method == 'govuk': AuditAPIService.audit_event("Payment made via GOV.UK Pay", supporting_info={'reference': g.session.payment_info.payment_ref, 'charge_id': g.session.last_created_charge.charge_id}) elif g.session.payment_info.payment_method == 'cheque': AuditAPIService.audit_event("Payment made by cheque", supporting_info={'charge_id': g.session.last_created_charge.charge_id} ) else: AuditAPIService.audit_event("No payment needed", supporting_info={'explanation': g.session.payment_info.no_payment_notes, 'charge_id': g.session.last_created_charge.charge_id})''' return redirect(redirect_url)
def llc1_get_result(): current_app.logger.info('Endpoint called') if g.session.llc1_state is None: current_app.logger.info('Redirecting to: %s', url_for("create_llc1.create_llc1")) return redirect(url_for("create_llc1.create_llc1")) current_app.logger.info("Calling LLC1 Document API") AuditAPIService.audit_event( "User submitted an LLC Official Search request") document_service = LLC1DocumentService(current_app.config) response = document_service.generate(g.session.llc1_state.description, g.session.llc1_state.extent) ReportAPIService.send_number_of_charges_per_search_data({ 'date': datetime.now(timezone.utc).isoformat(), 'channel': 'MAINTAIN', 'number_of_charges': response['number_of_charges'], # TODO(): Update once repeat searches are added to the Maintain service to check if # this is a repeat search 'repeat': False }) current_app.logger.info("Adding LLC1 URL to session state") g.session.llc1_state.llc1_url = response['document_url'] g.session.llc1_state.external_llc1_url = response['external_url'] g.session.commit() current_app.logger.info("Render template 'search_result.html'") return render_template( 'search_result.html', url=g.session.llc1_state.external_llc1_url, supporting_documents=response.get('supporting_documents'))
def post_review(): current_app.logger.info('Endpoint called') g.session.redirect_route = None g.session.edited_fields = [] g.session.commit() if g.session.add_charge_state is None: current_app.logger.info('Redirecting to: {}'.format( url_for('add_land_charge.new'))) return redirect(url_for('add_land_charge.new')) redirect_url = url_for('add_land_charge.get_confirmation') current_app.logger.info( 'Redirecting to next step: {}'.format(redirect_url)) submit_token = request.form.get('csrf_token') if submit_token != g.session.submit_token: g.session.submit_token = submit_token g.session.commit() added_outside_users_authority = g.session.charge_added_outside_users_authority if added_outside_users_authority: g.session.charge_added_outside_users_authority = None g.session.commit() AuditAPIService.audit_event( "Submitting the charge", supporting_info=g.session.add_charge_state.to_json()) MaintainApiService.add_charge(g.session.add_charge_state) if added_outside_users_authority: AuditAPIService.audit_event( "Charge added outside users authority.", supporting_info={ 'originating-authority': g.session.user.organisation, 'id': g.session.last_created_charge.charge_id }) AuditAPIService.audit_event( "Charge created", supporting_info={'id': g.session.last_created_charge.charge_id}) return redirect(redirect_url)
def post_check_your_email(): # Make sure a code and generation time has actually been set if g.session.two_factor_authentication_code is None or \ g.session.two_factor_authentication_generation_time is None: return redirect( url_for('two_factor_authentication.get_check_your_email')) code = request.form.get('code') validation_errors = TwoFactorAuthenticationValidator.validate(code) if 'code' in validation_errors.errors: # Log user out after 3 incorrect attempts AuditAPIService.audit_event('Invalid entry of 2FA code') if g.session.two_factor_authentication_invalid_attempts and \ g.session.two_factor_authentication_invalid_attempts >= 2: AuditAPIService.audit_event('2FA code attempt limits exceeded') return redirect('/logout') if g.session.two_factor_authentication_invalid_attempts: g.session.two_factor_authentication_invalid_attempts = \ g.session.two_factor_authentication_invalid_attempts + 1 else: g.session.two_factor_authentication_invalid_attempts = 1 g.session.commit_2fa_state() return render_template( 'check_your_email.html', validation_errors=validation_errors.errors, validation_summary_heading=validation_errors.summary_heading_text, ), 400 # Check code hasn't expired and that it matches if (get_current_timestamp_minutes() - g.session.two_factor_authentication_generation_time) < 10 and \ str(code) == str(g.session.two_factor_authentication_code): g.session.two_factor_authentication_passed = True g.session.commit_2fa_state() AuditAPIService.audit_event('User successfully passed 2FA') return redirect(g.session.two_factor_authentication_redirect_url) AuditAPIService.audit_event('Invalid entry of 2FA code') # Log user out after 3 incorrect attempts if g.session.two_factor_authentication_invalid_attempts and \ g.session.two_factor_authentication_invalid_attempts >= 2: AuditAPIService.audit_event('2FA code attempt limits exceeded') return redirect('/logout') if g.session.two_factor_authentication_invalid_attempts: g.session.two_factor_authentication_invalid_attempts = g.session.two_factor_authentication_invalid_attempts + 1 else: g.session.two_factor_authentication_invalid_attempts = 1 g.session.commit_2fa_state() # Generate error for incorrect / expired code validation_errors = TwoFactorAuthenticationValidator.generate_invalid_code_error_message( ) return render_template( 'check_your_email.html', validation_errors=validation_errors.errors, validation_summary_heading=validation_errors.summary_heading_text, ), 400