Chromium Code Reviews| Index: mailer.py |
| diff --git a/mailer.py b/mailer.py |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..ef15f868b73119c1015a835315b3fd0993915430 |
| --- /dev/null |
| +++ b/mailer.py |
| @@ -0,0 +1,149 @@ |
| +import datetime |
| +from google.appengine.api import mail |
| +import hashlib |
| +import hmac |
| +import logging |
| +import json |
| +import webapp2 |
| +from webapp2_extras import jinja2 |
| + |
| +import gatekeeper_mailer |
| + |
| + |
| +class BaseHandler(webapp2.RequestHandler): |
| + """Provide a cached Jinja environment to each request.""" |
| + @webapp2.cached_property |
| + def jinja2(self): |
| + # Returns a Jinja2 renderer cached in the app registry. |
| + return jinja2.get_jinja2(app=self.app) |
| + |
| + def render_response(self, _template, **context): |
| + # Renders a template and writes the result to the response. |
| + rv = self.jinja2.render_template(_template, **context) |
| + self.response.write(rv) |
| + |
| + |
| +class MainPage(BaseHandler): |
| + def get(self): |
| + context = {'title': 'Chromium Gatekeeper Mailer'} |
| + self.render_response('main_mailer.html', **context) |
| + |
| + |
| +class Email(BaseHandler): |
| + @staticmethod |
| + def _validate_message(message, secret): |
| + # Make this backwards compatable with python 2.6, since total_seconds() |
| + # exists only in python 2.7. |
| + utc_now_td = (datetime.datetime.utcnow() - |
| + datetime.datetime.utcfromtimestamp(0)) |
| + utc_now = utc_now_td.days * 86400 + utc_now_td.seconds |
| + |
| + if (utc_now < message['time']) or (utc_now - message['time'] > 60): |
| + logging.error('message was rejected due to time') |
| + return False |
| + |
| + hasher = hmac.new(secret, '%s:%d:%d' % (message['message'], |
| + message['time'], |
| + message['salt']), |
| + hashlib.sha256) |
| + |
| + client_hash = hasher.hexdigest() |
| + |
| + return client_hash == message['sha256'] |
| + |
| + |
| + @staticmethod |
| + def _verify_json(build_data): |
| + fields = ['waterfall_url', |
| + 'build_url', |
| + 'project_name', |
| + 'builderName', |
| + 'steps', |
| + 'unsatisfied', |
| + 'revisions', |
| + 'blamelist', |
| + 'result', |
| + 'number', |
| + 'changes', |
| + 'reason', |
| + 'recipients'] |
| + |
| + for field in fields: |
| + if field not in build_data: |
| + logging.error('build_data did not contain field %s' % field) |
| + return False |
| + |
| + stepfields = ['started', |
| + 'text', |
| + 'results', |
| + 'name', |
| + 'logs', |
| + 'urls'] |
| + |
| + if not build_data['steps']: |
| + logging.error('build_data did not contain any steps') |
| + return False |
| + for step in build_data['steps']: |
| + for field in stepfields: |
| + if field not in step: |
| + logging.error('build_step did not contain field %s' % field) |
| + return False |
| + |
| + return True |
| + |
| + def post(self): |
| + blob = self.request.get('json') |
| + if not blob: |
| + self.response.out.write('no json data sent') |
| + logging.error('error no json sent') |
| + self.error(400) |
| + return |
| + |
| + message = {} |
| + try: |
| + message = json.loads(blob) |
| + except ValueError as e: |
| + self.response.out.write('couldn\'t decode json') |
| + logging.error('error decoding incoming json: %s' % e) |
| + self.error(400) |
| + return |
| + |
| + if not self._validate_message(message, 'pajamas'): |
|
agable
2013/08/01 17:52:44
Nice secret :)
|
| + self.response.out.write('unauthorized') |
| + logging.error('incoming message did not validate') |
| + self.error(403) |
| + return |
| + |
| + build_data = json.loads(message['message']) |
| + |
| + if not self._verify_json(build_data): |
| + logging.error('error verifying incoming json: %s' % build_data) |
| + self.response.out.write('json build format is incorrect') |
|
agable
2013/08/01 17:52:44
Could be a more informative error message -- what
|
| + self.error(400) |
| + return |
| + |
| + from_addr = build_data.get('from_addr', 'buildbot@chromium.org') |
|
agable
2013/08/01 17:52:44
Does sending as this address work, or does appengi
Mike Stip (use stip instead)
2013/08/29 19:56:46
it doesn't, fixed
|
| + recipients = ', '.join(build_data['recipients']) |
| + |
| + template = gatekeeper_mailer.MailTemplate(build_data['waterfall_url'], |
| + build_data['build_url'], |
| + build_data['project_name'], |
| + from_addr) |
| + |
| + |
| + text_content, html_content, subject = template.genMessageContent(build_data) |
| + |
| + message = mail.EmailMessage(sender=from_addr, |
| + subject=subject, |
| + #to=recipients, |
| + to=['xusydoc@chromium.org'], |
| + body=text_content, |
| + html=html_content) |
| + logging.info('sending email to %s', recipients) |
| + message.send() |
| + self.response.out.write('email sent') |
| + |
| + |
| +app = webapp2.WSGIApplication([('/mailer', MainPage), |
| + ('/mailer/email', Email)], |
| + debug=True) |