Blob Blame Raw


import base64
import http.client
import urllib.parse

import db.holder
from model.model import Model
from answer import Result
from translator import Translator


class RepoProxy:
  SKIP_HEADERS = {
    'connection',
    'keep-alive',
    'proxy-authenticate',
    'proxy-authorization',
    'proxy-connection',
    'te',
    'trailers',
    'transfer-encoding',
    'upgrade' }
  
  def __init__(self, server):
    self.server = server
    
  def unauthorized(self):
    return Result(
      '401 Unauthorized',
      [('WWW-Authenticate', 'Basic ream="Authorization area", charset="UTF-8"')],
      list('401 Unauthorized') )

  def forbidden(self):
    return Result('403 Forbidden', list(), list('403 Forbidden'))

  def badgateway(self):
    return Result('502 Bad Gateway', list(), list('502 Bad Gateway'))

  def split(self, text, separator, left = False):
    x = text.split(separator, 1)
    if len(x) < 2:
      return ('', x[0]) if left else (x[0], '')
    return x
  
  def input_reader(self, request):
    data = request.env['wsgi.input']
    while True:
      chunk = data.read(4096)
      if not chunk: break
      yield chunk

  def reader(self, connection, response):
    while True:
      chunk = response.read(4096)
      if not chunk: break
      yield chunk
    connection.close()

  def proxy(self, request, fullurl):
    s = fullurl
    protocol, s = self.split(s, '://')
    host, s = self.split(s, '/')
    url = '/' + s
    auth, host = self.split(host, '@', True)
    host, port = self.split(host, ':')
    if not port: port = None
    
    if not host or not url:
      print('repoproxy: bad url [' + host + '][' + url + '][' + fullurl + ']')
      return self.badgateway()
    
    if auth:
      print('repoproxy: authorization in url is not supported [' + auth + '][' + fullurl + ']')
      return self.badgateway()
    
    connection_class = None
    if protocol == 'https':
      connection_class = http.client.HTTPSConnection
    elif protocol == 'http':
      connection_class = http.client.HTTPConnection
    if not connection_class:
      print('repoproxy: protocol is not supported [' + protocol + '][' + fullurl + ']')
      return self.badgateway()
    
    body = self.input_reader(request)

    header_prefix = 'HTTP_'
    proxy_headers = dict()
    for k, v in request.env.items():
      if k.startswith(header_prefix):
        kk = k[len(header_prefix):].lower().replace('_', '-')
        if not kk in self.SKIP_HEADERS:
          proxy_headers[kk] = v
    if 'CONTENT_TYPE' in request.env:
      proxy_headers['content-type'] = request.env['CONTENT_TYPE']
    try:
      length = int(request.env['CONTENT_LENGTH'])
      if length: proxy_headers['content-length'] = str(length)
    except Exception:
      pass
    proxy_headers['host'] = host
    
    connection = None
    try:
      connection = connection_class(host = host, port = port)
      connection.request(
        request.method,
        url,
        body = body,
        headers = proxy_headers )
    except Exception as e:
      print('repoproxy: connection failed [' + fullurl + ']')
      print(e)
      return self.badgateway()

    response = connection.getresponse()
    headers = list()
    for k, v in response.getheaders():
      if not k.lower() in self.SKIP_HEADERS:
        headers.append( (k, v) )

    return Result(str(response.status) + ' ' + str(response.reason), headers, self.reader(connection, response))

  def process(self, request, path):
    if len(path) < 2:
      return self.forbidden()
    repoowner = path[0]
    reponame = path[1]
    nextpath = path[2:]
    
    if request.method != 'GET' and request.method != 'POST':
      return self.forbidden()
    
    login = ''
    password = ''
    sslcert = ''
    if 'HTTP_AUTHORIZATION' in request.env:
      try:
        authtype, credentials = str(request.env['HTTP_AUTHORIZATION']).split()
        assert authtype.lower() == 'basic'
        login, password = base64.b64decode(credentials).decode('utf8').split(':')
      except Exception:
        return self.unauthorized()
    elif 'HTTP_X_SSL_CLIENT_CERT' in request.env:
      sslcert = str(urllib.parse.unquote(request.env['HTTP_X_SSL_CLIENT_CERT']))

    url = None
    with db.holder.Holder(request.server.dbpool, readonly = True) as connection:
      request.connection = connection
      request.model = Model(connection, Translator(), 0)
  
      user = None
      user_id = None
      if login:
        user_id = request.model.users.check_password(login, password)
        if not user_id:
          return self.unauthorized()
      elif sslcert:
        sslcert_pure = request.model.sslcerts.extract_data(sslcert)
        sslcert_object = request.model.sslcerts.get_by_data(sslcert_pure)
        if sslcert_object:
          user_id = sslcert_object.user_id

      if user_id:
        request.model = Model(connection, Translator(), user_id)
        user = request.model.users.get_by_id(user_id)
        assert(user)
      
      owner = request.model.users.get_by_login( repoowner )
      if not owner:
        return self.forbidden() if user else self.unauthorized()
    
      repo = request.model.repositories.get_by_name( owner, reponame )
      if not repo:
        return self.forbidden() if user else self.unauthorized()
      
      writeaccess = repo.repotype.iswriteaccess(request, nextpath)

      if writeaccess:
        if not repo.can_write():
          return self.forbidden() if user else self.unauthorized()
      
      url = repo.gen_internalurl()
      
      getvars = request.env.get('QUERY_STRING', '')
      if nextpath:
        url += '/' + '/'.join(nextpath)
      if getvars:
        url += '?' + getvars
      return self.proxy(request, url)