Blame repoproxy.py

e5b0ac
e5b0ac
e5b0ac
import base64
e5b0ac
import http.client
e5b0ac
e5b0ac
import db.holder
e5b0ac
from model.model import Model
e5b0ac
from answer import Result
e5b0ac
from translator import Translator
e5b0ac
e5b0ac
e5b0ac
class RepoProxy:
e5b0ac
  SKIP_HEADERS = {
e5b0ac
    'connection',
e5b0ac
    'keep-alive',
e5b0ac
    'proxy-authenticate',
e5b0ac
    'proxy-authorization',
e5b0ac
    'proxy-connection',
e5b0ac
    'te',
e5b0ac
    'trailers',
e5b0ac
    'transfer-encoding',
e5b0ac
    'upgrade' }
e5b0ac
  
e5b0ac
  def __init__(self, server):
e5b0ac
    self.server = server
e5b0ac
    
e5b0ac
  def unauthorized(self):
e5b0ac
    return Result(
e5b0ac
      '401 Unauthorized',
e5b0ac
      [('WWW-Authenticate', 'Basic ream="Authorization area", charset="UTF-8"')],
e5b0ac
      list('401 Unauthorized') )
e5b0ac
e5b0ac
  def forbidden(self):
e5b0ac
    return Result('403 Forbidden', list(), list('403 Forbidden'))
e5b0ac
e5b0ac
  def badgateway(self):
e5b0ac
    return Result('502 Bad Gateway', list(), list('502 Bad Gateway'))
e5b0ac
e5b0ac
  def split(self, text, separator, left = False):
e5b0ac
    x = text.split(separator, 1)
e5b0ac
    if len(x) < 2:
e5b0ac
      return ('', x[0]) if left else (x[0], '')
e5b0ac
    return x
e5b0ac
  
e5b0ac
  def input_reader(self, request):
e5b0ac
    data = request.env['wsgi.input']
e5b0ac
    while True:
e5b0ac
      chunk = data.read(4096)
e5b0ac
      if not chunk: break
e5b0ac
      yield chunk
e5b0ac
e5b0ac
  def reader(self, connection, response):
e5b0ac
    while True:
e5b0ac
      chunk = response.read(4096)
e5b0ac
      if not chunk: break
e5b0ac
      yield chunk
e5b0ac
    connection.close()
e5b0ac
e5b0ac
  def proxy(self, request, fullurl):
e5b0ac
    s = fullurl
e5b0ac
    protocol, s = self.split(s, '://')
e5b0ac
    host, s = self.split(s, '/')
e5b0ac
    url = '/' + s
e5b0ac
    auth, host = self.split(host, '@', True)
e5b0ac
    host, port = self.split(host, ':')
e5b0ac
    if not port: port = None
e5b0ac
    
e5b0ac
    if not host or not url:
e5b0ac
      print('repoproxy: bad url [' + host + '][' + url + '][' + fullurl + ']')
e5b0ac
      return self.badgateway()
e5b0ac
    
e5b0ac
    if auth:
e5b0ac
      print('repoproxy: authorization in url is not supported [' + auth + '][' + fullurl + ']')
e5b0ac
      return self.badgateway()
e5b0ac
    
e5b0ac
    connection_class = None
e5b0ac
    if protocol == 'https':
e5b0ac
      connection_class = http.client.HTTPSConnection
e5b0ac
    elif protocol == 'http':
e5b0ac
      connection_class = http.client.HTTPConnection
e5b0ac
    if not connection_class:
e5b0ac
      print('repoproxy: protocol is not supported [' + protocol + '][' + fullurl + ']')
e5b0ac
      return self.badgateway()
e5b0ac
    
e5b0ac
    body = self.input_reader(request)
e5b0ac
e5b0ac
    header_prefix = 'HTTP_'
e5b0ac
    proxy_headers = dict()
e5b0ac
    for k, v in request.env.items():
e5b0ac
      if k.startswith(header_prefix):
e5b0ac
        kk = k[len(header_prefix):].lower().replace('_', '-')
e5b0ac
        if not kk in self.SKIP_HEADERS:
e5b0ac
          proxy_headers[kk] = v
e5b0ac
    if 'CONTENT_TYPE' in request.env:
e5b0ac
      proxy_headers['content-type'] = request.env['CONTENT_TYPE']
e5b0ac
    proxy_headers['host'] = host
e5b0ac
    
e5b0ac
    connection = None
e5b0ac
    try:
e5b0ac
      connection = connection_class(host = host, port = port)
e5b0ac
      connection.request(
e5b0ac
        request.method,
e5b0ac
        url,
e5b0ac
        body = body,
e5b0ac
        headers = proxy_headers )
e5b0ac
    except Exception as e:
e5b0ac
      print('repoproxy: connection failed [' + fullurl + ']')
e5b0ac
      print(e)
e5b0ac
      return self.badgateway()
e5b0ac
e5b0ac
    response = connection.getresponse()
e5b0ac
    headers = list()
e5b0ac
    for k, v in response.getheaders():
e5b0ac
      if not k.lower() in self.SKIP_HEADERS:
e5b0ac
        headers.append( (k, v) )
e5b0ac
e5b0ac
    return Result(str(response.status) + ' ' + str(response.reason), headers, self.reader(connection, response))
e5b0ac
e5b0ac
  def process(self, request, path):
e5b0ac
    if len(path) < 2:
e5b0ac
      return self.forbidden()
e5b0ac
    repoowner = path[0]
e5b0ac
    reponame = path[1]
e5b0ac
    nextpath = path[2:]
e5b0ac
    
e5b0ac
    if request.method != 'GET' and request.method != 'POST':
e5b0ac
      return self.forbidden()
e5b0ac
    
e5b0ac
    post = request.method == 'POST'
e5b0ac
    login = ''
e5b0ac
    password = ''
e5b0ac
    if 'HTTP_AUTHORIZATION' in request.env:
e5b0ac
      try:
e5b0ac
        authtype, credentials = str(request.env['HTTP_AUTHORIZATION']).split()
e5b0ac
        assert authtype.lower() == 'basic'
e5b0ac
        login, password = base64.b64decode(credentials).decode('utf8').split(':')
e5b0ac
      except Exception:
e5b0ac
        return self.unauthorized()
e5b0ac
    
e5b0ac
    url = None
e5b0ac
    with db.holder.Holder(request.server.dbpool, readonly = True) as connection:
e5b0ac
      request.connection = connection
e5b0ac
      request.model = Model(connection, Translator(), superuser = True)
e5b0ac
      
e5b0ac
      user = None
e5b0ac
      if login:
e5b0ac
        user_id = request.model.users.check_password(login, password)
e5b0ac
        if not user_id:
e5b0ac
          return self.unauthorized()
e5b0ac
        user = request.model.users.get_by_id(user_id)
e5b0ac
        assert(user)
e5b0ac
      elif post:
e5b0ac
        return self.unauthorized()
e5b0ac
      
e5b0ac
      owner = request.model.users.get_by_login( repoowner )
e5b0ac
      if not owner:
e5b0ac
        return self.forbidden()
e5b0ac
    
e5b0ac
      repo = request.model.repositories.get_by_name( owner.id, reponame, owner )
e5b0ac
      if not repo:
e5b0ac
        return self.forbidden()
e5b0ac
      
e5b0ac
      if post:
e5b0ac
        if user.id != repo.user_id and not request.model.rights.get_superuser(user.id):
e5b0ac
          return self.unauthorized()
e5b0ac
      
e5b0ac
      url = repo.gen_internalurl()
e5b0ac
      
e5b0ac
    getvars = request.env.get('QUERY_STRING', '')
e5b0ac
    if nextpath:
e5b0ac
      url += '/' + '/'.join(nextpath)
e5b0ac
    if getvars:
e5b0ac
      url += '?' + getvars
e5b0ac
    return self.proxy(request, url)
e5b0ac