def __set_code(subscriber_id, sms): """ Sets a new sms verification code for the given subscriber ID. Args: subscriber_id (str): The UUID that is assigned to the subscriber upon creation by Meerkat Hermes. sms (int): The SMS number to which the new code should be sent. Returns: The Meerkat Hermes response object. """ code = round(random.random()*9999) message = gettext( 'Your verification code for {country} public health ' 'surveillance notifications is: {code}. For further information ' 'please see your email.' ).format( country=current_app.config['MESSAGING_CONFIG']['messages']['country'], code=code ) data = {'sms': sms, 'message': message} response = libs.hermes('/verify', 'PUT', {'subscriber_id': subscriber_id, 'code': code}) response = libs.hermes('/sms', 'PUT', data) return response
def sms_code(subscriber_id): """ Chooses, sets and checks SMS verification codes for the subscriber corresponding to the ID given in the URL. If a POST request is made to this URL it checks whether the code supplied in the POST request form data matches the code sent to the phone. If it does, it rediects to Stage 4, if it doesn't it redirects to stage 3 again with a flash informing the user they got the wrong code. If a GET request is made to this URL, the function selects a new code and sends the code out to the phone. It then redirects to Stage 3 with a flash message informing the user whether the new code has been succesffully sent. Args: subscriber_id (str): The UUID that is assigned to the subscriber upon creation by Meerkat Hermes. """ # If a POST request is made we check the given verification code. if request.method == 'POST': if __check_code(subscriber_id, request.form['code']): libs.hermes('/verify/' + subscriber_id, 'GET') return redirect( "/" + g.get("language") + "/messaging/subscribe/verified/" + subscriber_id, code=302 ) else: flash('You submitted the wrong code.', 'error') return redirect( "/" + g.get("language") + "/messaging/subscribe/verify/" + subscriber_id, code=302 ) # If a GET request is made we send a new code. else: subscriber = libs.hermes('/subscribe/' + subscriber_id, 'GET') response = __set_code(subscriber_id, subscriber['Item']['sms']) if response['ResponseMetadata']['HTTPStatusCode'] == 200: flash(gettext('A new code has been sent to your phone.')) return redirect( "/" + g.get("language") + "/messaging/subscribe/verify/" + subscriber_id, code=302 ) else: current_app.logger.error( "Request to send SMS failed. Response:\n{}".format(response) ) flash( gettext('Error: Try again later, or contact administrator.'), 'error' ) return redirect( "/" + g.get("language") + "/messaging/subscribe/verify/" + subscriber_id, code=302 )
def report_fault(): """ Enables users to directly report faults to the developer. This page displays a fault report form and generates a fault report email from the data it posts to the server. """ # If a post request is made to the url, process the form's data. if request.method == 'POST': # Get the data from the POST request and initialise variables. data = request.form now = datetime.datetime.now().strftime("%I:%M%p on %B %d, %Y") deployment = current_app.config['DEPLOYMENT'] # Create a simple string that displays the submitted data details = "<b>" for key, value in data.items(): details = ''.join( [details, key.capitalize(), ':</b> ', value, '<br/><br/><b>']) # Send an email # TODO: Direct github issue creation if from a personal account. try: hermes('/email', 'PUT', data={ 'email': '*****@*****.**', 'subject': gettext('Fault Report') + ' | {} | {}'.format(deployment, data['url']), 'message': gettext('There was a fault reported at {} in the ' '{} deployment. Here are the details...' '\n\n{}').format(now, deployment, details) }) except Exception as e: logging.warning("Error sending email through hermes...") logging.warning(e) flash( gettext( 'Could not notify developers. Please contact them directly.' ), 'error') abort(502) return render_template('homepage/fault_report_response.html', content=g.config['TECHNICAL_CONFIG'], details=details.replace('\n', '<br/>')) # If a get request is made to the url, display the form elif request.method == 'GET': url = request.args.get('url', '') return render_template('homepage/fault_report_form.html', content=g.config['TECHNICAL_CONFIG'], url=url)
def test_hermes(self, mock_requests): hermes("publish", "POST", {"topics": ["test-topic"]}) headers = { 'content-type': 'application/json', 'authorization': 'Bearer ' } mock_requests.request.assert_called_with( "POST", mk.app.config['HERMES_ROOT'] + "publish", json={'topics': ['test-topic']}, headers=headers)
def get_subscribers(): """ Function that securely uses the server's access to hermes api to extract subscriber data from hermes. If the request went straight from the browsers console to hermes, we would have to give the user direct access to hermes. This is not safe. """ country = current_app.config['MESSAGING_CONFIG']['messages']['country'] subscribers = libs.hermes('/subscribers/'+country, 'GET') return jsonify({'rows': subscribers})
def verify(subscriber_id): """ Subscription Process Stage 3: Verfies contact details for the subscriber ID specified in the URL. If no SMS number is provided, then just landing on this page is enough to verify the users email address (assuming the ID is not guessable). In this case we do a redirect to Stage 4. If the user has already been verified, then we also redirect to stage four with a flash message to remind them that they have already verified. In all other cases we show the SMS verification form. Args: subscriber_id (str): The UUID that is assigned to the subscriber upon creation by Meerkat Hermes. """ # Get the subscriber subscriber = libs.hermes('/subscribe/' + subscriber_id, 'GET') if subscriber['Item']['verified'] is True: flash(gettext('You have already verified your account.')) return redirect( "/" + g.get("language") + '/messaging/subscribe/verified/' + subscriber_id, code=302 ) elif 'sms' not in subscriber['Item']: current_app.logger.warning(str(subscriber['Item'])) libs.hermes('/verify/' + subscriber_id, 'GET') return redirect( "/" + g.get("language") + '/messaging/subscribe/verified/' + subscriber_id ) else: return render_template('messaging/verify.html', content=g.config['MESSAGING_CONFIG'], week=c.api('/epi_week'), data=subscriber['Item'])
def delete_subscribers(): """ Delete the subscribers specified in the post arguments. """ # Load the list of subscribers to be deleted. subscribers = request.get_json() # Try to delete each subscriber, flag up if there is an error error = False for subscriber_id in subscribers: response = libs.hermes('/subscribe/' + subscriber_id, 'DELETE') if response['status'] != 'successful': error = True if error: return "ERROR: There was an error deleting some users." else: return "Users successfully deleted."
def __check_code(subscriber_id, code): """ Checks if the given code for the given subscriber ID is the correct SMS verification code. Args: subscriber_id (str): The UUID that is assigned to the subscriber upon creation by Meerkat Hermes. code (str): The code to be checked. Returns: bool: True if there is a match, False otherwise. """ response = libs.hermes('/verify', 'POST', {'subscriber_id': subscriber_id, 'code': code}) current_app.logger.warning(str(response)) return bool(response['matched'])
def subscribed(): """ Subscription Process Stage 2: Confirms successful subscription request and informs the user of the verification process. This method assembles the HTML form data into a structure Meerkat Hermes understands and then uses the Meerkat Hermes "subscribe" resource to create the subscriber. It further assembles the email and SMS verification messages and uses the Meerkat Hermes to send it out. """ # Convert form immutabledict to dict. data = {} for key in request.form.keys(): key_list = request.form.getlist(key) if(len(key_list) > 1): data[key] = key_list else: data[key] = key_list[0] # Call hermes subscribe method. subscribe_response = libs.hermes('/subscribe', 'PUT', data) # Assemble and send verification email. url = request.url_root + \ g.get("language") + "/messaging/subscribe/verify/" + \ subscribe_response['subscriber_id'] verify_text = gettext(g.config['MESSAGING_CONFIG']['messages'].get( 'verify_text', "Dear {first_name} {last_name} ,\n\n" + "Your subscription to receive public health surveillance " "notifications from {country} has been created or updated. An " "administrator of the system may have done this on your behalf. " "\n\nIn order to receive future notifications, please " "verify your contact details by copying and pasting the following url " "into your address bar: {url}\n" )).format( first_name=data["first_name"], last_name=data["last_name"], country=current_app.config['MESSAGING_CONFIG']['messages']['country'], url=url ) verify_html = gettext(g.config['MESSAGING_CONFIG']['messages'].get( 'verify_html', "<p>Dear {first_name} {last_name},</p>" "<p>Your subscription to receive public health surveillance " "notifications from {country} has been created or updated. " "An administrator of the system may have done this on your " "behalf.</p><p> To receive future notifications, please verify " "your contact details by <a href='{url}' target='_blank'>" "clicking here</a>.</p>" )).format( first_name=data["first_name"], last_name=data["last_name"], country=current_app.config['MESSAGING_CONFIG']['messages']['country'], url=url ) libs.hermes('/email', 'PUT', { 'email': data['email'], 'subject': gettext('Please verify your contact details'), 'message': verify_text, 'html': verify_html, 'from': current_app.config['MESSAGING_CONFIG']['messages']['from'] }) # Set and send sms verification code. if 'sms' in data: __set_code(subscribe_response['subscriber_id'], data['sms']) # Delete the old account if it exists. Inform the user of success. if data.get('id', None): response = libs.hermes('/subscribe/' + data['id'], 'DELETE') if hasattr(response, 'status_code') and response.status_code != 200: flash(gettext( 'Account update failed: invalid ID. ' 'Creating new subscription instead.' )) else: flash( gettext('Subscription updated for ') + data['first_name'] + " " + data['last_name'] + "." ) return render_template('messaging/subscribed.html', content=g.config['MESSAGING_CONFIG'], week=c.api('/epi_week'), data=data)
def verified(subscriber_id): """ Subscription Process Stage 4: Confirms that the users details has been verified, and sends out a confirmation email as well. Args: subscriber_id (str): The UUID that is assigned to the subscriber upon creation by Meerkat Hermes. """ # Get the subscriber subscriber = libs.hermes('/subscribe/' + subscriber_id, 'GET')['Item'] # If the subscriber isn't verified redirect to the verify stage. if not subscriber['verified']: return redirect( '/' + g.get("language") + '/messaging/subscribe/verify/' + subscriber_id, code=302 ) country = current_app.config['MESSAGING_CONFIG']['messages']['country'] # Send a confirmation e-mail with the unsubscribe link. confirmation_text = gettext(g.config['MESSAGING_CONFIG']['messages'].get( 'confirmation_text', "Dear {first_name} {last_name},\n\n" "Thank you for subscribing to receive public health surveillance " "notifications from {country}. We can confirm that your contact " "details have been successfully verified.\n\nYou can unsubscribe at " "any time by clicking on the relevant link in your e-mails.\n\n If " "you wish to unsubscribe now copy and paste the following url into " "your address bar:\n{url}/unsubscribe/{subscriber_id}" )).format( first_name=subscriber["first_name"], last_name=subscriber["last_name"], country=country, url=current_app.config["HERMES_ROOT"], subscriber_id=subscriber_id ) confirmation_html = gettext(g.config['MESSAGING_CONFIG']['messages'].get( 'confirmation_html', "<p>Dear {first_name} {last_name},</p>" "<p>Thank you for subscribing to receive public health surveillance " "notifications from {country}. We can confirm that your contact " "details have been successfully verified.</p><p>You can unsubscribe " "at any time by clicking on the relevant link in your e-mails.</p><p> " "If you wish to unsubscribe now " "<a href='{url}/unsubscribe/{subscriber_id}'>click here.</a></p>" )).format( first_name=subscriber["first_name"], last_name=subscriber["last_name"], country=country, url=current_app.config["HERMES_ROOT"], subscriber_id=subscriber_id ) email = { 'email': subscriber['email'], 'subject': gettext("Your subscription has been successful"), 'message': confirmation_text, 'html': confirmation_html, 'from': current_app.config['MESSAGING_CONFIG']['messages']['from'] } email_response = libs.hermes('/email', 'PUT', email) current_app.logger.warning('Response is: ' + str(email_response)) return render_template('messaging/verified.html', content=g.config['MESSAGING_CONFIG'], week=c.api('/epi_week'))
def send_email_report(report, location=None, end_date=None, start_date=None): """Sends an email via Hermes with the latest report. Args: report (str): The report ID, from the REPORTS_LIST configuration file parameter. """ report_list = current_app.config['REPORTS_CONFIG']['report_list'] country = current_app.config['MESSAGING_CONFIG']['messages']['country'] topic = current_app.config['MESSAGING_CONFIG']['subscribe'][ 'topic_prefix'] + report # TESTING # If report requested begins with "test_" then send to the test topic # E.g. test_communicable_diseases would send cd report to "test-emails" test_id = "" test_sub = "" if report.startswith("test_"): report = report[5:] topic = "test-emails" test_id = "-" + str(datetime.now().time().isoformat()) test_sub = "TEST {} | ".format(current_app.config['DEPLOYMENT']) if validate_report_arguments(current_app.config, report, location, end_date, start_date): app.logger.debug("creating report") ret = create_report(config=current_app.config, report=report, location=location, end_date=end_date, start_date=start_date) relative_url = url_for(report_list[report].get('email_report_format', 'reports.report'), report=report, location=location, end_date=end_date, start_date=start_date) report_url = current_app.config['LIVE_URL'] + relative_url # Use meerkat_auth to authenticate the email's report link # Only do this if an access account is specified for the email email_access = report_list.get(report, {}).get('email_access_account', {}) if email_access: app.logger.warning('Authenticating') token = authenticate(email_access.get('username'), email_access.get('password')) if token: report_url += "?meerkat_jwt=" + str(token) # Use env variable to determine whether to fetch image content from external source or not if int(current_app.config['PDFCROWD_USE_EXTERNAL_STATIC_FILES']) == 1: content_url = current_app.config['PDFCROWD_STATIC_FILE_URL'] else: content_url = current_app.config['LIVE_URL'] + 'static/' html_email_body = render_template(ret['template_email_html'], report=ret['report'], extras=ret['extras'], address=ret['address'], content=g.config['REPORTS_CONFIG'], report_url=report_url, content_url=content_url) plain_email_body = render_template(ret['template_email_plain'], report=ret['report'], extras=ret['extras'], address=ret['address'], content=g.config['REPORTS_CONFIG'], report_url=report_url, content_url=content_url) epi_week = ret['report']['data']['epi_week_num'] start_date = datetime_from_json(ret['report']['data']['start_date']) end_date = datetime_from_json(ret['report']['data']['end_date']) if report_list[report]['default_period'] == 'month': subject = '{test_subject}{country} | {title} ({start_date} - {end_date})'.format( test_subject=test_sub, country=gettext(country), title=gettext(report_list[report]['monthly_email_title']), start_date=format_datetime(start_date, 'dd MMMM YYYY'), end_date=format_datetime(end_date, 'dd MMMM YYYY')) email_id = ''.join([ topic, "-", end_date.strftime('%m'), "-", end_date.strftime('%Y'), "-", report, test_id ]) else: subject = '{test_subject}{country} | {title} {epi_week_text} {epi_week} ({start_date} - {end_date})'.format( test_subject=test_sub, country=gettext(country), title=gettext(report_list[report]['title']), epi_week_text=gettext('Epi Week'), epi_week=epi_week, start_date=format_datetime(start_date, 'dd MMMM YYYY'), end_date=format_datetime(end_date, 'dd MMMM YYYY')) email_id = ''.join([ topic, "-", str(epi_week), "-", end_date.strftime('%Y'), "-", report, test_id ]) # Assemble the message data in a manner hermes will understand. message = { "id": email_id, "topics": topic, "html-message": html_email_body, "message": plain_email_body, "subject": subject, "from": current_app.config['MESSAGING_CONFIG']['messages']['from'] } # Publish the message to hermes r = hermes('/publish', 'PUT', message) print(r) succ = 0 fail = 0 for resp in r: try: resp['ResponseMetadata']['HTTPStatusCode'] except KeyError: current_app.logger.warning("Hermes return value error:" + str(resp['message'])) fail += 1 except TypeError: current_app.logger.warning("Hermes job error:" + str(r['message'])) abort(502) else: if resp['ResponseMetadata']['HTTPStatusCode'] == 200: succ += 1 else: current_app.logger.warning( "Hermes error while sending message:" + str(resp['message'])) fail += 1 return '\nSending {succ} messages succeeded, {fail} messages failed\n\n'.format( succ=succ, fail=fail) else: current_app.logger.warning("Aborting. Report doesn't exist: " + str(report)) abort(501)