Чтобы управлять доступом можно использовать различные решения gitosys, gitolite, mercurial-server, но эти решения работают через SSH, что не всегда удобно (должен быть ключ). В добавок не хватает гибкости у подобных решений.
Основные требования:
Для решения этой задачи сделал следующую систему…
Собрал nginx с модулем Auth Request
Часть файла конфигурации:
127.0.0.1:8002 — в моём случае по этому адресу работает hgserve
Вся магия кроется в uwsgi-скрипте. Его задача проверить данные полученные от nginx. И вернуть код в зависимости от результата проверки (200,401,403).
Скрипт запускается с помощью uWSGI:
Схема базы данных:
Основные требования:
- доступ по логину/паролю (HTTPS)
- контроль прав на чтение/запись
- публичный/приватный репозиторий
- управления всем через веб интерфейс
- все данные (информация о проекте и пользователях) должны храниться в базе (MySQL)
Для решения этой задачи сделал следующую систему…
Собрал nginx с модулем Auth Request
Часть файла конфигурации:
server { ... location = /auth { include /etc/nginx/uwsgi_params; uwsgi_pass unix:/tmp/uwsgi.sock; } location /hg { set $project ""; if ( $uri ~ "^/hg/([a-zA-Z0-9_\-]+).*$" ) { set $project $1; } if ( $project = "" ) { return 403; } if ( !-d /var/projects/$repo ) { return 404; } auth_request /auth; include /etc/nginx/proxy_params; proxy_redirect off; proxy_pass http://127.0.0.1:8002; } }
127.0.0.1:8002 — в моём случае по этому адресу работает hgserve
Вся магия кроется в uwsgi-скрипте. Его задача проверить данные полученные от nginx. И вернуть код в зависимости от результата проверки (200,401,403).
#!/usr/bin/python
# configure
db_host = 'localhost'
db_name = 'authdb'
db_user = 'test'
db_pass = 'test'
role_id_read = 1 # member
role_id_write = 2 # developer
#
import re
import os
import base64
import MySQLdb
db_conn = MySQLdb.connect(host = db_host,
user = db_user,
passwd = db_pass,
db = db_name)
def get_project_info(project_name):
global db_conn
cursor = db_conn.cursor()
query = 'SELECT id,is_private FROM projects WHERE name=%s'
cursor.execute(query, (project_name, ))
row = cursor.fetchone()
cursor.close()
return row
def get_role_id(project_id, username, password):
global db_conn
cursor = db_conn.cursor()
query = 'SELECT id FROM users WHERE name=%s AND pass=sha1(%s)'
cursor.execute(query, (username, password, ))
row = cursor.fetchone()
if row == None:
return -1
user_id = row[0]
query = 'SELECT role_id FROM project_user_perm WHERE project_id=%s AND user_id=%s'
cursor.execute(query, (project_id, user_id, ))
row = cursor.fetchone()
cursor.close()
if row == None:
return 0
return row[0]
def can_user_read(project_id, username, password):
role_id = get_role_id(project_id, username, password)
if role_id == -1:
return -1
if role_id == role_id_read or role_id == role_id_write :
return 1
return 0
def can_user_write(project_id, username, password):
role_id = get_role_id(project_id, username, password)
if role_id == -1:
return -1
if role_id == role_id_write :
return 1
return 0
def ok200(callback):
callback('200 OK', [])
return []
def err401(callback):
callback('401 Unauthorized', [('WWW-Authenticate', 'Basic realm="Restrict"')])
return []
def err403(callback):
callback('403 Forbidden', [])
return []
def application(env, resp):
req_uri = env.get('REQUEST_URI', '')
m = re.match('^/(\w+)/(\w+).*$', req_uri)
if m == None:
return err403(resp)
project_name = m.group(2)
project_info = get_project_info(project_name)
if project_info == None:
return err403(resp)
req_method = env.get('REQUEST_METHOD', '')
if req_method == 'GET' and project_info[1] == 0:
return ok200(resp)
req_http_auth = env.get('HTTP_AUTHORIZATION', '')
if req_http_auth == '':
return err401(resp)
m = re.match('^Basic ([a-zA-Z0-9=]+)$', req_http_auth)
if m == None:
return err403(resp)
userpass = base64.b64decode(m.group(1))
m = re.match('^(\w+):(\S+)$', userpass)
if m == None:
return err403(resp)
username = m.group(1)
password = m.group(2)
result = 0
if req_method == 'GET':
result = can_user_read(project_info[0], username, password)
else:
result = can_user_write(project_info[0], username, password)
if result == -1:
return err401(resp)
elif result == 1:
return ok200(resp)
else:
return err403(resp)
Скрипт запускается с помощью uWSGI:
uwsgi -s /tmp/uwsgi.sock \ --wsgi-file /var/projects/vcs_auth/vcs_auth_app.py \ --uid www-data --gid www-data \ --pidfile /var/run/vcs_auth_app.pid \ -d /var/log/uwsgi/vcs_auth_app.log
Схема базы данных: