py.lib

Yohn Y. 2020-07-24 Parent:7874a3e2281e Child:50ff84d0bc56

13:cab7fedf8432 Browse Files

.. Реструктуризация

Alarm.py awNet/__init__.py awNet/db/__init__.py awNet/db/pg.py awNet/db/sqlite.py awNet/webapp/__init__.py awNet/webapp/tool/__init__.py awNet/webapp/tool/url.py awNet/webapp/win.py db/__init__.py db/ldap.py db/pg/psy.py db/pg/py.py db/sqlite.py ldapUtil.py ldap_utils/ldap_passwd_changer.py ldap_utils/ldap_util.py log/slog_syslogger.py log/slog_syslogger_tiny.py log/syslogger.py webapp/__init__.py webapp/tool/__init__.py webapp/tool/url.py webapp/win.py

     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/Alarm.py	Fri Jul 24 14:40:37 2020 +0300
     1.3 @@ -0,0 +1,25 @@
     1.4 +#!/usr/bin/python
     1.5 +# -*- coding: utf-8 -*-
     1.6 +
     1.7 +from signal import SIGALRM, alarm, signal
     1.8 +
     1.9 +class AlarmTimeout(Exception): pass
    1.10 +
    1.11 +def _handler(sig, frame):
    1.12 +	raise AlarmTimeout('Operation timeout')
    1.13 +
    1.14 +class MkAlarm(object):
    1.15 +	def __init__(self, timeout):
    1.16 +		signal(SIGALRM, _handler)
    1.17 +		alarm(timeout)
    1.18 +		
    1.19 +	def __enter__(self):
    1.20 +		pass
    1.21 +		
    1.22 +	def __exit__(self, eType, eObj, tb):
    1.23 +		if eObj == None:
    1.24 +			alarm(0)
    1.25 +	
    1.26 +	def __del__(self):
    1.27 +		alarm(0)
    1.28 +			
    1.29 \ No newline at end of file
     2.1 --- a/awNet/__init__.py	Tue Nov 12 21:28:23 2019 +0300
     2.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.3 @@ -1,4 +0,0 @@
     2.4 -# -*- coding: utf-8 -*-
     2.5 -# ---
     2.6 -#  Контейнер модулей собственного производства общего назначения
     2.7 -# ---
     2.8 \ No newline at end of file
     3.1 --- a/awNet/db/__init__.py	Tue Nov 12 21:28:23 2019 +0300
     3.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.3 @@ -1,4 +0,0 @@
     3.4 -# -*- coding: utf-8 -*-
     3.5 -# ---
     3.6 -#  Модули для работы с базами данных
     3.7 -# ---
     3.8 \ No newline at end of file
     4.1 --- a/awNet/db/pg.py	Tue Nov 12 21:28:23 2019 +0300
     4.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.3 @@ -1,31 +0,0 @@
     4.4 -import postgresql.driver as DBM
     4.5 -import postgresql.exceptions as DBErr
     4.6 -
     4.7 -def GetDB(*, name, user, passwd, host='localhost', port=5432):
     4.8 -  """
     4.9 -  На случай когда лениво каждый раз вызывать базу со всеми регалиями,
    4.10 -  регалии сохраняем в специального вызывателя :)))
    4.11 -  """
    4.12 -  def func ():
    4.13 -    return DB(name, user, passwd, host=host, port=port)
    4.14 -  return func
    4.15 -
    4.16 -class DB:
    4.17 -  def __init__(self, db_name, db_user, db_passwd, *, host='localhost', port=5432):
    4.18 -    self._db = DBM.connect(
    4.19 -      host = host,
    4.20 -      port = port,
    4.21 -      user = db_user,
    4.22 -      password = db_passwd,
    4.23 -      database = db_name
    4.24 -    )
    4.25 -    self.xact = self._db.xact
    4.26 -    self.q = self._db.query
    4.27 -    self.pq = self._db.prepare
    4.28 -    self.ex = self._db.execute
    4.29 -    self.proc = self._db.proc
    4.30 -    self.close = self._db.close
    4.31 -  
    4.32 -  def __del__(self):
    4.33 -    if self._db.state != 'closed':
    4.34 -      self._db.close()
    4.35 \ No newline at end of file
     5.1 --- a/awNet/db/sqlite.py	Tue Nov 12 21:28:23 2019 +0300
     5.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.3 @@ -1,55 +0,0 @@
     5.4 -# -*- coding: utf-8 -*-
     5.5 -# Dep
     5.6 -from sqlite3 import connect as _sqlite_connect
     5.7 -# Exceptions
     5.8 -from sqlite3 import Warning, Error, DatabaseError, IntegrityError, ProgrammingError
     5.9 -
    5.10 -# --- # PROCS # --- #
    5.11 -def GetDB(db_file):
    5.12 -	def func():
    5.13 -		return DB(db_file)
    5.14 -	return func
    5.15 -
    5.16 -# --- # CLASS # --- #
    5.17 -class DB:
    5.18 -	def __init__(self, db_file):
    5.19 -		self._con = None
    5.20 -		self._con = _sqlite_connect(db_file)
    5.21 -		self.q = self._con.execute
    5.22 -		self.qm = self._con.executemany
    5.23 -		self.commit = self._con.commit
    5.24 -		self.rollback = self._con.rollback
    5.25 -		
    5.26 -		# DB PREP
    5.27 -		self.cq("PRAGMA journal=WAL")
    5.28 -	
    5.29 -	def __call__(self, *a, **wa):
    5.30 -		return self.q(*a, **wa)
    5.31 -	
    5.32 -	def __del__(self):
    5.33 -		if self._con == None: return
    5.34 -		try:
    5.35 -			self.rollback()     
    5.36 -			self._con.close()
    5.37 -		except Error:
    5.38 -			pass
    5.39 -		except Warning:
    5.40 -			pass
    5.41 -	
    5.42 -	def cq(self, *a, **wa):
    5.43 -		try:
    5.44 -			res = self.q(*a, **wa)
    5.45 -			self.commit()
    5.46 -			return res
    5.47 -		except Exception as e:
    5.48 -			self.rollback()
    5.49 -			raise e
    5.50 -		
    5.51 -	def cqm(self, *a, **wa):
    5.52 -		try:
    5.53 -			res = self.qm(*a, **wa)
    5.54 -			self.commit()
    5.55 -			return res
    5.56 -		except Exception as e:
    5.57 -			self.rollback()
    5.58 -			raise e
    5.59 \ No newline at end of file
     7.1 --- a/awNet/webapp/tool/__init__.py	Tue Nov 12 21:28:23 2019 +0300
     7.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.3 @@ -1,4 +0,0 @@
     7.4 -# -*- coding: utf-8 -*-
     7.5 -# ---
     7.6 -#  
     7.7 -# ---
     7.8 \ No newline at end of file
     8.1 --- a/awNet/webapp/tool/url.py	Tue Nov 12 21:28:23 2019 +0300
     8.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.3 @@ -1,12 +0,0 @@
     8.4 -# -*- coding: utf-8 -*-
     8.5 -# ---
     8.6 -#  Инструменты для работы с URL
     8.7 -# ---
     8.8 -from base64 import urlsafe_b64encode as _b64e, urlsafe_b64decode as _b64d
     8.9 -from urllib.parse import quote as urlquote
    8.10 -
    8.11 -def encode(buf):
    8.12 -  return _b64e(buf.encode('UTF-8')).decode('UTF-8')
    8.13 -  
    8.14 -def decode(buf):
    8.15 -  return _b64d(buf.encode('UTF-8')).decode('UTF-8')
     9.1 --- a/awNet/webapp/win.py	Tue Nov 12 21:28:23 2019 +0300
     9.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.3 @@ -1,72 +0,0 @@
     9.4 -# -*- coding: utf-8 -*-
     9.5 -# ---
     9.6 -#  Модуль среды приложений для Windows
     9.7 -# ---
     9.8 -
     9.9 -from bottle import Bottle as App, request as Req, response as Ans, SimpleTemplate as BottleTemplate, abort as Abort
    9.10 -from os.path import dirname, abspath as pAbs, split as pSplit, join as pJoin
    9.11 -
    9.12 -# --- CLASSES ---
    9.13 -class ReqEnv:
    9.14 -	def __init__(self):
    9.15 -		self.urlBase = Req.environ.get('SCRIPT_NAME', '')
    9.16 -		
    9.17 -	def getUrl(self, url):
    9.18 -		url = str(url)
    9.19 -		buf = self.urlBase
    9.20 -		buf += url if url.startswith('/') else '/' + url
    9.21 -		return buf
    9.22 -	
    9.23 -	def __getitem__(self, key):
    9.24 -		return Req.environ[key]
    9.25 -		
    9.26 -	def __iter__(self):
    9.27 -		for key in Req.environ.keys():
    9.28 -			yield key
    9.29 -			
    9.30 -	@staticmethod
    9.31 -	def abort(*a, **ka):
    9.32 -		Abort(*a, **ka)
    9.33 -	
    9.34 -	@staticmethod	
    9.35 -	def get(self):
    9.36 -		return Req.GET
    9.37 -		
    9.38 -	@staticmethod	
    9.39 -	def post():
    9.40 -		return Req.POST
    9.41 -		
    9.42 -class Env:
    9.43 -	def __init__(self, scFile):
    9.44 -		self.wd = pSplit(dirname(pAbs(scFile)))[0]
    9.45 -		self.static = pJoin(self.wd, 'static')
    9.46 -		
    9.47 -	def http(self, tplName):
    9.48 -		return BottleTemplate(name=pJoin(self.wd, 'http', '%s.html' % tplName)).render
    9.49 -		
    9.50 -	def db(self):
    9.51 -		""" 
    9.52 -		Путь к базе SQLite, иные типы БД дожны иметь более
    9.53 -		серьёзные средства авторизации
    9.54 -		"""
    9.55 -		return pJoin(self.wd, 'db.sqlite')
    9.56 -	
    9.57 -	@staticmethod
    9.58 -	def httpTpl(buf):
    9.59 -		return BottleTemplate(buf).render
    9.60 -	
    9.61 -	@staticmethod
    9.62 -	def app():
    9.63 -		return App()
    9.64 -		
    9.65 -	@staticmethod
    9.66 -	def appRun(app):
    9.67 -		app.run(server='cgi')
    9.68 -	
    9.69 -# --- PROCS ---
    9.70 -def runApp(app):
    9.71 -	Env.appRun(app)
    9.72 -	
    9.73 -def Template(buf):
    9.74 -	return Env.httpTpl(buf)
    9.75 -	
    9.76 \ No newline at end of file
    11.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    11.2 +++ b/db/ldap.py	Fri Jul 24 14:40:37 2020 +0300
    11.3 @@ -0,0 +1,84 @@
    11.4 +# coding: utf-8
    11.5 +
    11.6 +from ldap3 import Server, Connection, SIMPLE, SUBTREE
    11.7 +
    11.8 +LDAP_PAGE=1000
    11.9 +
   11.10 +class LdapError(Exception): pass
   11.11 +
   11.12 +class LdapRes():
   11.13 +	def __init__(self, dn, attrib):
   11.14 +		self.dn = dn
   11.15 +		self.attr = attrib
   11.16 +		
   11.17 +	def __getitem__(self, item):
   11.18 +		return self.attr[item]
   11.19 +	
   11.20 +	def __iter__(self):
   11.21 +		return iter(self.attr)
   11.22 +	
   11.23 +	def __repr__(self):
   11.24 +		return '<LdapRes: dn: %s>' % self.dn
   11.25 +	
   11.26 +	@classmethod
   11.27 +	def fromLdapQuery(cls, q):
   11.28 +		if not isinstance(q, dict):
   11.29 +			raise LdapError('LdapRes: Parsing Error, not ldap response item')
   11.30 +		if not (('dn' in q) and ('attributes' in q)):
   11.31 +			raise LdapError('LdapRes: Parsing Error, format mismatch')
   11.32 +		
   11.33 +		return cls(q['dn'], q['attributes'])
   11.34 +
   11.35 +class Ldap():
   11.36 +	def __init__(self, host, user, passwd, timeout=60, queryTimeout=300, **kwa):
   11.37 +		if 'baseDN' in kwa:
   11.38 +			self._baseDN = kwa['baseDN']
   11.39 +			del kwa['baseDN']
   11.40 +		else:
   11.41 +			self._baseDN = None
   11.42 +		ldapSrv = Server(host, connect_timeout=timeout, **kwa)
   11.43 +		self._conn = self._makeConnFabric(ldapSrv, authentication=SIMPLE,
   11.44 +			user=user, password=passwd,
   11.45 +			check_names=True, lazy=True,
   11.46 +			auto_referrals=False, raise_exceptions=True, auto_range=True
   11.47 +		)
   11.48 +		self.queryTimeout = queryTimeout
   11.49 +		
   11.50 +	def __call__(self, filter, attrib, queryTimeout=None, baseDN = None):
   11.51 +		if baseDN is None:
   11.52 +			if self._baseDN is None:
   11.53 +				raise LdapError('No base dn on query execution')
   11.54 +			baseDN = self._baseDN
   11.55 +		if queryTimeout is None:
   11.56 +			queryTimeout = self.queryTimeout
   11.57 +		try:
   11.58 +			conn = self._conn()
   11.59 +			with conn:
   11.60 +				conn.open()
   11.61 +				conn.bind()
   11.62 +			
   11.63 +				res = conn.extend.standard.paged_search(baseDN,
   11.64 +					filter, attributes=attrib, paged_size=LDAP_PAGE, generator=False,
   11.65 +					search_scope=SUBTREE, time_limit=queryTimeout
   11.66 +				)
   11.67 +
   11.68 +				for i in res:
   11.69 +					if i['type'] == 'searchResEntry':
   11.70 +						yield LdapRes.fromLdapQuery(i)
   11.71 +			
   11.72 +		except Exception as e:
   11.73 +			raise LdapError("Error on get data (%s): %s" % (type(e), str(e)), *e.args[1:])
   11.74 +		
   11.75 +	def getList(self, *a, **kwa):
   11.76 +		return [ i for i in self(*a, **kwa) ]
   11.77 +	
   11.78 +	@staticmethod
   11.79 +	def _makeConnFabric(*a, **kwa):
   11.80 +		def _func():
   11.81 +			return Connection(*a, **kwa)
   11.82 +		
   11.83 +		return _func
   11.84 +		
   11.85 +		
   11.86 +		
   11.87 +		
   11.88 \ No newline at end of file
    12.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    12.2 +++ b/db/pg/psy.py	Fri Jul 24 14:40:37 2020 +0300
    12.3 @@ -0,0 +1,69 @@
    12.4 +# -*- coding: utf-8 -*-
    12.5 +import psycopg2
    12.6 +from psycopg2 import Error as DBError, Warning, DataError, IntegrityError, ProgrammingError
    12.7 +# DBError: Клас-родитель для всех ошибок
    12.8 +# DataError: Проблема с данными (деление на ноль, численное изначение за областью значение и пр.)
    12.9 +# IntegrityError: задеты ограничения целостности БД (уникольность, внешние ключи и пр.)
   12.10 +# ProgrammingError: ошибки в запросах (таблица не найдена, или уже существует, ошибка в синтаксисе запроса и пр.)
   12.11 +
   12.12 +import psycopg2.extensions
   12.13 +psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
   12.14 +psycopg2.extensions.register_type(psycopg2.extensions.UNICODEARRAY)
   12.15 +
   12.16 +class Error(Exception): pass
   12.17 +
   12.18 +
   12.19 +def dbFactory(**kva):
   12.20 +	""" Фабрика объектов	
   12.21 +	"""
   12.22 +	def _func():
   12.23 +		return DB(**kva)
   12.24 +	return _func
   12.25 +
   12.26 +class DB(object):
   12.27 +	def __init__(self, **kva):
   12.28 +		self._conn = psycopg2.connect(**kva)
   12.29 +		self.commit = self._conn.commit
   12.30 +		self.rollback = self._conn.rollback
   12.31 +		self.cursor = self._conn.cursor
   12.32 +		self.reset = self._conn.reset
   12.33 +	
   12.34 +	def __enter__(self, *a, **kva):
   12.35 +		return self._conn.__enter__(*a, **kva)
   12.36 +		
   12.37 +	def __exit__(self, *a, **kva):
   12.38 +		return self._conn.__exit__(*a, **kva)
   12.39 +	
   12.40 +	def __call__(self, *a, **kva):
   12.41 +		_cur = self.cursor()
   12.42 +		_cur.execute(*a, **kva)
   12.43 +		return _cur
   12.44 +		
   12.45 +	def cq(self, *a, **kva):
   12.46 +		try:
   12.47 +			_cur = self.cursor()
   12.48 +			_cur.execute(*a, **kva)
   12.49 +			self.commit()
   12.50 +			return _cur
   12.51 +		except Exception as e:
   12.52 +			self.rollback()
   12.53 +			raise e
   12.54 +			
   12.55 +	def close(self):
   12.56 +		try:
   12.57 +			self.rollback()
   12.58 +			self._conn.close()
   12.59 +		except:
   12.60 +			pass		
   12.61 +	
   12.62 +	def __del__(self):
   12.63 +		self.close()
   12.64 +	
   12.65 +	def __repr__(self):
   12.66 +		return 'DB(%s)' % self._conn.dsn
   12.67 +		
   12.68 +	
   12.69 +		
   12.70 +		
   12.71 +
   12.72 +
    13.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    13.2 +++ b/db/pg/py.py	Fri Jul 24 14:40:37 2020 +0300
    13.3 @@ -0,0 +1,31 @@
    13.4 +import postgresql.driver as DBM
    13.5 +import postgresql.exceptions as DBErr
    13.6 +
    13.7 +def GetDB(*, name, user, passwd, host='localhost', port=5432):
    13.8 +  """
    13.9 +  На случай когда лениво каждый раз вызывать базу со всеми регалиями,
   13.10 +  регалии сохраняем в специального вызывателя :)))
   13.11 +  """
   13.12 +  def func ():
   13.13 +    return DB(name, user, passwd, host=host, port=port)
   13.14 +  return func
   13.15 +
   13.16 +class DB:
   13.17 +  def __init__(self, db_name, db_user, db_passwd, *, host='localhost', port=5432):
   13.18 +    self._db = DBM.connect(
   13.19 +      host = host,
   13.20 +      port = port,
   13.21 +      user = db_user,
   13.22 +      password = db_passwd,
   13.23 +      database = db_name
   13.24 +    )
   13.25 +    self.xact = self._db.xact
   13.26 +    self.q = self._db.query
   13.27 +    self.pq = self._db.prepare
   13.28 +    self.ex = self._db.execute
   13.29 +    self.proc = self._db.proc
   13.30 +    self.close = self._db.close
   13.31 +  
   13.32 +  def __del__(self):
   13.33 +    if self._db.state != 'closed':
   13.34 +      self._db.close()
   13.35 \ No newline at end of file
    14.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    14.2 +++ b/db/sqlite.py	Fri Jul 24 14:40:37 2020 +0300
    14.3 @@ -0,0 +1,34 @@
    14.4 +# coding: utf-8
    14.5 +import sqlite3
    14.6 +
    14.7 +class DB:
    14.8 +	def __init__(self, db_file):
    14.9 +		self._con = sqlite3.connect(db_file)
   14.10 +		self._ex = self._con.execute
   14.11 +		self.commit = self._con.commit
   14.12 +		self.rollback = self._con.rollback
   14.13 +		
   14.14 +		# DB PREP
   14.15 +		self._ex("PRAGMA journal=WAL")
   14.16 +		self.commit()
   14.17 +	
   14.18 +	def __del__(self):
   14.19 +		try:
   14.20 +			self.rollback()     
   14.21 +			self._con.close()
   14.22 +		except:
   14.23 +			pass
   14.24 +	
   14.25 +	def __call__(self, *a, **kwa):
   14.26 +		cur = self._con.cursor()
   14.27 +		cur.execute(*a, **kwa)
   14.28 +		return cur
   14.29 +	
   14.30 +	def cq(self, *a, **wa):
   14.31 +		try:
   14.32 +			res = self(*a, **wa)
   14.33 +			self.commit()
   14.34 +			return res
   14.35 +		except Exception as e:
   14.36 +			self.rollback()
   14.37 +			raise e
    15.1 --- a/ldapUtil.py	Tue Nov 12 21:28:23 2019 +0300
    15.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    15.3 @@ -1,86 +0,0 @@
    15.4 -# coding: utf-8
    15.5 -import ldap
    15.6 -
    15.7 -# ===========================================================
    15.8 -# Статус завершения оперций
    15.9 -STATUS_OK = 0           # Всё ОК.
   15.10 -STATUS_BADAUTH = 1      # Не верные пользователь или пароль.
   15.11 -STATUS_SERVERDOWN = 2   # Сервер не доступен.
   15.12 -STATUS_SERVERERROR = 3  # Ошибка при взаимодействии с сервером.
   15.13 -
   15.14 -class LdapError(Exception): pass
   15.15 -
   15.16 -class LdapAuth(object):
   15.17 -	def __init__(self, server, userPrefix = 'CORP\\', baseDN = 'DC=example,DC=net', attr = []):
   15.18 -		self.server = server
   15.19 -		self.prefix = userPrefix
   15.20 -		self.status = ''
   15.21 -		self.baseDN = baseDN
   15.22 -		self.statusCode = 0
   15.23 -		self.groups = None
   15.24 -		self.attr = None
   15.25 -		self._needAttr = [ i for i in map(str, attr) ]
   15.26 -		
   15.27 -	def __call__(self, user, passwd):
   15.28 -		try:
   15.29 -			conn = ldap.initialize(self.server)
   15.30 -			conn.set_option(ldap.OPT_REFERRALS, 0)
   15.31 -			conn.simple_bind_s(self.prefix + user, passwd)
   15.32 -		except ldap.INVALID_CREDENTIALS:
   15.33 -			conn.unbind()
   15.34 -			self.status = 'Invalid credentials'
   15.35 -			self.statusCode = STATUS_BADAUTH
   15.36 -			return False
   15.37 -		except ldap.SERVER_DOWN:
   15.38 -			self.status = 'Server is down'
   15.39 -			self.statusCode = STATUS_SERVERDOWN
   15.40 -			return False
   15.41 -		
   15.42 -		self.groups = []
   15.43 -		try:
   15.44 -			ldapData = conn.search_s(self.baseDN, ldap.SCOPE_SUBTREE, '(cn=%s)' % user, ['memberOf'] + self._needAttr)[0][1]
   15.45 -			for i in ldapData['memberOf']:
   15.46 -				self.groups.append(i.split(',')[0].split('=')[1].decode('utf-8'))
   15.47 -			del ldapData['memberOf']
   15.48 -			self.attr = ldapData
   15.49 -		except KeyError:
   15.50 -			self.status = 'User object from LDAP is wrong, it can be anonymous logon'
   15.51 -			self.statusCode = STATUS_SERVERERROR
   15.52 -			return False
   15.53 -		finally:
   15.54 -			conn.unbind()			
   15.55 -
   15.56 -		return True
   15.57 -	
   15.58 -	def __getitem__(self, key):
   15.59 -		return self.attr[key]
   15.60 -	
   15.61 -	def memberOf(self, group):
   15.62 -		"""Проверка на присутствие у пользователя некоторой группы
   15.63 -		"""
   15.64 -		if self.groups == None:
   15.65 -			raise LdapError('Request membership before auth call.')
   15.66 -			
   15.67 -		if not isinstance(group, unicode):
   15.68 -			if isinstance(group, str):
   15.69 -				group = group.decode('utf-8')
   15.70 -			else:
   15.71 -				group = str(group).decode('utf-8')
   15.72 -		
   15.73 -		if group in self.groups:
   15.74 -			return True
   15.75 -		else:
   15.76 -			return False
   15.77 -	
   15.78 -	def __contains__(self, group):
   15.79 -		return self.memberOf(group)
   15.80 -		
   15.81 -	def memberOfGroups(self, groups):
   15.82 -		if not len(groups) > 0:
   15.83 -			return False
   15.84 -		
   15.85 -		for group in groups:
   15.86 -			if not group in self:
   15.87 -				return False
   15.88 -		
   15.89 -		return True
    16.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    16.2 +++ b/ldap_utils/ldap_passwd_changer.py	Fri Jul 24 14:40:37 2020 +0300
    16.3 @@ -0,0 +1,111 @@
    16.4 +# coding: utf-8
    16.5 +"""
    16.6 +Смена пароля на пользователя в AD посредством Python
    16.7 +
    16.8 +"""
    16.9 +
   16.10 +from ldap3 import Server, Connection, SIMPLE, SUBTREE
   16.11 +from ldap3.core.exceptions import LDAPException
   16.12 +from secrets import token_urlsafe
   16.13 +import string
   16.14 +
   16.15 +class LdapError(Exception):
   16.16 +    @staticmethod
   16.17 +    def errFmt(e):
   16.18 +        _buf = ' '.join(str(e).split())            # всё в одну строку с одним пробелом
   16.19 +                                                   # между словами
   16.20 +        return "%s(%s)" % (type(e).__name__, _buf)
   16.21 +        
   16.22 +class LdapServerError(LdapError): pass
   16.23 +class LdapPassGenError(LdapError): pass
   16.24 +class LdapNotFound(LdapError): pass
   16.25 +
   16.26 +
   16.27 +class LdapPasswdChanger(object):
   16.28 +    def __init__(self, ldap_server_uri, ldap_user, ldap_passwd, ldap_baseDN, ldap_timeout=120, ldap_reconnect=5, passwd_len=25):
   16.29 +        self.ldap_server_uri = ldap_server_uri  # URI к серверу: ldaps://server_name:port_number
   16.30 +        self.ldap_user = ldap_user              # Пользователь, имеющий право сброса паролей
   16.31 +        self.ldap_passwd = ldap_passwd          # Пароль к тому пользователю
   16.32 +        self.ldap_baseDN = ldap_baseDN
   16.33 +        self.ldap_timeout = ldap_timeout        # Таймаут операций с сервером
   16.34 +        self.passwd_len = passwd_len            # Желаемая длинна пароля
   16.35 +        self.ldap_reconnect = ldap_reconnect    # Количество попыток переподключения к серверу
   16.36 +
   16.37 +        # Заготовка под пароли
   16.38 +        self._set_punct = set(string.punctuation)
   16.39 +        self._set_uper = set(string.ascii_uppercase)
   16.40 +        self._set_lower = set(string.ascii_lowercase)
   16.41 +
   16.42 +    def getPasswd(self):
   16.43 +        _cnt = 0
   16.44 +        while True:
   16.45 +            res = token_urlsafe(self.passwd_len)
   16.46 +            _res_set = set(res)
   16.47 +            if ((_res_set & self._set_punct)
   16.48 +                and (_res_set & self._set_uper)
   16.49 +                and (_res_set & self._set_lower)
   16.50 +            ):
   16.51 +                return res
   16.52 +
   16.53 +            _cnt += 1
   16.54 +            if _cnt > 60:
   16.55 +                raise LdapPassGenError('Не удалось сгенерировать пароль')
   16.56 +
   16.57 +
   16.58 +    def changePass(self, login):
   16.59 +        _reconnects = self.ldap_reconnect
   16.60 +        if _reconnects is None or _reconnects < 1:
   16.61 +            _reconnects = 1
   16.62 +
   16.63 +        ldapConn = None
   16.64 +        while True:
   16.65 +            _reconnects -= 1
   16.66 +            try:
   16.67 +                ldapSrv = Server(self.ldap_server_uri, connect_timeout=self.ldap_timeout)
   16.68 +                ldapConn = Connection(ldapSrv, authentication=SIMPLE,
   16.69 +                                      user=self.ldap_user, password=self.ldap_passwd,
   16.70 +                                      check_names=True,
   16.71 +                                      auto_referrals=False, raise_exceptions=True, auto_range=True,
   16.72 +                                      )
   16.73 +                ldapConn.open()
   16.74 +                if not ldapConn.bind():
   16.75 +                    continue
   16.76 +
   16.77 +                break
   16.78 +            except LDAPException as e:
   16.79 +                if not _reconnects > 0:
   16.80 +                    raise LdapServerError('Ошибка подключения к серверу: %s' % LdapServerError.errFmt(e))
   16.81 +
   16.82 +        filter = '(sAMAccountName=%s)' % login
   16.83 +        try:
   16.84 +            if not ldapConn.search(self.ldap_baseDN, filter):
   16.85 +                raise LdapServerError('Сервер вернул статус ощибки: %(desc)s(%(code)s) - %(msg)s' % {
   16.86 +                    'desc': ldapConn.result['description'],
   16.87 +                    'code': ldapConn.result['result'],
   16.88 +                    'msg': ldapConn.result['message'],
   16.89 +                    })
   16.90 +        except LDAPException as e:
   16.91 +            raise LdapServerError('Ошибка при поиска УЗ в ldap: %s' % LdapServerError.errFmt(e))
   16.92 +
   16.93 +        if not len(ldapConn.entries) > 0:
   16.94 +            raise LdapNotFound('Не найдена учётная запись: %s' % login)
   16.95 +        elif len(ldapConn.entries) != 1:
   16.96 +            raise LdapError('Наёдено более одной УЗ по заданному имени %s: %s - %s' %(
   16.97 +                login,
   16.98 +                len(ldapConn.entries),
   16.99 +                '; '.join([i.entry_dn for i in ldapConn.entries[:10]])
  16.100 +            ))
  16.101 +
  16.102 +        login_dn = ldapConn.entries[0].entry_dn
  16.103 +        new_passwd = self.getPasswd()
  16.104 +        try:
  16.105 +            ldapConn.extend.microsoft.modify_password(login_dn, new_password=new_passwd, old_password=None)
  16.106 +        except LDAPException as e:
  16.107 +            raise LdapServerError('Ошибка при смене пароля на УЗ: %s' % LdapServerError.errFmt(e))
  16.108 +
  16.109 +        return new_passwd
  16.110 +
  16.111 +    
  16.112 +    
  16.113 +    
  16.114 +
    17.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    17.2 +++ b/ldap_utils/ldap_util.py	Fri Jul 24 14:40:37 2020 +0300
    17.3 @@ -0,0 +1,86 @@
    17.4 +# coding: utf-8
    17.5 +import ldap
    17.6 +
    17.7 +# ===========================================================
    17.8 +# Статус завершения оперций
    17.9 +STATUS_OK = 0           # Всё ОК.
   17.10 +STATUS_BADAUTH = 1      # Не верные пользователь или пароль.
   17.11 +STATUS_SERVERDOWN = 2   # Сервер не доступен.
   17.12 +STATUS_SERVERERROR = 3  # Ошибка при взаимодействии с сервером.
   17.13 +
   17.14 +class LdapError(Exception): pass
   17.15 +
   17.16 +class LdapAuth(object):
   17.17 +	def __init__(self, server, userPrefix = 'CORP\\', baseDN = 'DC=example,DC=net', attr = []):
   17.18 +		self.server = server
   17.19 +		self.prefix = userPrefix
   17.20 +		self.status = ''
   17.21 +		self.baseDN = baseDN
   17.22 +		self.statusCode = 0
   17.23 +		self.groups = None
   17.24 +		self.attr = None
   17.25 +		self._needAttr = [ i for i in map(str, attr) ]
   17.26 +		
   17.27 +	def __call__(self, user, passwd):
   17.28 +		try:
   17.29 +			conn = ldap.initialize(self.server)
   17.30 +			conn.set_option(ldap.OPT_REFERRALS, 0)
   17.31 +			conn.simple_bind_s(self.prefix + user, passwd)
   17.32 +		except ldap.INVALID_CREDENTIALS:
   17.33 +			conn.unbind()
   17.34 +			self.status = 'Invalid credentials'
   17.35 +			self.statusCode = STATUS_BADAUTH
   17.36 +			return False
   17.37 +		except ldap.SERVER_DOWN:
   17.38 +			self.status = 'Server is down'
   17.39 +			self.statusCode = STATUS_SERVERDOWN
   17.40 +			return False
   17.41 +		
   17.42 +		self.groups = []
   17.43 +		try:
   17.44 +			ldapData = conn.search_s(self.baseDN, ldap.SCOPE_SUBTREE, '(cn=%s)' % user, ['memberOf'] + self._needAttr)[0][1]
   17.45 +			for i in ldapData['memberOf']:
   17.46 +				self.groups.append(i.split(',')[0].split('=')[1].decode('utf-8'))
   17.47 +			del ldapData['memberOf']
   17.48 +			self.attr = ldapData
   17.49 +		except KeyError:
   17.50 +			self.status = 'User object from LDAP is wrong, it can be anonymous logon'
   17.51 +			self.statusCode = STATUS_SERVERERROR
   17.52 +			return False
   17.53 +		finally:
   17.54 +			conn.unbind()			
   17.55 +
   17.56 +		return True
   17.57 +	
   17.58 +	def __getitem__(self, key):
   17.59 +		return self.attr[key]
   17.60 +	
   17.61 +	def memberOf(self, group):
   17.62 +		"""Проверка на присутствие у пользователя некоторой группы
   17.63 +		"""
   17.64 +		if self.groups == None:
   17.65 +			raise LdapError('Request membership before auth call.')
   17.66 +			
   17.67 +		if not isinstance(group, unicode):
   17.68 +			if isinstance(group, str):
   17.69 +				group = group.decode('utf-8')
   17.70 +			else:
   17.71 +				group = str(group).decode('utf-8')
   17.72 +		
   17.73 +		if group in self.groups:
   17.74 +			return True
   17.75 +		else:
   17.76 +			return False
   17.77 +	
   17.78 +	def __contains__(self, group):
   17.79 +		return self.memberOf(group)
   17.80 +		
   17.81 +	def memberOfGroups(self, groups):
   17.82 +		if not len(groups) > 0:
   17.83 +			return False
   17.84 +		
   17.85 +		for group in groups:
   17.86 +			if not group in self:
   17.87 +				return False
   17.88 +		
   17.89 +		return True
    18.1 --- a/log/slog_syslogger.py	Tue Nov 12 21:28:23 2019 +0300
    18.2 +++ b/log/slog_syslogger.py	Fri Jul 24 14:40:37 2020 +0300
    18.3 @@ -1,99 +1,145 @@
    18.4 -# coding: utf-8
    18.5 -"""
    18.6 -Тривиальная реализация сислоггера без блэк-джека и поэтесс
    18.7 -
    18.8 -Метки в журнале о уровне сообщения:
    18.9 -  "`": Debug
   18.10 -  ".": Info
   18.11 -  "*": Warning
   18.12 -  "!": Error
   18.13 -  "#": Alert
   18.14 -
   18.15 -"""
   18.16 -from time import time
   18.17 -from datetime import timedelta
   18.18 -
   18.19 -import syslog
   18.20 -from traceback import extract_tb, extract_stack
   18.21 -from sys import exc_info
   18.22 -
   18.23 -
   18.24 -class Timing(object):
   18.25 -    def __init__(self, name=None):
   18.26 -        if name is None:
   18.27 -            self.prefix = ''
   18.28 -        else:
   18.29 -            self.prefix = '%s :: ' % name
   18.30 -        self.tsAll = time()
   18.31 -        self.ts = self.tsAll
   18.32 -
   18.33 -    def getTime(self):
   18.34 -        return time() - self.ts
   18.35 -
   18.36 -    def reset(self):
   18.37 -        self.ts = time()
   18.38 -        self.tsAll = self.ts
   18.39 -
   18.40 -    def __str__(self):
   18.41 -        ts = time()
   18.42 -        return self.prefix + '%s(%.4f)' % (timedelta(seconds=(ts - self.tsAll)), ts - self.ts)
   18.43 -
   18.44 -    def __call__(self, msg):
   18.45 -        _buf = '%s | %s' % (self, msg)
   18.46 -        self.ts = time()
   18.47 -        return _buf
   18.48 -
   18.49 -
   18.50 -class SimpleSysLogger(object):
   18.51 -    @staticmethod
   18.52 -    def initSyslog(ident):
   18.53 -        syslog.openlog(ident, syslog.LOG_PID)
   18.54 -
   18.55 -    @staticmethod
   18.56 -    def getTiming(name=None):
   18.57 -        return Timing(name)
   18.58 -
   18.59 -    def __init__(self, prefix, facility=syslog.LOG_USER):
   18.60 -        self.prefix = str(prefix)
   18.61 -        self.facility = facility
   18.62 -
   18.63 -    def _write(self, flag, mark, msg):
   18.64 -        for l in str(msg).splitlines():
   18.65 -            syslog.syslog(self.facility | flag, '%s: %s %s' % (self.prefix, mark, l))
   18.66 -
   18.67 -    def __call__(self, msg):
   18.68 -        self._write(syslog.LOG_INFO, '.', msg)
   18.69 -    
   18.70 -    def err(self, msg):
   18.71 -        self._write(syslog.LOG_ERR, '!', msg)
   18.72 -    
   18.73 -    def warn(self, msg):
   18.74 -        self._write(syslog.LOG_WARNING, '*', msg)
   18.75 -    
   18.76 -    def debug(self, msg):
   18.77 -        self._write(syslog.LOG_DEBUG, '`', msg)
   18.78 -
   18.79 -    def alert(self, msg):
   18.80 -        self._write(syslog.LOG_ALERT, '#', msg)
   18.81 -
   18.82 -    def sublog(self, prefix):
   18.83 -        return self.__class__('%s/%s' % (self.prefix, prefix), self.facility)
   18.84 -
   18.85 -    def excpt(self, msg, eClass=None, eObj=None, eTb=None, stack_skip=0):
   18.86 -        if eClass is None:
   18.87 -            eClass, eObj, eTb = exc_info()
   18.88 -        
   18.89 -        tbData_tb = list(extract_tb(eTb))[::-1]
   18.90 -        tbData_stack = list(extract_stack())[::-1][(2 + stack_skip):]
   18.91 -        self.err(msg)
   18.92 -        self.err('--- EXCEPTION ---')
   18.93 -        self.err(' %s (%s)' % (eClass.__name__, eObj))
   18.94 -        self.err('--- TRACEBACK ---')
   18.95 -        for _tbFile, _tbLine, _tbFunc, _tbText in tbData_tb:
   18.96 -            self.err('File: %s, line %s in %s' % (_tbFile, _tbLine, _tbFunc))
   18.97 -            self.err('   %s' % _tbText)
   18.98 -        self.err('>>> Exception Handler <<<')
   18.99 -        for _tbFile, _tbLine, _tbFunc, _tbText in tbData_stack:
  18.100 -            self.err('File: %s, line %s in %s' % (_tbFile, _tbLine, _tbFunc))
  18.101 -            self.err('   %s' % _tbText)
  18.102 -        self.err('--- END EXCEPTION ---')
  18.103 +# coding: utf-8
  18.104 +
  18.105 +"""\
  18.106 +Логирование в системный журнал Unix
  18.107 +
  18.108 +Метки в журнале о уровне сообщения:
  18.109 +  "`": Debug
  18.110 +  ".": Info
  18.111 +  "*": Warning
  18.112 +  "!": Error
  18.113 +  "#": Alert
  18.114 +"""
  18.115 +
  18.116 +import syslog
  18.117 +from sys import exc_info
  18.118 +from time import time
  18.119 +from traceback import extract_tb, extract_stack
  18.120 +from datetime import timedelta
  18.121 +
  18.122 +LOG_FACILITY = {
  18.123 +    'auth': syslog.LOG_AUTH,
  18.124 +    'authpriv': syslog.LOG_AUTH,
  18.125 +    'cron': syslog.LOG_CRON,
  18.126 +    'daemon': syslog.LOG_DAEMON,
  18.127 +    'ftp': syslog.LOG_DAEMON,
  18.128 +    'kern': syslog.LOG_KERN,
  18.129 +    'lpr': syslog.LOG_LPR,
  18.130 +    'mail': syslog.LOG_MAIL,
  18.131 +    'news': syslog.LOG_NEWS,
  18.132 +    'syslog': syslog.LOG_SYSLOG,
  18.133 +    'user': syslog.LOG_USER,
  18.134 +    'uucp': syslog.LOG_UUCP,
  18.135 +    'local0': syslog.LOG_LOCAL0,
  18.136 +    'local1': syslog.LOG_LOCAL1,
  18.137 +    'local2': syslog.LOG_LOCAL2,
  18.138 +    'local3': syslog.LOG_LOCAL3,
  18.139 +    'local4': syslog.LOG_LOCAL4,
  18.140 +    'local5': syslog.LOG_LOCAL5,
  18.141 +    'local6': syslog.LOG_LOCAL6,
  18.142 +    'local7': syslog.LOG_LOCAL7
  18.143 +}
  18.144 +
  18.145 +class LoggerError(Exception): pass
  18.146 +
  18.147 +
  18.148 +# --- INTERFACE --- #
  18.149 +FACILITY = LOG_FACILITY['user']
  18.150 +
  18.151 +
  18.152 +def log_prep(ident, facility='user'):
  18.153 +    global FACILITY
  18.154 +    if not facility.lower() in LOG_FACILITY:
  18.155 +        raise LoggerError('Unknown facility')
  18.156 +
  18.157 +    syslog.openlog(ident, syslog.LOG_PID)
  18.158 +
  18.159 +    FACILITY = LOG_FACILITY[facility]
  18.160 +
  18.161 +
  18.162 +class Timing(object):
  18.163 +    def __init__(self, name=None):
  18.164 +        if name is None:
  18.165 +            self.prefix = ''
  18.166 +        else:
  18.167 +            self.prefix = '%s :: ' % name
  18.168 +        self.tsAll = time()
  18.169 +        self.ts = self.tsAll
  18.170 +
  18.171 +    def getTime(self):
  18.172 +        return time() - self.ts
  18.173 +
  18.174 +    def reset(self):
  18.175 +        self.ts = time()
  18.176 +        self.tsAll = self.ts
  18.177 +
  18.178 +    def __str__(self):
  18.179 +        ts = time()
  18.180 +        return self.prefix + '%s(%.4f)' % (timedelta(seconds=(ts - self.tsAll)), ts - self.ts)
  18.181 +
  18.182 +    def __call__(self, msg):
  18.183 +        _buf = '%s | %s' % (self, msg)
  18.184 +        self.ts = time()
  18.185 +        return _buf
  18.186 +
  18.187 +
  18.188 +class SysLogger(object):
  18.189 +    init_log = log_prep
  18.190 +
  18.191 +    @staticmethod
  18.192 +    def get_timing(name=None):
  18.193 +        return Timing(name)
  18.194 +
  18.195 +    def __init__(self, prefix, facility=FACILITY):
  18.196 +        self.prefix = str(prefix)
  18.197 +        self.facility = facility
  18.198 +
  18.199 +    def _write(self, flag, mark, msg):
  18.200 +        for l in str(msg).splitlines():
  18.201 +            syslog.syslog(self.facility | flag, '%s: %s %s' % (self.prefix, mark, l))
  18.202 +
  18.203 +    def __call__(self, msg):
  18.204 +        self._write(syslog.LOG_INFO, '.', msg)
  18.205 +
  18.206 +    def err(self, msg):
  18.207 +        self._write(syslog.LOG_ERR, '!', msg)
  18.208 +
  18.209 +    def warn(self, msg):
  18.210 +        self._write(syslog.LOG_WARNING, '*', msg)
  18.211 +
  18.212 +    def debug(self, msg):
  18.213 +        self._write(syslog.LOG_DEBUG, '`', msg)
  18.214 +
  18.215 +    def alert(self, msg):
  18.216 +        self._write(syslog.LOG_ALERT, '#', msg)
  18.217 +
  18.218 +    def sublog(self, prefix):
  18.219 +        return self.__class__('%s/%s' % (self.prefix, prefix), self.facility)
  18.220 +
  18.221 +    def excpt(self, msg, eClass=None, eObj=None, eTb=None, stack_skip=0):
  18.222 +        if eClass is None:
  18.223 +            eClass, eObj, eTb = exc_info()
  18.224 +
  18.225 +        if eClass is None:
  18.226 +            # Если вдруг вызываем без произошедшего исключения
  18.227 +            self.err(msg)
  18.228 +        else:
  18.229 +            tbData_tb = list(extract_tb(eTb))[::-1]
  18.230 +            tbData_stack = list(extract_stack())[::-1][(2 + stack_skip):]
  18.231 +
  18.232 +            self.err(msg)
  18.233 +
  18.234 +            self.err('--- EXCEPTION ---')
  18.235 +            self.err(' %s (%s)' % (eClass.__name__, eObj))
  18.236 +
  18.237 +            self.err('--- TRACEBACK ---')
  18.238 +            for _tbFile, _tbLine, _tbFunc, _tbText in tbData_tb:
  18.239 +                self.err('File: %s, line %s in %s' % (_tbFile, _tbLine, _tbFunc))
  18.240 +                self.err('   %s' % _tbText)
  18.241 +
  18.242 +            self.err('>>> Exception Handler <<<')
  18.243 +            for _tbFile, _tbLine, _tbFunc, _tbText in tbData_stack:
  18.244 +                self.err('File: %s, line %s in %s' % (_tbFile, _tbLine, _tbFunc))
  18.245 +                self.err('   %s' % _tbText)
  18.246 +
  18.247 +            self.err('--- END EXCEPTION ---')
    19.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    19.2 +++ b/log/slog_syslogger_tiny.py	Fri Jul 24 14:40:37 2020 +0300
    19.3 @@ -0,0 +1,99 @@
    19.4 +# coding: utf-8
    19.5 +"""
    19.6 +Тривиальная реализация сислоггера без блэк-джека и поэтесс
    19.7 +
    19.8 +Метки в журнале о уровне сообщения:
    19.9 +  "`": Debug
   19.10 +  ".": Info
   19.11 +  "*": Warning
   19.12 +  "!": Error
   19.13 +  "#": Alert
   19.14 +
   19.15 +"""
   19.16 +from time import time
   19.17 +from datetime import timedelta
   19.18 +
   19.19 +import syslog
   19.20 +from traceback import extract_tb, extract_stack
   19.21 +from sys import exc_info
   19.22 +
   19.23 +
   19.24 +class Timing(object):
   19.25 +    def __init__(self, name=None):
   19.26 +        if name is None:
   19.27 +            self.prefix = ''
   19.28 +        else:
   19.29 +            self.prefix = '%s :: ' % name
   19.30 +        self.tsAll = time()
   19.31 +        self.ts = self.tsAll
   19.32 +
   19.33 +    def getTime(self):
   19.34 +        return time() - self.ts
   19.35 +
   19.36 +    def reset(self):
   19.37 +        self.ts = time()
   19.38 +        self.tsAll = self.ts
   19.39 +
   19.40 +    def __str__(self):
   19.41 +        ts = time()
   19.42 +        return self.prefix + '%s(%.4f)' % (timedelta(seconds=(ts - self.tsAll)), ts - self.ts)
   19.43 +
   19.44 +    def __call__(self, msg):
   19.45 +        _buf = '%s | %s' % (self, msg)
   19.46 +        self.ts = time()
   19.47 +        return _buf
   19.48 +
   19.49 +
   19.50 +class SimpleSysLogger(object):
   19.51 +    @staticmethod
   19.52 +    def initSyslog(ident):
   19.53 +        syslog.openlog(ident, syslog.LOG_PID)
   19.54 +
   19.55 +    @staticmethod
   19.56 +    def getTiming(name=None):
   19.57 +        return Timing(name)
   19.58 +
   19.59 +    def __init__(self, prefix, facility=syslog.LOG_USER):
   19.60 +        self.prefix = str(prefix)
   19.61 +        self.facility = facility
   19.62 +
   19.63 +    def _write(self, flag, mark, msg):
   19.64 +        for l in str(msg).splitlines():
   19.65 +            syslog.syslog(self.facility | flag, '%s: %s %s' % (self.prefix, mark, l))
   19.66 +
   19.67 +    def __call__(self, msg):
   19.68 +        self._write(syslog.LOG_INFO, '.', msg)
   19.69 +    
   19.70 +    def err(self, msg):
   19.71 +        self._write(syslog.LOG_ERR, '!', msg)
   19.72 +    
   19.73 +    def warn(self, msg):
   19.74 +        self._write(syslog.LOG_WARNING, '*', msg)
   19.75 +    
   19.76 +    def debug(self, msg):
   19.77 +        self._write(syslog.LOG_DEBUG, '`', msg)
   19.78 +
   19.79 +    def alert(self, msg):
   19.80 +        self._write(syslog.LOG_ALERT, '#', msg)
   19.81 +
   19.82 +    def sublog(self, prefix):
   19.83 +        return self.__class__('%s/%s' % (self.prefix, prefix), self.facility)
   19.84 +
   19.85 +    def excpt(self, msg, eClass=None, eObj=None, eTb=None, stack_skip=0):
   19.86 +        if eClass is None:
   19.87 +            eClass, eObj, eTb = exc_info()
   19.88 +        
   19.89 +        tbData_tb = list(extract_tb(eTb))[::-1]
   19.90 +        tbData_stack = list(extract_stack())[::-1][(2 + stack_skip):]
   19.91 +        self.err(msg)
   19.92 +        self.err('--- EXCEPTION ---')
   19.93 +        self.err(' %s (%s)' % (eClass.__name__, eObj))
   19.94 +        self.err('--- TRACEBACK ---')
   19.95 +        for _tbFile, _tbLine, _tbFunc, _tbText in tbData_tb:
   19.96 +            self.err('File: %s, line %s in %s' % (_tbFile, _tbLine, _tbFunc))
   19.97 +            self.err('   %s' % _tbText)
   19.98 +        self.err('>>> Exception Handler <<<')
   19.99 +        for _tbFile, _tbLine, _tbFunc, _tbText in tbData_stack:
  19.100 +            self.err('File: %s, line %s in %s' % (_tbFile, _tbLine, _tbFunc))
  19.101 +            self.err('   %s' % _tbText)
  19.102 +        self.err('--- END EXCEPTION ---')
    20.1 --- a/log/syslogger.py	Tue Nov 12 21:28:23 2019 +0300
    20.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    20.3 @@ -1,278 +0,0 @@
    20.4 -#!/usr/bin/env python
    20.5 -# -*- coding: utf-8 -*-
    20.6 -""" Логирование в системный журнал Unix
    20.7 -
    20.8 -Метки в журнале о уровне сообщения:
    20.9 -  "`": Debug
   20.10 -  ".": Info
   20.11 -  "*": Warning
   20.12 -  "!": Error
   20.13 -  "#": Alert
   20.14 -"""
   20.15 -import syslog
   20.16 -from sys import argv, version_info, exc_info
   20.17 -from time import time
   20.18 -from traceback import extract_tb
   20.19 -
   20.20 -LOG_FACILITY = {
   20.21 -    'auth': syslog.LOG_AUTH,
   20.22 -    'authpriv': syslog.LOG_AUTH,
   20.23 -    'cron': syslog.LOG_CRON,
   20.24 -    'daemon': syslog.LOG_DAEMON,
   20.25 -    'ftp': syslog.LOG_DAEMON,
   20.26 -    'kern': syslog.LOG_KERN,
   20.27 -    'lpr': syslog.LOG_LPR,
   20.28 -    'mail': syslog.LOG_MAIL,
   20.29 -    'news': syslog.LOG_NEWS,
   20.30 -    'syslog': syslog.LOG_SYSLOG,
   20.31 -    'user': syslog.LOG_USER,
   20.32 -    'uucp': syslog.LOG_UUCP,
   20.33 -    'local0': syslog.LOG_LOCAL0,
   20.34 -    'local1': syslog.LOG_LOCAL1,
   20.35 -    'local2': syslog.LOG_LOCAL2,
   20.36 -    'local3': syslog.LOG_LOCAL3,
   20.37 -    'local4': syslog.LOG_LOCAL4,
   20.38 -    'local5': syslog.LOG_LOCAL5,
   20.39 -    'local6': syslog.LOG_LOCAL6,
   20.40 -    'local7': syslog.LOG_LOCAL7
   20.41 -}
   20.42 -
   20.43 -# --- INTERFACE --- #
   20.44 -FACILITY = LOG_FACILITY['user']
   20.45 -def logPrep(ident, facility='user'):
   20.46 -    global FACILITY
   20.47 -    if not facility.lower() in LOG_FACILITY:
   20.48 -        raise LoggerError('Unknown facility')
   20.49 -    syslog.openlog(ident, syslog.LOG_PID)
   20.50 -
   20.51 -    FACILITY = LOG_FACILITY[facility]
   20.52 -
   20.53 -
   20.54 -# --- ABSTRACT PART --- #
   20.55 -
   20.56 -LOG_DEBUG = "`"
   20.57 -LOG_INFO = "."
   20.58 -LOG_WARN = "*"
   20.59 -LOG_ERR = "!"
   20.60 -LOG_ALERT = "#"
   20.61 -
   20.62 -class LoggerError(Exception): pass
   20.63 -
   20.64 -if version_info.major == 3:
   20.65 -    def _strWrap(msg):
   20.66 -        return str(msg)
   20.67 -elif version_info.major == 2:
   20.68 -    def _strWrap(msg):
   20.69 -        if isinstance(msg, unicode):
   20.70 -            return msg
   20.71 -        else:
   20.72 -            return str(msg).decode('utf-8')
   20.73 -else:
   20.74 -    raise LoggerError('Unknown major version of Python')
   20.75 -
   20.76 -
   20.77 -def _splitLines(obj):
   20.78 -    if isinstance(obj, (list, tuple)):
   20.79 -        for i in _parseList(obj):
   20.80 -            yield i
   20.81 -    elif isinstance(obj, dict):
   20.82 -        for i in _parseDict(obj):
   20.83 -            yield i
   20.84 -    else:
   20.85 -        buf = _strWrap(obj).splitlines()
   20.86 -        for i in buf:
   20.87 -            if len(i) < 401:
   20.88 -                yield i
   20.89 -            else:
   20.90 -                i = '<| ' + i + ' |>'
   20.91 -                lenI = len(i)
   20.92 -                c = 0
   20.93 -                while (c + 401) < lenI:
   20.94 -                    yield i[c:c+401]
   20.95 -                    c += 401
   20.96 -                yield i[c:]
   20.97 -
   20.98 -def _parseList(lst):
   20.99 -    for i in lst:
  20.100 -        i = _splitLines(i)
  20.101 -        try:
  20.102 -            yield '- %s' % next(i)
  20.103 -            for l in i:
  20.104 -                yield '  %s' % l
  20.105 -        except StopIteration: pass
  20.106 -
  20.107 -def _parseDict(dct):
  20.108 -    for key in sorted(dct):
  20.109 -        yield '%s:' % key
  20.110 -        for l in _splitLines(dct[key]):
  20.111 -            yield '    %s' % l
  20.112 -
  20.113 -def _parseArgs(a, kwa):
  20.114 -    if a:
  20.115 -        yield '| --- ARGS:'
  20.116 -        for i in _parseList(a):
  20.117 -            yield '| %s' % i
  20.118 -
  20.119 -    if kwa:
  20.120 -        yield '| --- NAMED ARGS:'
  20.121 -        for i in _parseDict(kwa):
  20.122 -            yield '| %s' % i
  20.123 -
  20.124 -    if a or kwa:
  20.125 -        yield '| ---'
  20.126 -
  20.127 -class TimingLog():
  20.128 -    def __init__(self, logproc, opName):
  20.129 -        self.logproc = logproc
  20.130 -        self.opName = opName
  20.131 -        self.dt = time()
  20.132 -
  20.133 -    def __str__(self):
  20.134 -        return '%s :: %.4f' % (self.opName, (time() - self.dt))
  20.135 -
  20.136 -    def __call__(self, msg, *a, **kwa):
  20.137 -        self.logproc('%s | %s' % (self, msg))
  20.138 -        for i in _parseArgs(a, kwa):
  20.139 -            self.logproc('%s | %s' % (self, i))
  20.140 -
  20.141 -    def reset(self):
  20.142 -        self.dt = time()
  20.143 -
  20.144 -
  20.145 -class OperationContext(object):
  20.146 -    def __init__(self, log, opName):
  20.147 -        self.opName = opName
  20.148 -        self.log = log
  20.149 -        self.msgBuf = []
  20.150 -
  20.151 -    def __call__(self, msg, *a, **kwa):
  20.152 -        self.msgBuf.append((msg, [ i for i in map(_strWrap, a)]))
  20.153 -
  20.154 -    def __enter__(self):
  20.155 -        return self
  20.156 -
  20.157 -    def __exit__(self, exc_type, exc_val, exc_tb):
  20.158 -        if exc_type is not None:
  20.159 -            self.log.err("Operation '%s' fail" % self.opName)
  20.160 -            self.log.excpt_handler(exc_type, exc_val, exc_tb)
  20.161 -
  20.162 -            if self.msgBuf:
  20.163 -                self.log.err('--- MESSAGES ---')
  20.164 -                for msg, a, kwa in self.msgBuf:
  20.165 -                    self.log.err(msg, a)
  20.166 -                self.log.err('--- END MESSAGES ---')
  20.167 -
  20.168 -
  20.169 -class AbstractLogger(object):
  20.170 -    def err(self, msg, *a, **kwa):
  20.171 -        raise LoggerError('NotImplemented')
  20.172 -
  20.173 -    def __call__(self, msg, *a, **kwa):
  20.174 -        raise LoggerError('NotImplemented')
  20.175 -
  20.176 -    def excpt_handler(self, eType, eObj, eTb):
  20.177 -        eTb = extract_tb(eTb)
  20.178 -        eType = str(eType)
  20.179 -        try:
  20.180 -            eType = eType.split("'")[1]
  20.181 -        except IndexError:
  20.182 -            pass
  20.183 -
  20.184 -        try:
  20.185 -            eArgs = eObj.args[1:]
  20.186 -        except:
  20.187 -            eArgs = []
  20.188 -
  20.189 -        try:
  20.190 -            eKwa = eObj.kwa
  20.191 -        except:
  20.192 -            eKwa = {}
  20.193 -
  20.194 -        eObj = str(eObj)
  20.195 -
  20.196 -        self.err('--- EXCEPTION ---')
  20.197 -        self.err(eType)
  20.198 -
  20.199 -        for l in _splitLines(eObj):
  20.200 -            self.err('  ' + l)
  20.201 -
  20.202 -        for l in _parseArgs(eArgs, eKwa):
  20.203 -            self.err(l)
  20.204 -
  20.205 -        self.err('--- TRACEBACK ---')
  20.206 -        for _tbFile, _tbLine, _tbFunc, _tbText in eTb:
  20.207 -            self.err('File: %s, line %s in %s' % (_tbFile, _tbLine, _tbFunc))
  20.208 -            self.err('  %s' % _tbText)
  20.209 -        self.err('--- END EXCEPTION ---')
  20.210 -
  20.211 -    def excpt(self, msg, *a, **kwa):
  20.212 -        eType, eObj, eTb = exc_info()
  20.213 -        self.err(msg, *a, **kwa)
  20.214 -        self.excpt_handler(eType, eObj, eTb)
  20.215 -
  20.216 -    def cntxt(self, opName):
  20.217 -        """
  20.218 -        Если операцию нужно залогировать но при этом совершенно не хочется покрывать код
  20.219 -        толстым слоем try ... except можно завернуть операцию в контекст, и даже сохранить
  20.220 -        возвращаемом логером объекте отладочную информацию. При падении и только при нём
  20.221 -        это обязательно попадёт в лог.
  20.222 -
  20.223 -        ОСТОРОЖНО ПАМЯТЬ!!!!!
  20.224 -        Объект копирует себе все переданные данные и опреобразует их сразу в строки
  20.225 -        Если объекту передать одни и те же параметры они всё равно будут храниться
  20.226 -        по копии на каждый вызов в виде строк, что может сьесть много памяти и вызвать
  20.227 -        болезненное её особождение.
  20.228 -
  20.229 -        :param opName: членораздельное имя операции
  20.230 -        """
  20.231 -        return OperationContext(self, opName)
  20.232 -
  20.233 -    def getTiming(self, opName):
  20.234 -        return TimingLog(self, opName)
  20.235 -
  20.236 -    def dumpDict(self, msg, dct):
  20.237 -        self('--- ' + _strWrap(msg))
  20.238 -        for i in _parseDict(dct):
  20.239 -            self('| ' + i)
  20.240 -        self('---')
  20.241 -    
  20.242 -    @staticmethod
  20.243 -    def logPrep(*a, **kwa):
  20.244 -        logPrep(*a, **kwa)
  20.245 -
  20.246 -# --- END ABSTRACT PART --- #
  20.247 -
  20.248 -class Logger(AbstractLogger):
  20.249 -    facility = FACILITY
  20.250 -    def __init__(self, modName = 'main'):
  20.251 -        self.name = modName
  20.252 -
  20.253 -    def _write(self, flags, prefix, msg):
  20.254 -        syslog.syslog(flags, u"%s %s: %s" % (prefix, self.name, msg))
  20.255 -
  20.256 -    def __call__(self, msg, *a, **kwa):
  20.257 -        _flags = self.facility | syslog.LOG_INFO
  20.258 -        _prefix = LOG_INFO + ' ' + self.name + ': '
  20.259 -        syslog.syslog(_flags, _prefix + _strWrap(msg))
  20.260 -        for i in _parseArgs(a, kwa):
  20.261 -            syslog.syslog(_flags, _prefix + i)
  20.262 -
  20.263 -    def warn(self, msg, *a, **kwa):
  20.264 -        _flags = self.facility | syslog.LOG_WARNING
  20.265 -        _prefix = LOG_WARN + ' ' + self.name + ': '
  20.266 -        syslog.syslog(_flags, _prefix + _strWrap(msg))
  20.267 -        for i in _parseArgs(a, kwa):
  20.268 -            syslog.syslog(_flags, _prefix + i)
  20.269 -
  20.270 -    def err(self, msg, *a, **kwa):
  20.271 -        _flags = self.facility | syslog.LOG_ERR
  20.272 -        _prefix = LOG_ERR + ' ' + self.name + ': '
  20.273 -        syslog.syslog(_flags, _prefix + _strWrap(msg))
  20.274 -        for i in _parseArgs(a, kwa):
  20.275 -            syslog.syslog(_flags, _prefix + i)
  20.276 -
  20.277 -
  20.278 -
  20.279 -
  20.280 -
  20.281 -
    22.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    22.2 +++ b/webapp/tool/__init__.py	Fri Jul 24 14:40:37 2020 +0300
    22.3 @@ -0,0 +1,4 @@
    22.4 +# -*- coding: utf-8 -*-
    22.5 +# ---
    22.6 +#  
    22.7 +# ---
    22.8 \ No newline at end of file
    23.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    23.2 +++ b/webapp/tool/url.py	Fri Jul 24 14:40:37 2020 +0300
    23.3 @@ -0,0 +1,12 @@
    23.4 +# -*- coding: utf-8 -*-
    23.5 +# ---
    23.6 +#  Инструменты для работы с URL
    23.7 +# ---
    23.8 +from base64 import urlsafe_b64encode as _b64e, urlsafe_b64decode as _b64d
    23.9 +from urllib.parse import quote as urlquote
   23.10 +
   23.11 +def encode(buf):
   23.12 +  return _b64e(buf.encode('UTF-8')).decode('UTF-8')
   23.13 +  
   23.14 +def decode(buf):
   23.15 +  return _b64d(buf.encode('UTF-8')).decode('UTF-8')
    24.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    24.2 +++ b/webapp/win.py	Fri Jul 24 14:40:37 2020 +0300
    24.3 @@ -0,0 +1,72 @@
    24.4 +# -*- coding: utf-8 -*-
    24.5 +# ---
    24.6 +#  Модуль среды приложений для Windows
    24.7 +# ---
    24.8 +
    24.9 +from bottle import Bottle as App, request as Req, response as Ans, SimpleTemplate as BottleTemplate, abort as Abort
   24.10 +from os.path import dirname, abspath as pAbs, split as pSplit, join as pJoin
   24.11 +
   24.12 +# --- CLASSES ---
   24.13 +class ReqEnv:
   24.14 +	def __init__(self):
   24.15 +		self.urlBase = Req.environ.get('SCRIPT_NAME', '')
   24.16 +		
   24.17 +	def getUrl(self, url):
   24.18 +		url = str(url)
   24.19 +		buf = self.urlBase
   24.20 +		buf += url if url.startswith('/') else '/' + url
   24.21 +		return buf
   24.22 +	
   24.23 +	def __getitem__(self, key):
   24.24 +		return Req.environ[key]
   24.25 +		
   24.26 +	def __iter__(self):
   24.27 +		for key in Req.environ.keys():
   24.28 +			yield key
   24.29 +			
   24.30 +	@staticmethod
   24.31 +	def abort(*a, **ka):
   24.32 +		Abort(*a, **ka)
   24.33 +	
   24.34 +	@staticmethod	
   24.35 +	def get(self):
   24.36 +		return Req.GET
   24.37 +		
   24.38 +	@staticmethod	
   24.39 +	def post():
   24.40 +		return Req.POST
   24.41 +		
   24.42 +class Env:
   24.43 +	def __init__(self, scFile):
   24.44 +		self.wd = pSplit(dirname(pAbs(scFile)))[0]
   24.45 +		self.static = pJoin(self.wd, 'static')
   24.46 +		
   24.47 +	def http(self, tplName):
   24.48 +		return BottleTemplate(name=pJoin(self.wd, 'http', '%s.html' % tplName)).render
   24.49 +		
   24.50 +	def db(self):
   24.51 +		""" 
   24.52 +		Путь к базе SQLite, иные типы БД дожны иметь более
   24.53 +		серьёзные средства авторизации
   24.54 +		"""
   24.55 +		return pJoin(self.wd, 'db.sqlite')
   24.56 +	
   24.57 +	@staticmethod
   24.58 +	def httpTpl(buf):
   24.59 +		return BottleTemplate(buf).render
   24.60 +	
   24.61 +	@staticmethod
   24.62 +	def app():
   24.63 +		return App()
   24.64 +		
   24.65 +	@staticmethod
   24.66 +	def appRun(app):
   24.67 +		app.run(server='cgi')
   24.68 +	
   24.69 +# --- PROCS ---
   24.70 +def runApp(app):
   24.71 +	Env.appRun(app)
   24.72 +	
   24.73 +def Template(buf):
   24.74 +	return Env.httpTpl(buf)
   24.75 +	
   24.76 \ No newline at end of file