Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: modules/rietveld/files/wrapper.py

Issue 29341151: Issue 4019 - Added "Edge" to platform choices in Issues tracker at issues1. (Closed)
Patch Set: Created May 10, 2016, 3:35 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « modules/private-stub/hiera/hosts.yaml ('k') | modules/sitescripts/manifests/init.pp » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 2
3 from ConfigParser import SafeConfigParser 3 from ConfigParser import SafeConfigParser
4 import hashlib 4 import hashlib
5 import hmac 5 import hmac
6 import json 6 import json
7 import os 7 import os
8 import re 8 import re
9 import sys 9 import sys
10 import urllib 10 import urllib
11 11
12 OAUTH2_AUTHURL = 'https://accounts.google.com/o/oauth2/auth' 12 OAUTH2_AUTHURL = 'https://accounts.google.com/o/oauth2/auth'
13 OAUTH2_TOKENURL = 'https://accounts.google.com/o/oauth2/token' 13 OAUTH2_TOKENURL = 'https://accounts.google.com/o/oauth2/token'
14 OAUTH2_DATAURL = 'https://www.googleapis.com/plus/v1/people/me' 14 OAUTH2_DATAURL = 'https://www.googleapis.com/plus/v1/people/me'
15 OAUTH2_SCOPE = 'email' 15 OAUTH2_SCOPE = 'email'
16 16
17 OAUTH2_TOKEN_EXPIRATION = 5 * 60 17 OAUTH2_TOKEN_EXPIRATION = 5 * 60
18 18
19
20 def setup_paths(engine_dir): 19 def setup_paths(engine_dir):
21 sys.path.append(engine_dir) 20 sys.path.append(engine_dir)
22 21
23 import wrapper_util 22 import wrapper_util
24 paths = wrapper_util.Paths(engine_dir) 23 paths = wrapper_util.Paths(engine_dir)
25 script_name = os.path.basename(__file__) 24 script_name = os.path.basename(__file__)
26 sys.path[0:0] = paths.script_paths(script_name) 25 sys.path[0:0] = paths.script_paths(script_name)
27 return script_name, paths.script_file(script_name) 26 return script_name, paths.script_file(script_name)
28
29 27
30 def adjust_server_id(): 28 def adjust_server_id():
31 from google.appengine.tools.devappserver2 import http_runtime_constants 29 from google.appengine.tools.devappserver2 import http_runtime_constants
32 http_runtime_constants.SERVER_SOFTWARE = 'Production/2.0' 30 http_runtime_constants.SERVER_SOFTWARE = 'Production/2.0'
33
34 31
35 def fix_request_scheme(): 32 def fix_request_scheme():
36 from google.appengine.runtime.wsgi import WsgiRequest 33 from google.appengine.runtime.wsgi import WsgiRequest
37 orig_init = WsgiRequest.__init__ 34 orig_init = WsgiRequest.__init__
38 35 def __init__(self, *args):
39 def __init__(self, *args): 36 orig_init(self, *args)
40 orig_init(self, *args) 37 self._environ['wsgi.url_scheme'] = self._environ.get('HTTP_X_FORWARDED_PROTO ', 'http')
41 self._environ['wsgi.url_scheme'] = self._environ.get('HTTP_X_FORWARDED_P ROTO', 'http') 38 self._environ['HTTPS'] = 'on' if self._environ['wsgi.url_scheme'] == 'https' else 'off'
42 self._environ['HTTPS'] = 'on' if self._environ['wsgi.url_scheme'] == 'ht tps' else 'off' 39 WsgiRequest.__init__ = __init__
43 WsgiRequest.__init__ = __init__
44
45 40
46 def read_config(path): 41 def read_config(path):
47 config = SafeConfigParser() 42 config = SafeConfigParser()
48 config.read(path) 43 config.read(path)
49 return config 44 return config
50
51 45
52 def set_storage_path(storage_path): 46 def set_storage_path(storage_path):
53 sys.argv.extend(['--storage_path', storage_path]) 47 sys.argv.extend(['--storage_path', storage_path])
54
55 48
56 def replace_runtime(): 49 def replace_runtime():
57 from google.appengine.tools.devappserver2 import python_runtime 50 from google.appengine.tools.devappserver2 import python_runtime
58 runtime_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '_py thon_runtime.py') 51 runtime_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '_pyth on_runtime.py')
59 python_runtime._RUNTIME_PATH = runtime_path 52 python_runtime._RUNTIME_PATH = runtime_path
60 python_runtime._RUNTIME_ARGS = [sys.executable, runtime_path] 53 python_runtime._RUNTIME_ARGS = [sys.executable, runtime_path]
61
62 54
63 def protect_cookies(cookie_secret): 55 def protect_cookies(cookie_secret):
64 from google.appengine.tools.devappserver2 import login 56 from google.appengine.tools.devappserver2 import login
65 57
66 def calculate_signature(message): 58 def calculate_signature(message):
67 return hmac.new(cookie_secret, message, hashlib.sha256).hexdigest() 59 return hmac.new(cookie_secret, message, hashlib.sha256).hexdigest()
68 60
69 def _get_user_info_from_dict(cookie_dict, cookie_name=login._COOKIE_NAME): 61 def _get_user_info_from_dict(cookie_dict, cookie_name=login._COOKIE_NAME):
70 cookie_value = cookie_dict.get(cookie_name, '') 62 cookie_value = cookie_dict.get(cookie_name, '')
71 63
72 email, admin, user_id, signature = (cookie_value.split(':') + ['', '', ' ', ''])[:4] 64 email, admin, user_id, signature = (cookie_value.split(':') + ['', '', '', ' '])[:4]
73 if '@' not in email or signature != calculate_signature(':'.join([email, admin, user_id])): 65 if '@' not in email or signature != calculate_signature(':'.join([email, adm in, user_id])):
74 return '', False, '' 66 return '', False, ''
75 return email, (admin == 'True'), user_id 67 return email, (admin == 'True'), user_id
76 login._get_user_info_from_dict = _get_user_info_from_dict 68 login._get_user_info_from_dict = _get_user_info_from_dict
77 69
78 orig_create_cookie_data = login._create_cookie_data 70 orig_create_cookie_data = login._create_cookie_data
79 71 def _create_cookie_data(email, admin):
80 def _create_cookie_data(email, admin): 72 result = orig_create_cookie_data(email, admin)
81 result = orig_create_cookie_data(email, admin) 73 result += ':' + calculate_signature(result)
82 result += ':' + calculate_signature(result) 74 return result
83 return result 75 login._create_cookie_data = _create_cookie_data
84 login._create_cookie_data = _create_cookie_data
85
86 76
87 def enable_oauth2(client_id, client_secret, admins): 77 def enable_oauth2(client_id, client_secret, admins):
88 from google.appengine.tools.devappserver2 import login 78 from google.appengine.tools.devappserver2 import login
89 79
90 def request(method, url, data): 80 def request(method, url, data):
91 if method != 'POST': 81 if method != 'POST':
92 url += '?' + urllib.urlencode(data) 82 url += '?' + urllib.urlencode(data)
93 data = None 83 data = None
94 else: 84 else:
95 data = urllib.urlencode(data) 85 data = urllib.urlencode(data)
96 response = urllib.urlopen(url, data) 86 response = urllib.urlopen(url, data)
97 try: 87 try:
98 return json.loads(response.read()) 88 return json.loads(response.read())
99 finally: 89 finally:
100 response.close() 90 response.close()
101 91
102 token_cache = {} 92 token_cache = {}
103 93 def get_user_info(access_token):
104 def get_user_info(access_token): 94 email, is_admin, expiration = token_cache.get(access_token, (None, None, 0))
105 email, is_admin, expiration = token_cache.get(access_token, (None, None, 0)) 95 now = time.mktime(time.gmtime())
106 now = time.mktime(time.gmtime()) 96 if now > expiration:
97 get_params = {
98 'access_token': access_token,
99 }
100 data = request('GET', OAUTH2_DATAURL, get_params)
101 emails = [e for e in data.get('emails') if e['type'] == 'account']
102 if not emails:
103 return None, None
104
105 email = emails[0]['value']
106 is_admin = email in admins
107
108 for token, (_, _, expiration) in token_cache.items():
107 if now > expiration: 109 if now > expiration:
108 get_params = { 110 del token_cache[token]
109 'access_token': access_token, 111 token_cache[access_token] = (email, is_admin, now + OAUTH2_TOKEN_EXPIRATIO N)
110 } 112 return email, is_admin
111 data = request('GET', OAUTH2_DATAURL, get_params) 113
112 emails = [e for e in data.get('emails') if e['type'] == 'account'] 114 def get(self):
113 if not emails: 115 def error(text):
114 return None, None 116 self.response.status = 200
115 117 self.response.headers['Content-Type'] = 'text/plain'
116 email = emails[0]['value'] 118 self.response.write(text.encode('utf-8'))
117 is_admin = email in admins 119
118 120 def redirect(url):
119 for token, (_, _, expiration) in token_cache.items(): 121 self.response.status = 302
120 if now > expiration: 122 self.response.status_message = 'Found'
121 del token_cache[token] 123 self.response.headers['Location'] = url.encode('utf-8')
122 token_cache[access_token] = (email, is_admin, now + OAUTH2_TOKEN_EXP IRATION) 124
123 return email, is_admin 125 def logout(continue_url):
124 126 self.response.headers['Set-Cookie'] = login._clear_user_info_cookie()
125 def get(self): 127 redirect(continue_url)
126 def error(text): 128
127 self.response.status = 200 129 def login_step1(continue_url):
128 self.response.headers['Content-Type'] = 'text/plain' 130 # See https://stackoverflow.com/questions/10271110/python-oauth2-login-wit h-google
129 self.response.write(text.encode('utf-8')) 131 authorize_params = {
130 132 'response_type': 'code',
131 def redirect(url): 133 'client_id': client_id,
132 self.response.status = 302 134 'redirect_uri': base_url + login.LOGIN_URL_RELATIVE,
133 self.response.status_message = 'Found' 135 'scope': OAUTH2_SCOPE,
134 self.response.headers['Location'] = url.encode('utf-8') 136 'state': continue_url,
135 137 }
136 def logout(continue_url): 138 redirect(OAUTH2_AUTHURL + '?' + urllib.urlencode(authorize_params))
137 self.response.headers['Set-Cookie'] = login._clear_user_info_cookie( ) 139
138 redirect(continue_url) 140 def login_step2(code, continue_url):
139 141 token_params = {
140 def login_step1(continue_url): 142 'code': code,
141 # See https://stackoverflow.com/questions/10271110/python-oauth2-log in-with-google 143 'client_id': client_id,
142 authorize_params = { 144 'client_secret': client_secret,
143 'response_type': 'code', 145 'redirect_uri': base_url + login.LOGIN_URL_RELATIVE,
144 'client_id': client_id, 146 'grant_type':'authorization_code',
145 'redirect_uri': base_url + login.LOGIN_URL_RELATIVE, 147 }
146 'scope': OAUTH2_SCOPE, 148 data = request('POST', OAUTH2_TOKENURL, token_params)
147 'state': continue_url, 149 token = data.get('access_token')
148 } 150 if not token:
149 redirect(OAUTH2_AUTHURL + '?' + urllib.urlencode(authorize_params)) 151 error('No token in response: ' + str(data))
150 152 return
151 def login_step2(code, continue_url): 153
152 token_params = { 154 email, is_admin = get_user_info(token)
153 'code': code, 155 if not email:
154 'client_id': client_id, 156 error('No email address in response: ' + str(data))
155 'client_secret': client_secret, 157 return
156 'redirect_uri': base_url + login.LOGIN_URL_RELATIVE, 158 self.response.headers['Set-Cookie'] = login._set_user_info_cookie(email, i s_admin)
157 'grant_type': 'authorization_code', 159 redirect(continue_url)
158 } 160
159 data = request('POST', OAUTH2_TOKENURL, token_params) 161 action = self.request.get(login.ACTION_PARAM)
160 token = data.get('access_token') 162 continue_url = self.request.get(login.CONTINUE_PARAM)
161 if not token: 163 continue_url = re.sub(r'^http:', 'https:', continue_url)
162 error('No token in response: ' + str(data)) 164 base_url = 'https://%s/' % self.request.environ['HTTP_HOST']
163 return 165
164 166 if action.lower() == login.LOGOUT_ACTION.lower():
165 email, is_admin = get_user_info(token) 167 logout(continue_url or base_url)
166 if not email: 168 elif self.request.get('error'):
167 error('No email address in response: ' + str(data)) 169 error('Authorization failed: ' + self.request.get('error'))
168 return 170 else:
169 self.response.headers['Set-Cookie'] = login._set_user_info_cookie(em ail, is_admin) 171 code = self.request.get('code')
170 redirect(continue_url) 172 if code:
171 173 login_step2(code, self.request.get('state') or base_url)
172 action = self.request.get(login.ACTION_PARAM) 174 else:
173 continue_url = self.request.get(login.CONTINUE_PARAM) 175 login_step1(continue_url or base_url)
174 continue_url = re.sub(r'^http:', 'https:', continue_url) 176
175 base_url = 'https://%s/' % self.request.environ['HTTP_HOST'] 177 login.Handler.get = get
176 178
177 if action.lower() == login.LOGOUT_ACTION.lower(): 179 from google.appengine.api import user_service_stub, user_service_pb
178 logout(continue_url or base_url) 180 from google.appengine.runtime import apiproxy_errors
179 elif self.request.get('error'): 181 def _Dynamic_GetOAuthUser(self, request, response, request_id):
180 error('Authorization failed: ' + self.request.get('error')) 182 environ = self.request_data.get_request_environ(request_id)
181 else: 183 match = re.search(r'^OAuth (\S+)', environ.get('HTTP_AUTHORIZATION', ''))
182 code = self.request.get('code') 184 if not match:
183 if code: 185 raise apiproxy_errors.ApplicationError(
184 login_step2(code, self.request.get('state') or base_url) 186 user_service_pb.UserServiceError.OAUTH_INVALID_REQUEST)
185 else: 187
186 login_step1(continue_url or base_url) 188 email, is_admin = get_user_info(match.group(1))
187 189 if not email:
188 login.Handler.get = get 190 raise apiproxy_errors.ApplicationError(
189 191 user_service_pb.UserServiceError.OAUTH_INVALID_TOKEN)
190 from google.appengine.api import user_service_stub, user_service_pb 192
191 from google.appengine.runtime import apiproxy_errors 193 # User ID is based on email address, see appengine.tools.devappserver2.login
192 194 user_id_digest = hashlib.md5(email.lower()).digest()
193 def _Dynamic_GetOAuthUser(self, request, response, request_id): 195 user_id = '1' + ''.join(['%02d' % ord(x) for x in user_id_digest])[:20]
194 environ = self.request_data.get_request_environ(request_id) 196
195 match = re.search(r'^OAuth (\S+)', environ.get('HTTP_AUTHORIZATION', '') ) 197 response.set_email(email)
196 if not match: 198 response.set_user_id(user_id)
197 raise apiproxy_errors.ApplicationError( 199 response.set_auth_domain(user_service_stub._DEFAULT_AUTH_DOMAIN)
198 user_service_pb.UserServiceError.OAUTH_INVALID_REQUEST) 200 response.set_is_admin(is_admin)
199 201 response.set_client_id(client_id)
200 email, is_admin = get_user_info(match.group(1)) 202 response.add_scopes(OAUTH2_SCOPE)
201 if not email: 203
202 raise apiproxy_errors.ApplicationError( 204 user_service_stub.UserServiceStub._Dynamic_GetOAuthUser = _Dynamic_GetOAuthUse r
203 user_service_pb.UserServiceError.OAUTH_INVALID_TOKEN)
204
205 # User ID is based on email address, see appengine.tools.devappserver2.l ogin
206 user_id_digest = hashlib.md5(email.lower()).digest()
207 user_id = '1' + ''.join(['%02d' % ord(x) for x in user_id_digest])[:20]
208
209 response.set_email(email)
210 response.set_user_id(user_id)
211 response.set_auth_domain(user_service_stub._DEFAULT_AUTH_DOMAIN)
212 response.set_is_admin(is_admin)
213 response.set_client_id(client_id)
214 response.add_scopes(OAUTH2_SCOPE)
215
216 user_service_stub.UserServiceStub._Dynamic_GetOAuthUser = _Dynamic_GetOAuthU ser
217
218 205
219 def fix_target_resolution(): 206 def fix_target_resolution():
220 """ 207 """
221 By default, the dispatcher assumes port 80 for target authorities that 208 By default, the dispatcher assumes port 80 for target authorities that
222 only contain a hostname but no port part. This hard-coded behavior is 209 only contain a hostname but no port part. This hard-coded behavior is
223 altered in function fix_target_resolution() so that the port given 210 altered in function fix_target_resolution() so that the port given
224 as --port option to the appserver-script is used instead. Without this 211 as --port option to the appserver-script is used instead. Without this
225 monkey-patch, dispatching tasks from an application run behind a HTTP 212 monkey-patch, dispatching tasks from an application run behind a HTTP
226 proxy server on port 80 (or HTTPS on 443) will fail, because 213 proxy server on port 80 (or HTTPS on 443) will fail, because
227 applications will omit the default port when addressing resources. 214 applications will omit the default port when addressing resources.
228 """ 215 """
229 from google.appengine.tools.devappserver2.dispatcher import Dispatcher 216 from google.appengine.tools.devappserver2.dispatcher import Dispatcher
230 orig_resolve_target = Dispatcher._resolve_target 217 orig_resolve_target = Dispatcher._resolve_target
231 218
232 def resolve_target(dispatcher, hostname, path): 219 def resolve_target(dispatcher, hostname, path):
233 new_hostname = hostname if ":" in hostname else "%s:%d" % (hostname, dis patcher._port) 220 new_hostname = hostname if ":" in hostname else "%s:%d" % (hostname, dispatc her._port)
234 return orig_resolve_target(dispatcher, new_hostname, path) 221 return orig_resolve_target(dispatcher, new_hostname, path)
235 222
236 Dispatcher._resolve_target = resolve_target 223 Dispatcher._resolve_target = resolve_target
237 224
238 if __name__ == '__main__': 225 if __name__ == '__main__':
239 engine_dir = '/opt/google_appengine' 226 engine_dir = '/opt/google_appengine'
240 storage_path = '/var/lib/rietveld' 227 storage_path = '/var/lib/rietveld'
241 228
242 script_name, script_file = setup_paths(engine_dir) 229 script_name, script_file = setup_paths(engine_dir)
243 adjust_server_id() 230 adjust_server_id()
244 fix_request_scheme() 231 fix_request_scheme()
245 232
246 if script_name == 'dev_appserver.py': 233 if script_name == 'dev_appserver.py':
247 config = read_config(os.path.join(storage_path, 'config.ini')) 234 config = read_config(os.path.join(storage_path, 'config.ini'))
248 235
249 set_storage_path(storage_path) 236 set_storage_path(storage_path)
250 replace_runtime() 237 replace_runtime()
251 protect_cookies(config.get('main', 'cookie_secret')) 238 protect_cookies(config.get('main', 'cookie_secret'))
252 enable_oauth2( 239 enable_oauth2(
253 config.get('oauth2', 'client_id'), 240 config.get('oauth2', 'client_id'),
254 config.get('oauth2', 'client_secret'), 241 config.get('oauth2', 'client_secret'),
255 config.get('main', 'admins').split() 242 config.get('main', 'admins').split()
256 ) 243 )
257 fix_target_resolution() 244 fix_target_resolution()
258 245
259 execfile(script_file) 246 execfile(script_file)
OLDNEW
« no previous file with comments | « modules/private-stub/hiera/hosts.yaml ('k') | modules/sitescripts/manifests/init.pp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld