From 9e1462cc401ccddb4545372e1425199a090d6870 Mon Sep 17 00:00:00 2001 From: Ivan Mahonin Date: Dec 19 2019 20:12:27 +0000 Subject: git auth via client SSL cert --- diff --git a/action/actions.py b/action/actions.py index 6753c36..62a3a43 100644 --- a/action/actions.py +++ b/action/actions.py @@ -2,10 +2,12 @@ from util import mergedict import action.user +import action.sslcert import action.repo actions = dict() mergedict(actions, action.user.actions, 'user.') +mergedict(actions, action.sslcert.actions, 'sslcert.') mergedict(actions, action.repo.actions, 'repo.') diff --git a/action/sslcert.py b/action/sslcert.py new file mode 100644 index 0000000..911d172 --- /dev/null +++ b/action/sslcert.py @@ -0,0 +1,54 @@ + + +import exception +from action.action import Action +from action.user import UserBase + + +class SslcertCreate(UserBase): + def process(self, request): + user_id = self.parse_user_id(request) + data = request.model.sslcerts.extract_data( str(request.postvars.get('data', '')) ) + + user = request.model.users.get_by_id(user_id) + if not user: + raise exception.ActionError( request.t('User not found') ) + + sslcert = None + try: + sslcert = request.model.sslcerts.create(user, data) + except Exception as e: + self.propagate_exception(e) + + request.connection.commit() + return self.redirect_to_user(request, user, ['edit']) + + +class SslcertDelete(UserBase): + def process(self, request): + sslcert_id = 0 + try: + sslcert_id = int(request.postvars.get('sslcert_id', 0)) + except Exception: + raise exception.ActionError( request.t('SSL Certificate Id incorrect') ) + if not sslcert_id: + raise exception.ActionError( request.t('SSL Certificate Id incorrect') ) + + sslcert = request.model.sslcerts.get_by_id(sslcert_id) + if not sslcert: + raise exception.ActionError( request.t('SSL Certificate not found') ) + user = sslcert.get_user() + + try: + sslcert.delete() + except Exception as e: + self.propagate_exception(e) + + request.connection.commit() + return self.redirect_to_user(request, user, ['edit']) + + +actions = { + 'create' : SslcertCreate(), + 'delete' : SslcertDelete(), +} diff --git a/doc/earthworm.sql b/doc/earthworm.sql index 4dfef0c..4515300 100644 --- a/doc/earthworm.sql +++ b/doc/earthworm.sql @@ -1,11 +1,11 @@ -- phpMyAdmin SQL Dump --- version 4.7.0 +-- version 4.8.5 -- https://www.phpmyadmin.net/ -- -- Host: localhost --- Generation Time: Sep 25, 2019 at 12:10 AM --- Server version: 10.1.37-MariaDB-0+deb9u1 --- PHP Version: 5.6.36-0+deb8u1 +-- Generation Time: Dec 19, 2019 at 08:09 PM +-- Server version: 10.1.41-MariaDB-0+deb9u1 +-- PHP Version: 7.0.33-0+deb9u6 SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; SET AUTOCOMMIT = 0; @@ -37,10 +37,6 @@ CREATE TABLE `ew_repositories` ( `description` text COLLATE utf8mb4_unicode_ci NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; --- --- Dumping data for table `ew_repositories` --- - -- -------------------------------------------------------- -- @@ -55,10 +51,18 @@ CREATE TABLE `ew_rights` ( `mode` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +-- -------------------------------------------------------- + -- --- Dumping data for table `ew_rights` +-- Table structure for table `ew_sslcerts` -- +CREATE TABLE `ew_sslcerts` ( + `id` int(11) NOT NULL, + `user_id` int(11) NOT NULL, + `data` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + -- -------------------------------------------------------- -- @@ -95,10 +99,6 @@ CREATE TABLE `ew_users` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- --- Dumping data for table `ew_users` --- - --- -- Indexes for dumped tables -- @@ -115,6 +115,12 @@ ALTER TABLE `ew_rights` ADD PRIMARY KEY (`id`); -- +-- Indexes for table `ew_sslcerts` +-- +ALTER TABLE `ew_sslcerts` + ADD PRIMARY KEY (`id`); + +-- -- Indexes for table `ew_test` -- ALTER TABLE `ew_test` @@ -134,22 +140,32 @@ ALTER TABLE `ew_users` -- AUTO_INCREMENT for table `ew_repositories` -- ALTER TABLE `ew_repositories` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=15; + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + -- -- AUTO_INCREMENT for table `ew_rights` -- ALTER TABLE `ew_rights` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3; + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + +-- +-- AUTO_INCREMENT for table `ew_sslcerts` +-- +ALTER TABLE `ew_sslcerts` + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + -- -- AUTO_INCREMENT for table `ew_test` -- ALTER TABLE `ew_test` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3; + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + -- -- AUTO_INCREMENT for table `ew_users` -- ALTER TABLE `ew_users` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=38;COMMIT; + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; +COMMIT; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; diff --git a/model/model.py b/model/model.py index dd7876a..ad5b416 100644 --- a/model/model.py +++ b/model/model.py @@ -2,6 +2,7 @@ from model.rights import MyRights from model.rights import InternalRights from model.users import Users +from model.sslcerts import Sslcerts from model.repositories import Repositories @@ -14,6 +15,7 @@ class Model: self.translator = translator self.users = Users(self) + self.sslcerts = Sslcerts(self) self.repositories = Repositories(self) def verify_path_entry(self, entry): diff --git a/model/sslcerts.py b/model/sslcerts.py new file mode 100644 index 0000000..1afdede --- /dev/null +++ b/model/sslcerts.py @@ -0,0 +1,128 @@ + +import exception + +from model.base import ModelItemBase, ModelManagerBase +from model.users import User + + + +class Sslcert(ModelItemBase): + def __init__(self, sslcerts, row, user = None): + super().__init__(sslcerts, row) + + self.id = int(row['id']) + self.user_id = int(row['user_id']) + self.data = str(row['data']) + + assert(not user or (type(user) is User and user.id == self.user_id)) + self.user = user + + def get_user(self): + if self.user == None: + self.user = self.model.users.get_by_id(self.user_id) + assert(self.user) + assert(self.user.id == self.user_id) + return self.user + + def reset_cache(self): + self.manager.reset_cache(self.id, self.data) + + def can_delete(self): + return self.user_id == self.rights.user_id or self.rights.issuperuser() + + def delete(self): + if self.can_delete(): + self.connection.execute( + 'DELETE FROM %T WHERE `id`=%d', + self.table(), self.id ) + self.reset_cache() + else: + raise exception.ModelDeny() + + +class Sslcerts(ModelManagerBase): + def table(self): + return 'sslcerts' + + def itemtype(self): + return Sslcert + + def reset_cache(self, id, data): + super().reset_cache(id) + self.connection.cache.reset(self.table(), {'data': data}) + + def extract_data(self, data): + prefix = '-----BEGIN CERTIFICATE-----' + suffix = '-----END CERTIFICATE-----' + data = str(data) + i0 = data.find(prefix) + i1 = data.find(suffix) + if i0 >= 0 and i1 >= 0 and i0 + len(prefix) <= i1: + data = data[i0 + len(prefix):i1] + data = ''.join(data.split()) + return data + + def verify_data(self, data): + b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' + if not data or not type(data) is str: + return False + for c in data: + if c not in b64chars: + return False + return True + + def can_read(self, user): + assert(type(user) is User) + return user.id == self.rights.user_id \ + or self.rights.issuperuser() + + def can_create(self, user): + assert(type(user) is User) + return user.id == self.rights.user_id or self.rights.issuperuser() + + def create(self, user, data): + if not self.can_create(user): + raise exception.ModelDeny() + if not self.verify_data(data): + raise exception.ModelWrongData(self.t('Bad SSL certificate data')) + if self.get_by_data(data): + raise exception.ModelWrongData(self.t('SSL Certificate is not unique')) + + self.connection.execute( + 'INSERT INTO %T SET `user_id`=%d, `data`=%s', + self.table(), user.id, data ) + id = self.connection.insert_id() + self.reset_cache(id, data) + + return self.get_by_id(id, user) + + def get_by_id(self, id, user = None): + assert(type(id) is int) + row = self.connection.cache.row(self.table(), id) + if not row: + return None + if not user: + user = self.model.users.get_by_id(row['user_id']) + if not user: + return None + if not self.can_read(user): + return None + return Sslcert(self, row, user) + + def get_by_data(self, data): + assert(type(data) is str) + rows = self.connection.cache.select(self.table(), {'data': data}) + if not rows or len(rows) > 1: + return None + return Sslcert(self, rows[0]) + + def get_list(self, user): + assert(type(user) is User) + result = list() + if not self.can_read(user): + return result + rows = self.connection.query_dict('SELECT * FROM %T WHERE `user_id`=%d ORDER BY `data`', self.table(), user.id) + for row in rows: + result.append(Sslcert(self, row, user)) + return result + diff --git a/model/users.py b/model/users.py index f17c86a..1d6d6d6 100644 --- a/model/users.py +++ b/model/users.py @@ -66,6 +66,10 @@ class User(ModelItemBase): if hash != self._password: raise exception.ModelWrongData(self.t('Password incorrect')) + sslcerts = self.model.sslcerts.get_list(self) + for sslcert in sslcerts: + sslcert.delete() + repositories = self.model.repositories.get_list(self) for repo in repositories: repo.delete() diff --git a/page/user.py b/page/user.py index 7f319a0..d6a0c79 100644 --- a/page/user.py +++ b/page/user.py @@ -118,7 +118,31 @@ class UserUpdatePage(Page): form.add_submit() form.end() answer.content += form.content - + + sslcerts = request.model.sslcerts.get_list(user) + for sslcert in sslcerts: + maxlen = 64 + data = sslcert.data + html_lines = list() + while data: + html_lines.append( answer.e(data[0:maxlen]) ) + data = data[maxlen:] + form = Form(request) + form.begin('Client SSL certificate', 'sslcert.delete') + form.add_hidden('sslcert_id', sslcert.id) + form.add_field_raw('data:', '
'.join(html_lines)) + form.add_submit('Delete') + form.end() + answer.content += form.content + + form = Form(request) + form.begin('Add client SSL certificate', 'sslcert.create') + form.add_hidden('user_id', user.id) + form.add_textarea('data:', 'data') + form.add_submit() + form.end() + answer.content += form.content + if user.id != request.model.myrights.user_id and not user.get_superuser() is None: form = Form(request) form.begin('Global rights', 'user.setsuperuser') diff --git a/repoproxy.py b/repoproxy.py index 6c835df..e1d76d2 100644 --- a/repoproxy.py +++ b/repoproxy.py @@ -2,6 +2,7 @@ import base64 import http.client +import urllib.parse import db.holder from model.model import Model @@ -133,6 +134,7 @@ class RepoProxy: login = '' password = '' + sslcert = '' if 'HTTP_AUTHORIZATION' in request.env: try: authtype, credentials = str(request.env['HTTP_AUTHORIZATION']).split() @@ -140,17 +142,27 @@ class RepoProxy: 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)