diff --git a/config.py b/config.py index 4e000c9..d8092d4 100644 --- a/config.py +++ b/config.py @@ -1,32 +1,21 @@ -import configparser -import sys import os -from etcd_wrapper import EtcdWrapper -from ldap_manager import LdapManager +from ungleich_common.etcd_wrapper import EtcdWrapper +from ungleich_common.ldap_manager import LdapManager +from ungleich_common.config_parser import StrictConfigParser config_file = os.environ.get('meow-pay-config-file', default='pay.conf') -config = configparser.ConfigParser(allow_no_value=True) +config = StrictConfigParser(allow_no_value=True) +config.read(config_file) -try: - successfully_read_files = config.read(config_file) -except configparser.Error as err: - sys.exit(err) +etcd_client = EtcdWrapper( + host=config.get('etcd', 'host'), port=config.get('etcd', 'port'), + ca_cert=config.get('etcd', 'ca_cert'), cert_key=config.get('etcd', 'cert_key'), + cert_cert=config.get('etcd', 'cert_cert') +) -if not successfully_read_files: - sys.exit(f'Config file {config_file} couldn\'t be read.') - -try: - etcd_client = EtcdWrapper( - host=config.get('etcd', 'host'), port=config.get('etcd', 'port'), - ca_cert=config.get('etcd', 'ca_cert'), cert_key=config.get('etcd', 'cert_key'), - cert_cert=config.get('etcd', 'cert_cert') - ) - - ldap_manager = LdapManager( - server=config.get('ldap', 'server'), admin_dn=config.get('ldap', 'admin_dn'), - admin_password=config.get('ldap', 'admin_password') - ) -except configparser.Error as err: - sys.exit(f'{err} in config file {config_file}.') +ldap_manager = LdapManager( + server=config.get('ldap', 'server'), admin_dn=config.get('ldap', 'admin_dn'), + admin_password=config.get('ldap', 'admin_password') +) diff --git a/etcd_wrapper.py b/etcd_wrapper.py deleted file mode 100644 index 0f55271..0000000 --- a/etcd_wrapper.py +++ /dev/null @@ -1,75 +0,0 @@ -import etcd3 -import json -import logging - -from functools import wraps - - -class EtcdEntry: - def __init__(self, meta_or_key, value, value_in_json=True): - if hasattr(meta_or_key, 'key'): - # if meta has attr 'key' then get it - self.key = meta_or_key.key.decode('utf-8') - else: - # otherwise meta is the 'key' - self.key = meta_or_key - self.value = value.decode('utf-8') - - if value_in_json: - self.value = json.loads(self.value) - - -def readable_errors(func): - @wraps(func) - def wrapper(*args, **kwargs): - try: - return func(*args, **kwargs) - except etcd3.exceptions.ConnectionFailedError as err: - raise etcd3.exceptions.ConnectionFailedError( - 'Cannot connect to etcd: is etcd running as configured in uncloud.conf?' - ) from err - except etcd3.exceptions.ConnectionTimeoutError as err: - raise etcd3.exceptions.ConnectionTimeoutError('etcd connection timeout.') from err - except Exception: - logging.exception('Some etcd error occured. See syslog for details.') - - return wrapper - - -class EtcdWrapper: - @readable_errors - def __init__(self, *args, **kwargs): - self.client = etcd3.client(*args, **kwargs) - - @readable_errors - def get(self, *args, value_in_json=True, **kwargs): - _value, _key = self.client.get(*args, **kwargs) - if _key is None or _value is None: - return None - return EtcdEntry(_key, _value, value_in_json=value_in_json) - - @readable_errors - def put(self, *args, value_in_json=True, **kwargs): - _key, _value = args - if value_in_json: - _value = json.dumps(_value) - - if not isinstance(_key, str): - _key = _key.decode('utf-8') - - return self.client.put(_key, _value, **kwargs) - - @readable_errors - def get_prefix(self, *args, value_in_json=True, **kwargs): - event_iterator = self.client.get_prefix(*args, **kwargs) - for e in event_iterator: - yield EtcdEntry(*e[::-1], value_in_json=value_in_json) - - @readable_errors - def watch_prefix(self, key, value_in_json=True): - event_iterator, cancel = self.client.watch_prefix(key) - for e in event_iterator: - if hasattr(e, '_event'): - e = getattr('e', '_event') - if e.type == e.PUT: - yield EtcdEntry(e.kv.key, e.kv.value, value_in_json=value_in_json) diff --git a/ldap_manager.py b/ldap_manager.py deleted file mode 100644 index c0a793f..0000000 --- a/ldap_manager.py +++ /dev/null @@ -1,76 +0,0 @@ -import hashlib -import random -import base64 -import sys - -from ldap3 import Server, Connection, ObjectDef, Reader, ALL -from ldap3.core import exceptions - -SALT_BYTES = 15 - - -class LdapManager: - def __init__(self, server, admin_dn, admin_password): - self.server = Server(server, get_info=ALL) - try: - self.conn = Connection(server, admin_dn, admin_password, auto_bind=True) - except exceptions.LDAPException as err: - sys.exit(f'LDAP Error: {err}') - - self.person_obj_def = ObjectDef('inetOrgPerson', self.conn) - - def get(self, query=None, search_base='dc=ungleich,dc=ch'): - kwargs = { - 'connection': self.conn, - 'object_def': self.person_obj_def, - 'base': search_base, - } - if query: - kwargs['query'] = query - r = Reader(**kwargs) - return r.search() - - def is_password_valid(self, query_value, password, query_key='mail', **kwargs): - entries = self.get(query='({}={})'.format(query_key, query_value), **kwargs) - if entries: - password_in_ldap = entries[0].userPassword.value - found = self._check_password(password_in_ldap, password) - if not found: - raise Exception('Invalid Password') - else: - return entries[0] - else: - raise ValueError('Such {}={} not found'.format(query_key, query_value)) - - @staticmethod - def _check_password(tagged_digest_salt, password): - digest_salt_b64 = tagged_digest_salt[6:] - digest_salt = base64.decodebytes(digest_salt_b64) - digest = digest_salt[:20] - salt = digest_salt[20:] - - sha = hashlib.sha1(password.encode('utf-8')) - sha.update(salt) - - return digest == sha.digest() - - @staticmethod - def ssha_password(password): - """ - Apply the SSHA password hashing scheme to the given *password*. - *password* must be a :class:`bytes` object, containing the utf-8 - encoded password. - - Return a :class:`bytes` object containing ``ascii``-compatible data - which can be used as LDAP value, e.g. after armoring it once more using - base64 or decoding it to unicode from ``ascii``. - """ - - sha1 = hashlib.sha1() - salt = random.SystemRandom().getrandbits(SALT_BYTES * 8).to_bytes(SALT_BYTES, 'little') - sha1.update(password) - sha1.update(salt) - - digest = sha1.digest() - passwd = b'{SSHA}' + base64.b64encode(digest + salt) - return passwd diff --git a/requirements.txt b/requirements.txt index 843641e..6b6c77b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ ldap3 etcd3 stripe flask +git+git://code.ungleich.ch/ahmedbilal/ungleich-common@master#egg=ungleich-common \ No newline at end of file diff --git a/schemas.py b/schemas.py index 25555f9..8285491 100644 --- a/schemas.py +++ b/schemas.py @@ -5,91 +5,7 @@ import math from config import ldap_manager, etcd_client from helper import resolve_product - - -class ValidationException(Exception): - """Validation Error""" - - -class Field: - def __init__(self, _name, _type, _value=None, validators=None, disable_validation=False): - self.validation_disabled = disable_validation - self.name = _name - self.value = _value - self.type = _type - self.validators = validators or [] - - def is_valid(self): - if not self.validation_disabled: - if not isinstance(self.value, self.type): - try: - self.value = self.type(self.value) - except Exception: - raise ValidationException("Incorrect Type for '{}' field".format(self.name)) - - for validator in self.validators: - validator() - - def __repr__(self): - return self.name - - -class BaseSchema: - def __init__(self): - self.objects = {} - - def validation(self): - # custom validation is optional - return True - - def get_fields(self): - return [getattr(self, field) for field in dir(self) if isinstance(getattr(self, field), Field)] - - def is_valid(self): - for field in self.get_fields(): - field.is_valid() - self.validation() - - def get_cleaned_values(self): - field_kv_dict = { - field.name: field.value - for field in self.get_fields() - } - cleaned_values = field_kv_dict - cleaned_values.update(self.objects) - - return cleaned_values - - def add_schema(self, schema, data, under_field_name=None): - s = schema(data) - s.is_valid() - - base = self - if under_field_name: - # Create a field in self - setattr(self, under_field_name, Field(under_field_name, dict, _value={}, disable_validation=True)) - base = getattr(self, under_field_name) - - for field in s.get_fields(): - if under_field_name: - getattr(base, 'value')[field.name] = field.value - else: - setattr(base, field.name, field) - - self.objects.update(s.objects) - - @staticmethod - def get(dictionary: dict, key: str, return_default=False, default=None): - if dictionary is None: - raise ValidationException('No data provided at all.') - try: - value = dictionary[key] - except KeyError: - if return_default: - return {'_value': default, 'disable_validation': True} - raise ValidationException("Missing data for '{}' field.".format(key)) - else: - return {'_value': value, 'disable_validation': False} +from ungleich_common.schemas import BaseSchema, Field, ValidationException class AddProductSchema(BaseSchema): diff --git a/stripe_utils.py b/stripe_utils.py index 1004b86..a125474 100644 --- a/stripe_utils.py +++ b/stripe_utils.py @@ -2,15 +2,10 @@ import re import stripe import stripe.error import logging -import sys -from configparser import Error as ConfigParserError -from config import etcd_client as client, config as config, config_file +from config import etcd_client as client, config as config -try: - stripe.api_key = config.get('stripe', 'private_key') -except ConfigParserError as err: - sys.exit(f'{err} in config file {config_file}') +stripe.api_key = config.get('stripe', 'private_key') def handle_stripe_error(f):