forked from uncloud/uncloud
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
21
config.py
21
config.py
|
@ -1,23 +1,14 @@
|
||||||
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:
|
|
||||||
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(
|
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'),
|
||||||
|
@ -28,5 +19,3 @@ try:
|
||||||
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…
Reference in a new issue