Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(514)

Side by Side Diff: mailer.py

Issue 19878007: Add build mailer capability to support gatekeeper_ng. (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/chromium-build@master
Patch Set: Adds datastore-based auth. Created 7 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 import datetime
2 from google.appengine.ext import db
Vadim Sh. 2013/08/26 21:30:41 ndb? It will speed up secret fetch by automagica
Mike Stip (use stip instead) 2013/08/29 19:56:46 Done.
3 from google.appengine.api import mail
4 import hashlib
5 import hmac
Vadim Sh. 2013/08/26 21:30:41 nit: reorganize imports order: 1. stdlib. 2. other
Mike Stip (use stip instead) 2013/08/29 19:56:46 Done.
6 import logging
7 import json
8 import webapp2
9 from webapp2_extras import jinja2
10
11 import gatekeeper_mailer
12
13
14 class BaseHandler(webapp2.RequestHandler):
15 """Provide a cached Jinja environment to each request."""
16 @webapp2.cached_property
17 def jinja2(self):
18 # Returns a Jinja2 renderer cached in the app registry.
19 return jinja2.get_jinja2(app=self.app)
20
21 def render_response(self, _template, **context):
22 # Renders a template and writes the result to the response.
23 rv = self.jinja2.render_template(_template, **context)
24 self.response.write(rv)
25
26
27 class MainPage(BaseHandler):
28 def get(self):
29 context = {'title': 'Chromium Gatekeeper Mailer'}
30 self.render_response('main_mailer.html', **context)
31
32 class MailerSecret(db.Model):
Vadim Sh. 2013/08/26 21:30:41 nit: Move this class to the top of the page before
Mike Stip (use stip instead) 2013/08/29 19:56:46 Done.
33 """Model to represent the shared secret for the mail endpoint."""
34 secret = db.StringProperty()
35
36 class Email(BaseHandler):
37 @staticmethod
38 def _validate_message(message, secret):
39 # Make this backwards compatable with python 2.6, since total_seconds()
40 # exists only in python 2.7.
Vadim Sh. 2013/08/26 21:30:41 Is this comment still relevant?
Mike Stip (use stip instead) 2013/08/29 19:56:46 nope
41 utc_now_td = (datetime.datetime.utcnow() -
42 datetime.datetime.utcfromtimestamp(0))
43 utc_now = utc_now_td.days * 86400 + utc_now_td.seconds
Vadim Sh. 2013/08/26 21:30:41 And I think all this can be reduced to time.time()
Mike Stip (use stip instead) 2013/08/29 19:56:46 Done.
44
45 if (utc_now < message['time']) or (utc_now - message['time'] > 60):
Vadim Sh. 2013/08/26 21:30:41 abs(utc_now - message['time']) > 60 clock skew ca
Mike Stip (use stip instead) 2013/08/29 19:56:46 Done.
46 logging.error('message was rejected due to time')
47 return False
48
49 hasher = hmac.new(secret, '%s:%d:%d' % (message['message'],
50 message['time'],
51 message['salt']),
52 hashlib.sha256)
53
54 client_hash = hasher.hexdigest()
55
56 return client_hash == message['sha256']
Vadim Sh. 2013/08/26 21:30:41 Usually this comparison is done using constant tim
iannucci 2013/08/27 21:45:20 ((Actually it can make a difference. I've used a p
Mike Stip (use stip instead) 2013/08/29 19:56:46 Done.
57
58
59 @staticmethod
60 def _verify_json(build_data):
61 fields = ['waterfall_url',
62 'build_url',
63 'project_name',
64 'builderName',
65 'steps',
66 'unsatisfied',
67 'revisions',
68 'blamelist',
69 'result',
70 'number',
71 'changes',
72 'reason',
73 'recipients']
74
75 for field in fields:
76 if field not in build_data:
77 logging.error('build_data did not contain field %s' % field)
78 return False
79
80 stepfields = ['started',
Vadim Sh. 2013/08/26 21:30:41 nit: step_fields
Mike Stip (use stip instead) 2013/08/29 19:56:46 Done.
81 'text',
82 'results',
83 'name',
84 'logs',
85 'urls']
86
87 if not build_data['steps']:
88 logging.error('build_data did not contain any steps')
89 return False
90 for step in build_data['steps']:
91 for field in stepfields:
92 if field not in step:
93 logging.error('build_step did not contain field %s' % field)
94 return False
95
96 return True
97
98 def post(self):
99 blob = self.request.get('json')
Vadim Sh. 2013/08/26 21:30:41 I'm not sure what part of the system is sending th
Mike Stip (use stip instead) 2013/08/29 19:56:46 that's scripts/slave/gatekeeper_ng.py. I'll make a
100 if not blob:
101 self.response.out.write('no json data sent')
102 logging.error('error no json sent')
103 self.error(400)
104 return
105
106 message = {}
107 try:
108 message = json.loads(blob)
109 except ValueError as e:
110 self.response.out.write('couldn\'t decode json')
111 logging.error('error decoding incoming json: %s' % e)
112 self.error(400)
113 return
114
115 secret = str(MailerSecret.get_or_insert('mailer_secret').secret)
Vadim Sh. 2013/08/26 21:30:41 Why 'str' here? I'm thinking about a possibility o
Mike Stip (use stip instead) 2013/08/29 19:56:46 stuff from db came in as unicode, while stuff from
116 if not secret:
117 self.response.out.write('unauthorized')
118 logging.critical('mailer shared secret has not been set!')
119 self.error(500)
120 return
121
122 if not self._validate_message(message, secret):
123 self.response.out.write('unauthorized')
124 logging.error('incoming message did not validate')
125 self.error(403)
126 return
127
128 build_data = json.loads(message['message'])
Vadim Sh. 2013/08/26 21:30:41 Catch ValueError here as well?
Mike Stip (use stip instead) 2013/08/29 19:56:46 Done.
129
130 if not self._verify_json(build_data):
131 logging.error('error verifying incoming json: %s' % build_data)
132 self.response.out.write('json build format is incorrect')
133 self.error(400)
134 return
135
136 from_addr = build_data.get('from_addr', 'buildbot@chromium.org')
137 recipients = ', '.join(build_data['recipients'])
138
139 template = gatekeeper_mailer.MailTemplate(build_data['waterfall_url'],
140 build_data['build_url'],
141 build_data['project_name'],
142 from_addr)
143
144
145 text_content, html_content, subject = template.genMessageContent(build_data)
146
147 message = mail.EmailMessage(sender=from_addr,
148 subject=subject,
149 #to=recipients,
150 to=['xusydoc@chromium.org'],
151 body=text_content,
152 html=html_content)
153 logging.info('sending email to %s', recipients)
154 message.send()
155 self.response.out.write('email sent')
156
157
158 app = webapp2.WSGIApplication([('/mailer', MainPage),
159 ('/mailer/email', Email)],
160 debug=True)
OLDNEW
« gatekeeper_mailer.py ('K') | « gatekeeper_mailer.py ('k') | templates/base_mailer.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698