Blame repoproxy.py

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