Blame repoproxy.py

e5b0ac
e5b0ac
e5b0ac
import base64
e5b0ac
import http.client
9e1462
import urllib.parse
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']
61454c
    try:
61454c
      length = int(request.env['CONTENT_LENGTH'])
61454c
      if length: proxy_headers['content-length'] = str(length)
61454c
    except Exception:
61454c
      pass
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
    login = ''
e5b0ac
    password = ''
9e1462
    sslcert = ''
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()
a5dbf9
    elif 'EARTHWORM_SSL_CLIENT_CERT' in request.env:
a5dbf9
      sslcert = str(urllib.parse.unquote(request.env['EARTHWORM_SSL_CLIENT_CERT']))
9e1462
e5b0ac
    url = None
e5b0ac
    with db.holder.Holder(request.server.dbpool, readonly = True) as connection:
e5b0ac
      request.connection = connection
3976df
      request.model = Model(connection, Translator(), 0)
9e1462
  
e5b0ac
      user = None
9e1462
      user_id = None
e5b0ac
      if login:
e5b0ac
        user_id = request.model.users.check_password(login, password)
e5b0ac
        if not user_id:
e5b0ac
          return self.unauthorized()
9e1462
      elif sslcert:
9e1462
        sslcert_pure = request.model.sslcerts.extract_data(sslcert)
9e1462
        sslcert_object = request.model.sslcerts.get_by_data(sslcert_pure)
9e1462
        if sslcert_object:
9e1462
          user_id = sslcert_object.user_id
9e1462
9e1462
      if user_id:
3976df
        request.model = Model(connection, Translator(), user_id)
e5b0ac
        user = request.model.users.get_by_id(user_id)
e5b0ac
        assert(user)
e5b0ac
      
e5b0ac
      owner = request.model.users.get_by_login( repoowner )
e5b0ac
      if not owner:
3976df
        return self.forbidden() if user else self.unauthorized()
e5b0ac
    
89ea6c
      repo = request.model.repositories.get_by_name( owner, reponame )
e5b0ac
      if not repo:
3976df
        return self.forbidden() if user else self.unauthorized()
e5b0ac
      
61454c
      writeaccess = repo.repotype.iswriteaccess(request, nextpath)
a30080
61454c
      if writeaccess:
3976df
        if not repo.can_write():
3976df
          return self.forbidden() if user else self.unauthorized()
e5b0ac
      
e5b0ac
      url = repo.gen_internalurl()
e5b0ac
      
61454c
      getvars = request.env.get('QUERY_STRING', '')
61454c
      if nextpath:
61454c
        url += '/' + '/'.join(nextpath)
61454c
      if getvars:
61454c
        url += '?' + getvars
61454c
      return self.proxy(request, url)
e5b0ac