Commit 1f5b8481 authored by Gero Müller's avatar Gero Müller
Browse files

rename to ldap_export, add comamnd line tool to sync ldap, refactor server.py

parent b21185d9
#!/usr/bin/env python
import sys, os
base = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
if os.path.isdir(base):
sys.path.insert(0, base)
from argparse import ArgumentParser
import vispa
from vispa.extensions.ldap_export import LDAPExport
from vispa import MAJOR_VERSION, MINOR_VERSION
from sqlalchemy.orm import scoped_session, sessionmaker
import logging
logging.basicConfig(level=logging.INFO)
parser = ArgumentParser()
default_base = os.path.expanduser("~/.vispa-%s.%s" % (MAJOR_VERSION, MINOR_VERSION))
parser.add_argument('-c', '--config-dir', dest='configdir',
default=os.path.join(default_base, "conf"),
help='Directory containing configuration files '
'(default: %s)' % os.path.join(default_base, "conf"))
parser.add_argument('-d', '--data-dir', dest='vardir',
default=os.path.join(default_base, "var"),
help='Directory containing server '
'data and cache files (default: %s)'
% os.path.join(default_base, "var"))
parser.add_argument('--delete',
default=False,
help='Delete unknown users and groups')
args = parser.parse_args()
vispa.setup_config(os.path.abspath(args.configdir))
db = vispa.models.open_database(args.vardir)
session = scoped_session(sessionmaker(autoflush=True, autocommit=False))
session.configure(bind=db)
ldapexport = LDAPExport()
if ldapexport.connect():
session = scoped_session(sessionmaker(autoflush=True, autocommit=False))
session.configure(bind=self.server._engine)
if args.delete:
self.delete_unknown(session)
self.sync_all_users(session)
...@@ -82,6 +82,28 @@ def set_codepath(p): ...@@ -82,6 +82,28 @@ def set_codepath(p):
_codepath = p _codepath = p
def config_files(directory):
conf_d_dir = configpath(directory)
if os.path.isdir(conf_d_dir):
config_files = [f for f in os.listdir(conf_d_dir) if f.endswith(".ini") and os.path.isfile(os.path.join(conf_d_dir, f))]
config_files.sort()
return [os.path.join(conf_d_dir, f) for f in config_files]
else:
return []
def setup_config(conf_dir):
logger.info('Using %s as config dir.' % conf_dir)
set_configpath(conf_dir)
logger.info('Read config file: %s', configpath('vispa.ini'))
config.read(configpath('vispa.ini'))
# load all ini files
_config_files = config_files('vispa.d')
logger.info('Read config files: %s', _config_files)
config.read(_config_files)
class VispaConfigParser(cp.SafeConfigParser): class VispaConfigParser(cp.SafeConfigParser):
def __call__(self, section, option, default=None): def __call__(self, section, option, default=None):
if self.has_option(section, option): if self.has_option(section, option):
......
...@@ -15,51 +15,36 @@ from ldap3.core.exceptions import LDAPNoSuchObjectResult ...@@ -15,51 +15,36 @@ from ldap3.core.exceptions import LDAPNoSuchObjectResult
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def _r(val): def _r(val):
return [(ldap3.MODIFY_REPLACE, [val])] return [(ldap3.MODIFY_REPLACE, [val])]
class LDAPExportExtension(AbstractExtension):
def name(self):
return "ldap-export"
def dependencies(self):
return []
def setup(self):
url = vispa.config("ldap-export", "url")
user = vispa.config("ldap-export", "user")
password = vispa.config("ldap-export", "password")
set_library_log_detail_level(BASIC)
if url: class LDAPExport(object):
self.connection = ldap3.Connection(url, user, password, auto_bind=True, raise_exceptions=True)
else:
self.connection = None
logger.info(self.connection) def __init__(self):
if self.connection:
self.user_base = vispa.config("ldap-export", "user_base") self.user_base = vispa.config("ldap-export", "user_base")
self.group_base = vispa.config("ldap-export", "group_base") self.group_base = vispa.config("ldap-export", "group_base")
self.uid_offset = vispa.config("ldap-export", "uid_offset", 10000) self.uid_offset = vispa.config("ldap-export", "uid_offset", 10000)
self.private_group = vispa.config("ldap-export", "private_group", True) self.private_group = vispa.config("ldap-export", "private_group", True)
self.gid_offset = vispa.config("ldap-export", "gid_offset", 100000) self.gid_offset = vispa.config("ldap-export", "gid_offset", 100000)
set_library_log_detail_level(BASIC)
vispa.register_callback("user.activate", self.user_add) def connect(self):
vispa.register_callback("user.set_password", self.user_set_password) url = vispa.config("ldap-export", "url")
user = vispa.config("ldap-export", "user")
password = vispa.config("ldap-export", "password")
if vispa.config("ldap-export", "sync_on_startup", False): if not url:
if vispa.config("ldap-export", "delete_unknown", False): return
self.delete_unknown()
self.sync_all_users()
def delete_unknown(self): self.connection = ldap3.Connection(url, user, password, auto_bind=True, raise_exceptions=True)
session = scoped_session(sessionmaker(autoflush=True, autocommit=False))
session.configure(bind=self.server._engine) return self.connection != None
def delete_unknown(self, db):
try: try:
active_users = session.query(User, User.name).filter_by(status=User.ACTIVE).all() active_users = db.query(User, User.name).filter_by(status=User.ACTIVE).all()
self.connection.search(self.user_base, "(objectClass=posixAccount)", attributes=['uid']) self.connection.search(self.user_base, "(objectClass=posixAccount)", attributes=['uid'])
for ldap_user in self.connection.entries: for ldap_user in self.connection.entries:
...@@ -76,13 +61,11 @@ class LDAPExportExtension(AbstractExtension): ...@@ -76,13 +61,11 @@ class LDAPExportExtension(AbstractExtension):
except: except:
logger.exception("remove_invalid_ldap_entries") logger.exception("remove_invalid_ldap_entries")
finally: finally:
session.remove() db.remove()
def sync_all_users(self): def sync_all_users(self, db):
session = scoped_session(sessionmaker(autoflush=True, autocommit=False))
session.configure(bind=self.server._engine)
try: try:
users = session.query(User).filter_by(status=User.ACTIVE) users = db.query(User).filter_by(status=User.ACTIVE)
for user in users: for user in users:
try: try:
self.user_add(user) self.user_add(user)
...@@ -91,7 +74,7 @@ class LDAPExportExtension(AbstractExtension): ...@@ -91,7 +74,7 @@ class LDAPExportExtension(AbstractExtension):
except: except:
logger.exception("sync_all_users") logger.exception("sync_all_users")
finally: finally:
session.remove() db.remove()
def user_delete(self, user): def user_delete(self, user):
dn = 'cn=%s,%s' % (unicode(user.name), self.user_base) dn = 'cn=%s,%s' % (unicode(user.name), self.user_base)
...@@ -136,12 +119,12 @@ class LDAPExportExtension(AbstractExtension): ...@@ -136,12 +119,12 @@ class LDAPExportExtension(AbstractExtension):
self.group_add_member(username, username) self.group_add_member(username, username)
def group_add(self, name, id): def group_add(self, name, gid):
dn = 'cn=%s,%s' % (name, self.group_base) dn = 'cn=%s,%s' % (name, self.group_base)
classes = ['top', 'posixGroup'] classes = ['top', 'posixGroup']
attributes = { attributes = {
'cn': name, 'cn': name,
'gidNumber': id, 'gidNumber': gid,
} }
logger.info("Add group: %s, %s, %s", dn, classes, attributes) logger.info("Add group: %s, %s, %s", dn, classes, attributes)
try: try:
...@@ -150,7 +133,7 @@ class LDAPExportExtension(AbstractExtension): ...@@ -150,7 +133,7 @@ class LDAPExportExtension(AbstractExtension):
logger.info(" -> updated") logger.info(" -> updated")
changes = { changes = {
'cn': _r(name), 'cn': _r(name),
'gidNumber': _r(id), 'gidNumber': _r(gid),
} }
self.connection.modify(dn, changes) self.connection.modify(dn, changes)
...@@ -172,3 +155,28 @@ class LDAPExportExtension(AbstractExtension): ...@@ -172,3 +155,28 @@ class LDAPExportExtension(AbstractExtension):
logger.info("Change password: %s, %s", dn, changes) logger.info("Change password: %s, %s", dn, changes)
self.connection.modify(dn, changes) self.connection.modify(dn, changes)
class LDAPExportExtension(AbstractExtension):
def name(self):
return "ldap-export"
def dependencies(self):
return []
def setup(self):
self.ldapexport = LDAPExport()
if self.ldapexport.connect():
if vispa.config("ldap-export", "sync_on_startup", False):
session = scoped_session(sessionmaker(autoflush=True, autocommit=False))
session.configure(bind=self.server._engine)
if vispa.config("ldap-export", "delete_unknown", False):
self.delete_unknown(session)
self.sync_all_users(session)
vispa.register_callback("user.activate", self.ldapexport.user_add)
vispa.register_callback("user.set_password", self.ldapexport.user_set_password)
\ No newline at end of file
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import os
import logging
import vispa
import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
logger = logging.getLogger(__name__)
Base = declarative_base() Base = declarative_base()
__all__ = ['group', 'preference', 'project', 'role', 'shortcuts', 'user', __all__ = ['group', 'preference', 'project', 'role', 'shortcuts', 'user',
'workgroup', 'workspace'] 'workgroup', 'workspace']
FORBIDDEN_PHRASES = [u"drop ", u"select ", u"dump ", u"insert ", u"delete ", FORBIDDEN_PHRASES = [u"drop ", u"select ", u"dump ", u"insert ", u"delete ",
u"update ", u"drop\\ ", u"select\\ ", u"dump\\ ", u"update ", u"drop\\ ", u"select\\ ", u"dump\\ ",
u"insert\\ ", u"delete\\ ", u"update\\ "] u"insert\\ ", u"delete\\ ", u"update\\ "]
FORBIDDEN_CHARS = [u"´", u"`"] FORBIDDEN_CHARS = [u"´", u"`"]
def insertion_safe(*args, **kwargs): def insertion_safe(*args, **kwargs):
for arg in list(args) + kwargs.values(): for arg in list(args) + kwargs.values():
if isinstance(arg, dict): if isinstance(arg, dict):
...@@ -32,3 +38,32 @@ def insertion_safe(*args, **kwargs): ...@@ -32,3 +38,32 @@ def insertion_safe(*args, **kwargs):
if elem.lower().find(char) >= 0: if elem.lower().find(char) >= 0:
return False, elem return False, elem
return True, None return True, None
def open_database(var_dir=None):
var_dir = var_dir or ""
sa_identifier = vispa.config('database', 'sqlalchemy.url',
'sqlite:///%s' % os.path.join(var_dir, "vispa.db"))
pool_size = vispa.config('database', 'sqlalchemy.pool_size', 10)
max_overflow = vispa.config(
'database',
'sqlalchemy.max_overflow',
10)
# https://github.com/mitsuhiko/flask-sqlalchemy/issues/2
# http://docs.sqlalchemy.org/en/latest/core/pooling.html#dealing-with-disconnects
pool_recycle = vispa.config(
'database',
'sqlalchemy.pool_recycle',
7200)
logger.info('Use database %s.' % sa_identifier)
try:
engine = sqlalchemy.create_engine(
sa_identifier,
echo=False,
pool_size=pool_size,
pool_recycle=pool_recycle,
max_overflow=max_overflow)
except TypeError:
engine = sqlalchemy.create_engine(
sa_identifier,
echo=False)
return engine
\ No newline at end of file
...@@ -138,7 +138,6 @@ class AbstractExtension(object): ...@@ -138,7 +138,6 @@ class AbstractExtension(object):
view_id = cherrypy.request.private_params["_viewId"] view_id = cherrypy.request.private_params["_viewId"]
return "extension.%s.socket.%s" % (view_id, topic) return "extension.%s.socket.%s" % (view_id, topic)
class Server(object): class Server(object):
__default_server_config = { __default_server_config = {
...@@ -207,15 +206,6 @@ class Server(object): ...@@ -207,15 +206,6 @@ class Server(object):
self.__init_platform(**kwargs) self.__init_platform(**kwargs)
self.__init_plugins(**kwargs) self.__init_plugins(**kwargs)
def __config_files(self, dir):
conf_d_dir = vispa.configpath(dir)
if os.path.isdir(conf_d_dir):
config_files = [f for f in os.listdir(conf_d_dir) if f.endswith(".ini") and os.path.isfile(os.path.join(conf_d_dir, f))]
config_files.sort()
return [os.path.join(conf_d_dir, f) for f in config_files]
else:
return []
def __init_paths(self, **kwargs): def __init_paths(self, **kwargs):
# dir for variable files and folders # dir for variable files and folders
self.var_dir = os.path.abspath(kwargs.get('vardir', '')) self.var_dir = os.path.abspath(kwargs.get('vardir', ''))
...@@ -245,47 +235,14 @@ class Server(object): ...@@ -245,47 +235,14 @@ class Server(object):
if not os.path.exists(self.cache_dir): if not os.path.exists(self.cache_dir):
os.mkdir(self.cache_dir) os.mkdir(self.cache_dir)
# conf dir
self.conf_dir = os.path.abspath(kwargs.get('configdir', '')) self.conf_dir = os.path.abspath(kwargs.get('configdir', ''))
logger.info('Using %s as config dir.' % self.conf_dir) vispa.setup_config(self.conf_dir)
vispa.set_configpath(self.conf_dir)
logger.info('Read config file: %s', vispa.configpath('vispa.ini')) def __init_database(self, **kwargs):
vispa.config.read(vispa.configpath('vispa.ini'))
# load all ini files self._engine = vispa.models.open_database()
config_files = self.__config_files('vispa.d')
logger.info('Read config files: %s', config_files)
vispa.config.read(config_files)
def __init_database(self, **kwargs): if vispa.config('alembic', 'use_alembic', False):
sa_identifier = vispa.config('database', 'sqlalchemy.url',
'sqlite:///%s/vispa.db' % self.var_dir)
pool_size = vispa.config('database', 'sqlalchemy.pool_size', 10)
max_overflow = vispa.config(
'database',
'sqlalchemy.max_overflow',
10)
# https://github.com/mitsuhiko/flask-sqlalchemy/issues/2
# http://docs.sqlalchemy.org/en/latest/core/pooling.html#dealing-with-disconnects
pool_recycle = vispa.config(
'database',
'sqlalchemy.pool_recycle',
7200)
logger.info('Use database %s.' % sa_identifier)
try:
self._engine = sqlalchemy.create_engine(
sa_identifier,
echo=False,
pool_size=pool_size,
pool_recycle=pool_recycle,
max_overflow=max_overflow)
except TypeError:
self._engine = sqlalchemy.create_engine(
sa_identifier,
echo=False)
if vispa.config('alembic', 'use_alembic', True) and not sa_identifier.startswith('sqlite'):
logger.info("Use alembic") logger.info("Use alembic")
vispa.models.alembic.migrate(self._engine) vispa.models.alembic.migrate(self._engine)
else: else:
...@@ -420,7 +377,7 @@ class Server(object): ...@@ -420,7 +377,7 @@ class Server(object):
logger.info('Merge cherrpy config file: %s' % cherrypy_conf) logger.info('Merge cherrpy config file: %s' % cherrypy_conf)
cherrypy.config.update(cherrypy_conf) cherrypy.config.update(cherrypy_conf)
config_files = self.__config_files("cherrypy.d") config_files = vispa.config_files("cherrypy.d")
logger.info('Merge cherrypy config files: %s' % config_files) logger.info('Merge cherrypy config files: %s' % config_files)
for f in config_files: for f in config_files:
cherrypy.config.update(f) cherrypy.config.update(f)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment