Commit 7884aebb authored by Benjamin Fischer's avatar Benjamin Fischer
Browse files

Merge branch 'default' into 'windowManager'

parents 87df2da0 90455209
#!/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, action='store_const',
const=True,
help='Delete unknown users and groups')
args = parser.parse_args()
vispa.setup_config(os.path.abspath(args.configdir))
ldapexport = LDAPExport()
if ldapexport.connect():
db = vispa.models.open_database(args.vardir)
session = scoped_session(sessionmaker(autoflush=True, autocommit=False))
session.configure(bind=db)
if args.delete:
ldapexport.delete_unknown(session)
ldapexport.sync_all_users(session)
ldapexport.sync_all_groups(session)
......@@ -61,12 +61,6 @@ forgot.subject = Your VISPA password
# absolute url
password_url = http://localhost:4282/vispa/password
# enable guets logins without providing any information
enable_guest_login = False
# add guest automatically to the following groups
guest_groups = []
# default workspace action, i.e. a vispa.callbacks channel
workspace_action = openFileBrowser
......@@ -127,16 +121,23 @@ ignore = [dummy]
# Alternatively append the following domain as default, e.g. rwth-aachen.de
#remote.email_domain = None
# Number of rounds for sha265_crypt. The more the better, but may use too much CPU.
#hash.rounds = 100000
[usermanagement]
# do auto setup?
autosetup = True
autosetup = False
# the role/permission setup of the extensions can be accepted or not
accept_extensions = True
# name of the global project
global_project = VISPA
# default user and guest group
user_group = default_user
guest_group = default_guest
user_group = user
guest_group = guest
# three roles, e.g. with ascending rank
role_1 = Student
role_2 = Tutor
......@@ -145,6 +146,9 @@ role_3 = Manager
user_group_roles = [1]
guest_group_roles = []
# enable guets logins without providing any information
enable_guest_login = False
[ldap-export]
enable = False
......@@ -157,9 +161,11 @@ user_base = ou=people,dc=vispa,dc=local
group_base = ou=group,dc=vispa,dc=local
# add the following offset to all user ids
uid_offset = 1000
gid_offset = 1000
uid_offset = 10000
gid_offset = 100000
# create a group with the same name and id as the user
private_group = True
# create all missing users on startup
sync_on_startup = False
FROM debian:jessie
# image config
LABEL name="vispa-base"
LABEL version="0.1"
# basic environment variables
ENV PATH=/usr/local/bin:/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin
ENV LD_LIBRARY_PATH=/usr/local/lib
ENV LIBPATH=/usr/local/lib
ENV PYTHONPATH=/usr/local/lib:/usr/local/lib64/python2.7/site-packages
ENV PKG_CONFIG_PATH=/usr/local/lib/pkgconfig
ENV CMAKE_MODULE_PATH=/usr/local/etc/cmake
ENV MANPATH=/usr/local/man
# setup software
RUN apt-get -y update; apt-get clean
RUN apt-get -y install build-essential zip gzip zlib1g-dev libffi-dev libssl-dev libsqlite3-dev openssh-server nano wget htop git mercurial; apt-get clean
# RUN DEBIAN_FRONTEND=noninteractive apt-get -y install mysql-server; apt-get clean
RUN wget https://www.python.org/ftp/python/2.7.13/Python-2.7.13.tgz && \
tar -xzf Python-2.7.13.tgz && \
rm Python-2.7.13.tgz && \
cd Python-2.7.13 && \
./configure --enable-shared && \
make && \
make install && \
cd .. && \
rm -rf Python-2.7.13
RUN wget https://bootstrap.pypa.io/get-pip.py; python get-pip.py; rm get-pip.py
RUN pip install cherrypy
RUN pip install mako
RUN pip install sqlalchemy
# RUN pip install rpyc
RUN pip install -e git+https://github.com/geromueller/rpyc.git@12a53169100bce7175470f959e956f29902fa153#egg=rpyc-dev
RUN pip install alembic
RUN pip install paramiko
RUN pip install pycrypto
RUN pip install ws4py
RUN pip install passlib
# prepare files and directories
RUN mkdir -p /var/run/sshd
# default command
CMD bash
FROM 3pia/vispa-base
# image config
EXPOSE 4282
EXPOSE 22
LABEL name="vispa"
LABEL version="0.1"
# create a user
RUN echo 'vispa\nvispa' | adduser vispa --gecos ""
# download vispa and tweak the config
USER vispa
WORKDIR /home/vispa
RUN hg clone https://forge.physik.rwth-aachen.de/hg/vispa-web/vispa
RUN cd vispa/conf && cp cherrypy.ini.sample cherrypy.ini
# default command
USER root
CMD /usr/sbin/sshd && \
cd vispa && \
su -c "./bin/vispad -c conf --no-daemon -l debug" vispa
......@@ -41,7 +41,7 @@ setup(
license="GNU GPL v2",
packages=packages,
package_data={"vispa": files},
scripts=[os.path.join(srcdir, 'bin', 'vispa'), os.path.join(srcdir, 'bin', 'vispad')],
scripts=[os.path.join(srcdir, 'bin', 'vispa'), os.path.join(srcdir, 'bin', 'vispad'), os.path.join(srcdir, 'bin', 'vispa-ldap-export')],
install_requires=["sqlalchemy >= 0.9.0", "mako", "cherrypy",
"paramiko", "rpyc",
"alembic >= 0.7.3", # for Operations.batch_alter_table
......
......@@ -201,6 +201,7 @@ fi
#/srv/venv/bin/pip install --upgrade pip
#/srv/venv/bin/pip install --upgrade -r /srv/vispa/requirements.txt
/srv/venv/bin/pip install --upgrade pymysql
/srv/venv/bin/pip install --upgrade https://github.com/tomerfiliba/rpyc/archive/master.zip
/srv/venv/bin/python /srv/vispa/setup.py develop
mkdir -p /etc/vispa
......@@ -225,7 +226,7 @@ sqlalchemy.max_overflow = 50
[alembic]
use_alembic = True
# inplace installation
#script_location = vispa/models/alembic
script_location = vispa/models/alembic
# global installation
#script_location = vispa:models/alembic
auto_migrate = True
......@@ -241,6 +242,7 @@ password = zehdjkamam
user_base = ou=people,dc=vispa,dc=local
group_base = ou=group,dc=vispa,dc=local
sync_on_startup = False
private_group = True
[websockets]
enabled = True
......@@ -273,6 +275,17 @@ WantedBy=multi-user.target
EOF
# ----------------------------------------------------------------------
# setup database
# ----------------------------------------------------------------------
cd /srv/vispa
/srv/venv/bin/alembic -c /etc/vispa/vispa.ini upgrade head
/srv/venv/bin/alembic -c /etc/vispa/vispa.ini current
echo "delete from workspace where user_id is null;" | mysql vispa
echo "insert into workspace (user_id, name, host, auto_connect, login_credentials) values (null, 'vispa.local', 'localhost', 1, 1);" | mysql vispa
# ----------------------------------------------------------------------
# enable service
# ----------------------------------------------------------------------
......@@ -285,8 +298,6 @@ systemctl start vispa
#echo "delete from workspace where user_id is null;" | sqlite3 /var/lib/vispa/vispa.db
#echo "insert into workspace (user_id, name, host, auto_connect, login_credentials) values (null, 'vispa.local', 'localhost', 1, 1);" | sqlite3 /var/lib/vispa/vispa.db
#apt -y install sqlite3
echo "delete from workspace where user_id is null;" | mysql vispa
echo "insert into workspace (user_id, name, host, auto_connect, login_credentials) values (null, 'vispa.local', 'localhost', 1, 1);" | mysql vispa
# ----------------------------------------------------------------------
......
......@@ -82,6 +82,28 @@ def set_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):
def __call__(self, section, option, default=None):
if self.has_option(section, option):
......@@ -89,11 +111,23 @@ class VispaConfigParser(cp.SafeConfigParser):
# try to cast according to 'default'
if default is None:
return value
# boolean
if isinstance(default, bool):
if value == 'True':
return True
elif value == 'False':
return False
# int
if isinstance(default, int):
return int(value)
# long
if isinstance(default, long):
return long(value)
# string
if isinstance(default, str):
return str(value)
# list
elif isinstance(default, list):
if isinstance(default, list):
l = []
g = tokenize.generate_tokens(StringIO(value).readline)
for toknum, tokval, _, _, _ in g:
......@@ -104,20 +138,13 @@ class VispaConfigParser(cp.SafeConfigParser):
return l
# tuple
elif isinstance(default, tuple):
if isinstance(default, tuple):
if len(value):
return tuple(value.split(','))
else:
return ()
# boolean
elif isinstance(default, bool):
if value == 'True':
return True
elif value == 'False':
return False
# TODO: dict
else:
return value
return value
else:
return default
......
......@@ -47,9 +47,9 @@ class AjaxController(AbstractController):
user = User.register(cherrypy.request.db, username, email)
session = cherrypy.request.db
if vispa.config("usermanagement", "autosetup", False):
user_group = vispa.config("usermanagement", "user_group", None)
user_group = Group.get_by_name(session, user_group)
user_group = vispa.config("usermanagement", "user_group", "user")
if user_group:
user_group = Group.get_or_create_by_name(session, user_group)
user_group.add_user(session, user, Group_User_Assoc.CONFIRMED)
if vispa.config("web", "registration.autoactive", True):
......
......@@ -197,20 +197,16 @@ class RootController(AbstractController):
raise cherrypy.HTTPRedirect(path)
# return an error when guest logins are disabled
if vispa.config("web", "enable_guest_login", False) is False:
if vispa.config("usermanagement", "enable_guest_login", False) is False:
raise cherrypy.HTTPError(403, "Guest login not allowed!")
db= cherrypy.request.db
# actual guest login
user, password = User.guest_login(req.db)
for groupname in vispa.config("web", "guest_groups", []):
user, password = User.guest_login(db)
for groupname in vispa.config("usermanagement", "guest_group", "guest"):
group = Group.get_or_create_by_name(db, groupname)
group.users.append(user)
# autosetup new guest
if vispa.config("usermanagement", "autosetup", False):
guest_group = vispa.config("usermanagement", "guest_group", None)
guest_group = Group.get_by_name(req.db, guest_group)
guest_group.add_user(req.db, user, Group_User_Assoc.CONFIRMED)
group.add_user(db, user, Group_User_Assoc.UNCONFIRMED)
# update session
cherrypy.session["user_id"] = unicode(user.id)
......
......@@ -4,6 +4,7 @@ import logging
import vispa
from vispa.models.user import User
from vispa.models.group import Group
from vispa.server import AbstractExtension
from sqlalchemy.orm import scoped_session, sessionmaker
......@@ -14,47 +15,37 @@ from ldap3.core.exceptions import LDAPNoSuchObjectResult
logger = logging.getLogger(__name__)
class LDAPExportExtension(AbstractExtension):
def name(self):
return "ldap-export"
def _r(val):
return [(ldap3.MODIFY_REPLACE, [val])]
def dependencies(self):
return []
def setup(self):
class LDAPExport(object):
def __init__(self):
self.user_base = vispa.config("ldap-export", "user_base")
self.group_base = vispa.config("ldap-export", "group_base")
self.uid_offset = vispa.config("ldap-export", "uid_offset", 10000)
self.private_group = vispa.config("ldap-export", "private_group", True)
self.gid_offset = vispa.config("ldap-export", "gid_offset", 100000)
set_library_log_detail_level(BASIC)
def connect(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:
self.connection = ldap3.Connection(url, user, password, auto_bind=True, raise_exceptions=True)
else:
self.connection = None
logger.info(self.connection)
if self.connection:
self.user_base = vispa.config("ldap-export", "user_base")
self.group_base = vispa.config("ldap-export", "group_base")
self.uid_offset = vispa.config("ldap-export", "uid_offset", 1000)
self.gid_offset = vispa.config("ldap-export", "gid_offset", 1000)
vispa.register_callback("user.activate", self.user_add)
vispa.register_callback("user.set_password", self.user_set_password)
if vispa.config("ldap-export", "sync_on_startup", False):
if vispa.config("ldap-export", "delete_unknown", False):
self.delete_unknown()
self.sync_all_users()
def delete_unknown(self):
session = scoped_session(sessionmaker(autoflush=True, autocommit=False))
session.configure(bind=self.server._engine)
if not url:
return
self.connection = ldap3.Connection(url, user, password, auto_bind=True, raise_exceptions=True)
return self.connection != None
def delete_unknown(self, db):
try:
active_users = session.query(User, User.name).filter_by(status=User.ACTIVE).all()
active_users = [unicode(u.name) for u in db.query(User, User.name).filter_by(status=User.ACTIVE)]
groups = [unicode(g.name) for g in db.query(Group, Group.name)]
self.connection.search(self.user_base, "(objectClass=posixAccount)", attributes=['uid'])
for ldap_user in self.connection.entries:
......@@ -64,70 +55,151 @@ class LDAPExportExtension(AbstractExtension):
self.connection.search(self.group_base, "(objectClass=posixGroup)", attributes=['cn'])
for ldap_group in self.connection.entries:
if ldap_group.cn not in active_users:
private_group = self.private_group and ldap_group.cn in active_users
public_group = ldap_group.cn in groups
if not private_group and not public_group:
logger.info("Deleting unknown ldap group: %s", ldap_group.entry_get_dn())
self.connection.delete(ldap_group.entry_get_dn())
except:
logger.exception("remove_invalid_ldap_entries")
finally:
session.remove()
db.remove()
def sync_all_users(self):
session = scoped_session(sessionmaker(autoflush=True, autocommit=False))
session.configure(bind=self.server._engine)
def sync_all_users(self, db):
try:
users = session.query(User).filter_by(status=User.ACTIVE)
users = db.query(User).filter_by(status=User.ACTIVE)
for user in users:
try:
self.user_add(user)
self.user_add(unicode(user.name), user.id, user.password)
except:
pass
logger.exception("sync_all_users")
except:
logger.exception("sync_all_users")
finally:
session.remove()
db.remove()
def user_delete(self, user):
dn = 'cn=%s,%s' % (unicode(user.name), self.user_base)
def sync_all_groups(self, db):
try:
groups = db.query(Group)
for group in groups:
try:
self.group_add(unicode(group.name), group.id + self.gid_offset)
for member in group.users:
self.group_add_member(unicode(group.name), unicode(member.name))
except:
logger.exception("sync_all_groups")
except:
logger.exception("sync_all_groups")
finally:
db.remove()
def user_delete(self, name):
dn = 'cn=%s,%s' % (name, self.user_base)
logger.info("Delete user: %s", dn)
self.connection.delete(dn)
dn = 'cn=%s,%s' % (unicode(user.name), self.group_base)
dn = 'cn=%s,%s' % (name, self.group_base)
logger.info("Delete group: %s", dn)
self.connection.delete(dn)
def user_add(self, user):
dn = 'cn=%s,%s' % (unicode(user.name), self.user_base)
def user_add(self, name, uid, password):
username = name
dn = 'cn=%s,%s' % (name, self.user_base)
classes = ['top', 'person', 'organizationalPerson', 'inetOrgPerson', 'posixAccount', 'shadowAccount']
attributes = {
'uid': unicode(user.name),
'cn': unicode(user.name),
'sn': unicode(user.name),
'userPassword': '{CRYPT}' + unicode(user.password),
'uid': username,
'cn': username,
'sn': username,
'userPassword': '{CRYPT}' + password,
'loginShell': '/bin/bash',
'uidNumber': user.id + self.uid_offset,
'gidNumber': user.id + self.gid_offset,
'homeDirectory': '/home/%s/' % unicode(user.name)
'uidNumber': uid + self.uid_offset,
'gidNumber': uid + self.uid_offset,
'homeDirectory': '/home/%s/' % username
}
logger.info("Add user: %s, %s, %s", dn, classes, attributes)
self.connection.add(dn, classes, attributes)
try:
self.connection.add(dn, classes, attributes)
except ldap3.LDAPEntryAlreadyExistsResult:
logger.info(" -> updated")
changes = {
'uid': _r(username),
'cn': _r(username),
'userPassword': _r('{CRYPT}' + password),
'uidNumber': _r(uid + self.uid_offset),
'gidNumber': _r(uid + self.uid_offset),
'homeDirectory': _r('/home/%s/' % username)
}
self.connection.modify(dn, changes)
if self.private_group:
self.group_add(username, uid + self.uid_offset)
self.group_add_member(username, username)
dn = 'cn=%s,%s' % (unicode(user.name), self.group_base)
def group_add(self, name, gid):
dn = 'cn=%s,%s' % (name, self.group_base)
classes = ['top', 'posixGroup']
attributes = {
'cn': unicode(user.name),
'gidNumber': user.id + self.gid_offset,
'cn': name,
'gidNumber': gid,
}
logger.info("Add group: %s, %s, %s", dn, classes, attributes)
self.connection.add(dn, classes, attributes)
try:
self.connection.add(dn, classes, attributes)
except ldap3.LDAPEntryAlreadyExistsResult:
logger.info(" -> updated")
changes = {
'cn': _r(name),
'gidNumber': _r(gid),
}
self.connection.modify(dn, changes)
def group_add_member(self, groupname, username):
dn = 'cn=%s,%s' % (groupname, self.group_base)
change = {
'memberUid': [(ldap3.MODIFY_ADD, [username])],
}
try:
self.connection.modify(dn, change)
except ldap3.LDAPAttributeOrValueExistsResult:
pass
def user_set_password(self, user):
dn = 'cn=%s,%s' % (unicode(user.name), self.user_base)
def user_set_password(self, name, password):
dn = 'cn=%s,%s' % (name, self.user_base)
changes = {
'userPassword': (2, '{CRYPT}' + unicode(user.password))
'userPassword': (2, '{CRYPT}' + password)
}
logger.info("Change password: %s, %s", dn, changes)
self.connection.modify(dn, changes)
class LDAPExportExtension(AbstractExtension):
def name(self):
return "ldap-export"
def dependencies(self):
return []
def setup(self):