blob: fe9784a7e1ed59b768a2a75816c8b71c29e9420b [file] [log] [blame]
# Copyright 2008 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import base64
import hashlib
import hmac
import logging
import time
import zlib
from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from django.http import HttpResponse
from froofle.protobuf.service import RpcController
from codereview.models import Account, Settings
from codereview.internal.util import InternalAPI
from codereview.view_util import xsrf_for, is_xsrf_ok
from codereview.review_service import ReviewServiceImp
from codereview.internal.admin_service import AdminServiceImp
from codereview.internal.build_service import BuildServiceImp
from codereview.internal.bundle_store_service import BundleStoreServiceImp
from codereview.internal.change_service import ChangeServiceImp
from codereview.internal.merge_service import MergeServiceImp
MAX_TIME_WINDOW = 5 * 60 # seconds
XSRF_PATH = '/proto/'
services = {}
def register_service(s_impl):
services[s_impl.GetDescriptor().name] = s_impl
register_service(ReviewServiceImp())
register_service(AdminServiceImp())
register_service(BuildServiceImp())
register_service(BundleStoreServiceImp())
register_service(ChangeServiceImp())
register_service(MergeServiceImp())
class _LocalController(RpcController):
def __init__(self, s_impl):
self._failed = None
self._service = s_impl
def Reset(self):
pass
def Failed(self):
pass
def ErrorText(self):
pass
def StartCancel(self):
pass
def SetFailed(self, reason):
logging.error("Failed on %s: %s" % (
self._service.__class__.__name__,
reason))
self._failed = reason
def IsCancelled(self):
pass
def NotifyOnCancel(self, callback):
pass
def HasFailed(self):
return self._failed is not None
def token(req):
user = users.get_current_user()
if not user:
return HttpResponse(status=401, content="User must be logged in.")
return HttpResponse(content = xsrf_for(XSRF_PATH),
content_type = 'application/octet-stream')
def serve(req, service_name, action_name):
try:
type_str = req.META['CONTENT_TYPE']
except KeyError:
return HttpResponse(status=415, content="Invalid request body type")
type = type_str.split('; ')
type_dict = {}
type_dict['name'] = None
type_dict['compress'] = None
for t in type[1:]:
name, val = t.split('=', 1)
type_dict[name] = val
if type[0] != "application/x-google-protobuf":
return HttpResponse(status=415, content="Invalid request body type")
try: s_impl = services[service_name]
except KeyError:
return HttpResponse(status=404, content="Service not recognized.")
method = s_impl.GetDescriptor().FindMethodByName(action_name)
if not method:
return HttpResponse(status=404, content="Method not recognized.")
request = s_impl.GetRequestClass(method)()
request_name = request.DESCRIPTOR.full_name
if type_dict['name'] != request_name:
return HttpResponse(status=415,
content="Expected a %s" % request_name)
raw_body = req.raw_post_data
msg_bin = raw_body
if 'HTTP_CONTENT_MD5' in req.META:
expmd5 = req.META['HTTP_CONTENT_MD5']
actmd5 = hashlib.md5()
actmd5.update(raw_body)
actmd5 = base64.b64encode(actmd5.digest())
if actmd5 != expmd5:
return HttpResponse(status=412,
content="Content-MD5 incorrect")
compression = type_dict['compress']
if compression == 'deflate':
msg_bin = zlib.decompress(msg_bin)
elif compression:
return HttpResponse(status=415,
content="Unsupported compression %s" % compression)
if isinstance(s_impl, InternalAPI):
key = Settings.get_settings().internal_api_key
key = base64.b64decode(key)
try:
date = int(req.META['HTTP_X_DATE_UTC'])
except KeyError:
return HttpResponse(status=403,
content="X-Date-UTC header is required.")
try:
exp_sig = req.META['HTTP_AUTHORIZATION']
except KeyError:
return HttpResponse(status=403,
content="Authorization header is required.")
if not exp_sig.startswith("proto :"):
return HttpResponse(status=403,
content="Malformed authorization header.")
exp_sig = exp_sig[len("proto :"):]
now = time.time()
if abs(date - now) > MAX_TIME_WINDOW:
return HttpResponse(status=403,
content="Request is too early or too late.")
m = hmac.new(key, digestmod=hashlib.sha1)
m.update('POST %s\n' % req.path)
m.update('X-Date-UTC: %s\n' % date)
m.update('Content-Type: %s\n' % type_str)
m.update('\n')
m.update(raw_body)
if base64.b64encode(m.digest()) != exp_sig:
return HttpResponse(status=403,
content="Invalid request signature.")
else:
user = users.get_current_user()
if not user:
return HttpResponse(status=401, content="User must be logged in.")
try:
xsrf = req.META['HTTP_X_XSRF_TOKEN']
except KeyError:
return HttpResponse(status=403,
content="X-XSRF-Token header required.")
if not is_xsrf_ok(req, path=XSRF_PATH, xsrf=xsrf):
return HttpResponse(status=403,
content="X-XSRF-Token invalid.")
request.ParseFromString(msg_bin)
controller = _LocalController(s_impl)
class result_caddy:
_r = HttpResponse(status=500)
def __call__(self, r):
if r is not None:
r_bin = r.SerializeToString()
r_name = r.DESCRIPTOR.full_name
r_type = "application/x-google-protobuf; name=%s" % r_name
self._r = HttpResponse(content_type = r_type, content = r_bin)
done = result_caddy()
s_impl.http_request = req
s_impl.CallMethod(method, controller, request, done)
if controller.HasFailed():
return HttpResponse(status=500)
return done._r