OLD | NEW |
---|---|
(Empty) | |
1 import datetime | |
2 from google.appengine.api import mail | |
3 import hashlib | |
4 import hmac | |
5 import logging | |
6 import json | |
7 import webapp2 | |
8 from webapp2_extras import jinja2 | |
9 | |
10 import gatekeeper_mailer | |
11 | |
12 | |
13 class BaseHandler(webapp2.RequestHandler): | |
14 """Provide a cached Jinja environment to each request.""" | |
15 @webapp2.cached_property | |
16 def jinja2(self): | |
17 # Returns a Jinja2 renderer cached in the app registry. | |
18 return jinja2.get_jinja2(app=self.app) | |
19 | |
20 def render_response(self, _template, **context): | |
21 # Renders a template and writes the result to the response. | |
22 rv = self.jinja2.render_template(_template, **context) | |
23 self.response.write(rv) | |
24 | |
25 | |
26 class MainPage(BaseHandler): | |
27 def get(self): | |
28 context = {'title': 'Chromium Gatekeeper Mailer'} | |
29 self.render_response('main_mailer.html', **context) | |
30 | |
31 | |
32 class Email(BaseHandler): | |
33 @staticmethod | |
34 def _validate_message(message, secret): | |
35 # Make this backwards compatable with python 2.6, since total_seconds() | |
36 # exists only in python 2.7. | |
37 utc_now_td = (datetime.datetime.utcnow() - | |
38 datetime.datetime.utcfromtimestamp(0)) | |
39 utc_now = utc_now_td.days * 86400 + utc_now_td.seconds | |
40 | |
41 if (utc_now < message['time']) or (utc_now - message['time'] > 60): | |
42 logging.error('message was rejected due to time') | |
43 return False | |
44 | |
45 hasher = hmac.new(secret, '%s:%d:%d' % (message['message'], | |
46 message['time'], | |
47 message['salt']), | |
48 hashlib.sha256) | |
49 | |
50 client_hash = hasher.hexdigest() | |
51 | |
52 return client_hash == message['sha256'] | |
53 | |
54 | |
55 @staticmethod | |
56 def _verify_json(build_data): | |
57 fields = ['waterfall_url', | |
58 'build_url', | |
59 'project_name', | |
60 'builderName', | |
61 'steps', | |
62 'unsatisfied', | |
63 'revisions', | |
64 'blamelist', | |
65 'result', | |
66 'number', | |
67 'changes', | |
68 'reason', | |
69 'recipients'] | |
70 | |
71 for field in fields: | |
72 if field not in build_data: | |
73 logging.error('build_data did not contain field %s' % field) | |
74 return False | |
75 | |
76 stepfields = ['started', | |
77 'text', | |
78 'results', | |
79 'name', | |
80 'logs', | |
81 'urls'] | |
82 | |
83 if not build_data['steps']: | |
84 logging.error('build_data did not contain any steps') | |
85 return False | |
86 for step in build_data['steps']: | |
87 for field in stepfields: | |
88 if field not in step: | |
89 logging.error('build_step did not contain field %s' % field) | |
90 return False | |
91 | |
92 return True | |
93 | |
94 def post(self): | |
95 blob = self.request.get('json') | |
96 if not blob: | |
97 self.response.out.write('no json data sent') | |
98 logging.error('error no json sent') | |
99 self.error(400) | |
100 return | |
101 | |
102 message = {} | |
103 try: | |
104 message = json.loads(blob) | |
105 except ValueError as e: | |
106 self.response.out.write('couldn\'t decode json') | |
107 logging.error('error decoding incoming json: %s' % e) | |
108 self.error(400) | |
109 return | |
110 | |
111 if not self._validate_message(message, 'pajamas'): | |
agable
2013/08/01 17:52:44
Nice secret :)
| |
112 self.response.out.write('unauthorized') | |
113 logging.error('incoming message did not validate') | |
114 self.error(403) | |
115 return | |
116 | |
117 build_data = json.loads(message['message']) | |
118 | |
119 if not self._verify_json(build_data): | |
120 logging.error('error verifying incoming json: %s' % build_data) | |
121 self.response.out.write('json build format is incorrect') | |
agable
2013/08/01 17:52:44
Could be a more informative error message -- what
| |
122 self.error(400) | |
123 return | |
124 | |
125 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
| |
126 recipients = ', '.join(build_data['recipients']) | |
127 | |
128 template = gatekeeper_mailer.MailTemplate(build_data['waterfall_url'], | |
129 build_data['build_url'], | |
130 build_data['project_name'], | |
131 from_addr) | |
132 | |
133 | |
134 text_content, html_content, subject = template.genMessageContent(build_data) | |
135 | |
136 message = mail.EmailMessage(sender=from_addr, | |
137 subject=subject, | |
138 #to=recipients, | |
139 to=['xusydoc@chromium.org'], | |
140 body=text_content, | |
141 html=html_content) | |
142 logging.info('sending email to %s', recipients) | |
143 message.send() | |
144 self.response.out.write('email sent') | |
145 | |
146 | |
147 app = webapp2.WSGIApplication([('/mailer', MainPage), | |
148 ('/mailer/email', Email)], | |
149 debug=True) | |
OLD | NEW |