A lot of code moved to ungleich-common
This commit is contained in:
		
					parent
					
						
							
								ce709c3b6f
							
						
					
				
			
			
				commit
				
					
						cee92f2e99
					
				
			
		
					 6 changed files with 18 additions and 268 deletions
				
			
		
							
								
								
									
										29
									
								
								config.py
									
										
									
									
									
								
							
							
						
						
									
										29
									
								
								config.py
									
										
									
									
									
								
							| 
						 | 
					@ -1,32 +1,21 @@
 | 
				
			||||||
import configparser
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from etcd_wrapper import EtcdWrapper
 | 
					from ungleich_common.etcd_wrapper import EtcdWrapper
 | 
				
			||||||
from ldap_manager import LdapManager
 | 
					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_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:
 | 
					etcd_client = EtcdWrapper(
 | 
				
			||||||
    successfully_read_files = config.read(config_file)
 | 
					 | 
				
			||||||
except configparser.Error as err:
 | 
					 | 
				
			||||||
    sys.exit(err)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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'),
 | 
					    host=config.get('etcd', 'host'), port=config.get('etcd', 'port'),
 | 
				
			||||||
    ca_cert=config.get('etcd', 'ca_cert'), cert_key=config.get('etcd', 'cert_key'),
 | 
					    ca_cert=config.get('etcd', 'ca_cert'), cert_key=config.get('etcd', 'cert_key'),
 | 
				
			||||||
    cert_cert=config.get('etcd', 'cert_cert')
 | 
					    cert_cert=config.get('etcd', 'cert_cert')
 | 
				
			||||||
    )
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ldap_manager = LdapManager(
 | 
					ldap_manager = LdapManager(
 | 
				
			||||||
    server=config.get('ldap', 'server'), admin_dn=config.get('ldap', 'admin_dn'),
 | 
					    server=config.get('ldap', 'server'), admin_dn=config.get('ldap', 'admin_dn'),
 | 
				
			||||||
    admin_password=config.get('ldap', 'admin_password')
 | 
					    admin_password=config.get('ldap', 'admin_password')
 | 
				
			||||||
    )
 | 
					)
 | 
				
			||||||
except configparser.Error as err:
 | 
					 | 
				
			||||||
    sys.exit(f'{err} in config file {config_file}.')
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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)
 | 
					 | 
				
			||||||
| 
						 | 
					@ -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
 | 
					 | 
				
			||||||
| 
						 | 
					@ -2,3 +2,4 @@ ldap3
 | 
				
			||||||
etcd3
 | 
					etcd3
 | 
				
			||||||
stripe
 | 
					stripe
 | 
				
			||||||
flask
 | 
					flask
 | 
				
			||||||
 | 
					git+git://code.ungleich.ch/ahmedbilal/ungleich-common@master#egg=ungleich-common
 | 
				
			||||||
							
								
								
									
										86
									
								
								schemas.py
									
										
									
									
									
								
							
							
						
						
									
										86
									
								
								schemas.py
									
										
									
									
									
								
							| 
						 | 
					@ -5,91 +5,7 @@ import math
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from config import ldap_manager, etcd_client
 | 
					from config import ldap_manager, etcd_client
 | 
				
			||||||
from helper import resolve_product
 | 
					from helper import resolve_product
 | 
				
			||||||
 | 
					from ungleich_common.schemas import BaseSchema, Field, ValidationException
 | 
				
			||||||
 | 
					 | 
				
			||||||
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}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AddProductSchema(BaseSchema):
 | 
					class AddProductSchema(BaseSchema):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,15 +2,10 @@ import re
 | 
				
			||||||
import stripe
 | 
					import stripe
 | 
				
			||||||
import stripe.error
 | 
					import stripe.error
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from configparser import Error as ConfigParserError
 | 
					from config import etcd_client as client, config as config
 | 
				
			||||||
from config import etcd_client as client, config as config, config_file
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
try:
 | 
					stripe.api_key = config.get('stripe', 'private_key')
 | 
				
			||||||
    stripe.api_key = config.get('stripe', 'private_key')
 | 
					 | 
				
			||||||
except ConfigParserError as err:
 | 
					 | 
				
			||||||
    sys.exit(f'{err} in config file {config_file}')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def handle_stripe_error(f):
 | 
					def handle_stripe_error(f):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue