Blame repoproxy.py

Ivan Mahonin e5b0ac
Ivan Mahonin e5b0ac
Ivan Mahonin e5b0ac
import base64
Ivan Mahonin c3ca82
import socket
Ivan Mahonin e5b0ac
import http.client
Ivan Mahonin 9e1462
import urllib.parse
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 c3ca82
    sock = None
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 c3ca82
    elif protocol == 'http+unix':
Ivan Mahonin c3ca82
      connection_class = http.client.HTTPConnection
Ivan Mahonin c3ca82
      sockfile = urllib.parse.unquote(host)
Ivan Mahonin c3ca82
      host = 'localhost'
Ivan Mahonin c3ca82
      try:
Ivan Mahonin c3ca82
        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
Ivan Mahonin c3ca82
        sock.connect(sockfile)
Ivan Mahonin c3ca82
      except Exception as e:
Ivan Mahonin c3ca82
        print('repoproxy: cannot connect to unix socket [' + sockfile + ']')
Ivan Mahonin c3ca82
        print(e)
Ivan Mahonin c3ca82
        return self.badgateway()
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 c3ca82
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 61454c
    try:
Ivan Mahonin 61454c
      length = int(request.env['CONTENT_LENGTH'])
Ivan Mahonin 61454c
      if length: proxy_headers['content-length'] = str(length)
Ivan Mahonin 61454c
    except Exception:
Ivan Mahonin 61454c
      pass
Ivan Mahonin e5b0ac
    proxy_headers['host'] = host
Ivan Mahonin c3ca82
Ivan Mahonin e5b0ac
    connection = None
Ivan Mahonin e5b0ac
    try:
Ivan Mahonin e5b0ac
      connection = connection_class(host = host, port = port)
Ivan Mahonin c3ca82
      if sock: connection.sock = sock
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
    login = ''
Ivan Mahonin e5b0ac
    password = ''
Ivan Mahonin 9e1462
    sslcert = ''
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 a5dbf9
    elif 'EARTHWORM_SSL_CLIENT_CERT' in request.env:
Ivan Mahonin a5dbf9
      sslcert = str(urllib.parse.unquote(request.env['EARTHWORM_SSL_CLIENT_CERT']))
Ivan Mahonin 9e1462
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 3976df
      request.model = Model(connection, Translator(), 0)
Ivan Mahonin 9e1462
  
Ivan Mahonin e5b0ac
      user = None
Ivan Mahonin 9e1462
      user_id = 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 9e1462
      elif sslcert:
Ivan Mahonin 9e1462
        sslcert_pure = request.model.sslcerts.extract_data(sslcert)
Ivan Mahonin 9e1462
        sslcert_object = request.model.sslcerts.get_by_data(sslcert_pure)
Ivan Mahonin 9e1462
        if sslcert_object:
Ivan Mahonin 9e1462
          user_id = sslcert_object.user_id
Ivan Mahonin 9e1462
Ivan Mahonin 9e1462
      if user_id:
Ivan Mahonin 3976df
        request.model = Model(connection, Translator(), user_id)
Ivan Mahonin e5b0ac
        user = request.model.users.get_by_id(user_id)
Ivan Mahonin e5b0ac
        assert(user)
Ivan Mahonin e5b0ac
      
Ivan Mahonin e5b0ac
      owner = request.model.users.get_by_login( repoowner )
Ivan Mahonin e5b0ac
      if not owner:
Ivan Mahonin 3976df
        return self.forbidden() if user else self.unauthorized()
Ivan Mahonin e5b0ac
    
Ivan Mahonin 89ea6c
      repo = request.model.repositories.get_by_name( owner, reponame )
Ivan Mahonin e5b0ac
      if not repo:
Ivan Mahonin 3976df
        return self.forbidden() if user else self.unauthorized()
Ivan Mahonin e5b0ac
      
Ivan Mahonin 61454c
      writeaccess = repo.repotype.iswriteaccess(request, nextpath)
Ivan Mahonin a30080
Ivan Mahonin 61454c
      if writeaccess:
Ivan Mahonin 3976df
        if not repo.can_write():
Ivan Mahonin 3976df
          return self.forbidden() if user else self.unauthorized()
Ivan Mahonin e5b0ac
      
Ivan Mahonin e5b0ac
      url = repo.gen_internalurl()
Ivan Mahonin e5b0ac
      
Ivan Mahonin 61454c
      getvars = request.env.get('QUERY_STRING', '')
Ivan Mahonin 61454c
      if nextpath:
Ivan Mahonin 61454c
        url += '/' + '/'.join(nextpath)
Ivan Mahonin 61454c
      if getvars:
Ivan Mahonin 61454c
        url += '?' + getvars
Ivan Mahonin 61454c
      return self.proxy(request, url)
Ivan Mahonin e5b0ac