The branch, statusflag has been updated via 01ed711dba1fea373f1b212b40c1a50f61436ed4 (commit) from 92a791df1fcefa36560ed18b326ab0cc1ea5a5f2 (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- ----------------------------------------------------------------------- Summary of changes: etc/x2gobroker.conf | 4 +- sbin/x2gobroker | 153 ++++---------- x2gobroker/{ => backends}/__init__.py | 3 - x2gobroker/{ => backends}/base.py | 88 ++++++--- x2gobroker/{ => backends}/ldap.py | 0 x2gobroker/{ => backends}/simple.py | 20 +- x2gobroker/{ => backends}/zeroconf.py | 18 +- x2gobroker/config.py | 308 +++++++++++++++++++++++++++++ x2gobroker/defaults.py | 13 +- x2gobroker/utils.py | 82 ++++++++ x2gobroker/{ => web}/__init__.py | 3 - sbin/x2gobroker => x2gobroker/web/html.py | 121 ++++++------ x2gobroker/{__init__.py => web/json.py} | 12 +- 13 files changed, 587 insertions(+), 238 deletions(-) copy x2gobroker/{ => backends}/__init__.py (97%) rename x2gobroker/{ => backends}/base.py (61%) rename x2gobroker/{ => backends}/ldap.py (100%) rename x2gobroker/{ => backends}/simple.py (71%) rename x2gobroker/{ => backends}/zeroconf.py (77%) create mode 100644 x2gobroker/config.py create mode 100644 x2gobroker/utils.py copy x2gobroker/{ => web}/__init__.py (97%) copy sbin/x2gobroker => x2gobroker/web/html.py (53%) mode change 100755 => 100644 copy x2gobroker/{__init__.py => web/json.py} (70%) The diff of changes is: diff --git a/etc/x2gobroker.conf b/etc/x2gobroker.conf index 0123e60..ec6f4a9 100644 --- a/etc/x2gobroker.conf +++ b/etc/x2gobroker.conf @@ -63,8 +63,8 @@ use-authid = false # below value to true use-static-authid = true -# Make up your own static_authid below... -static-authid = <aaaavveeeerrrrryyyyylooonnnnggggssttrrriiinnnggg> +# Make up your own authid below... +authid = <aaaavveeeerrrrryyyyylooonnnnggggssttrrriiinnnggg> # X2Go Session Broker knows about two output formats: a text/html based output # and a text/json based output. The different outputs run under different URLs diff --git a/sbin/x2gobroker b/sbin/x2gobroker index 0b42c84..b5f3b48 100755 --- a/sbin/x2gobroker +++ b/sbin/x2gobroker @@ -23,132 +23,57 @@ import sys import os import web +import argparse try: import x2gobroker except ImportError: sys.path.insert(0, os.path.join(os.getcwd(), '..')) -# FIXME: here we have to add some code that genuinely detects the session broker backend... -broker_backend = "zeroconf" - -# load the requested broker -if broker_backend == "zeroconf": - import x2gobroker.zeroconf as broker -elif broker_backend == "simple": - import x2gobroker.simple as broker -elif broker_backend == "ldap": - import x2gobroker.ldap as broker - - -urls = ( '/', 'x2gobroker' ) - - -class x2gobroker: - - broker_backend = broker.X2GoBroker() - - http_header_items = { - 'Content-Type': 'text/html; charset=utf-8', - 'Expires': '+1h', - } - - page = web.template.Template("""$def with (html_header_items, output) -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html> -<head> -$for meta_tag in html_header_items["meta"]: - <meta $meta_tag="$html_header_items["meta"][meta_tag]"> -<title>$html_header_items['title']</title> -</head> +# parse-in potential command line options +cmdline_args = None +if __name__ == "__main__": + import setproctitle + setproctitle.setproctitle(os.path.basename(sys.argv[0])) -<body> -$output -</body> -</html> -""" - ) - html_header_items = { - 'title': 'X2Go Session Broker', - 'meta': { - 'author': 'X2Go Developers <x2go-dev@lists.berlios.de>', - 'charset': 'utf-8', - 'keywords': 'X2Go', - 'description': 'X2Go Session Broker', - }, + daemon_options = [ + {'args':['-C','--config-file'], 'default': None, 'metavar': 'CONFIG_FILE', 'help': 'Specify a special configuration file name, default is: /etc/x2go/x2gobroker.conf', }, + {'args':['-b', '--bind'], 'default': '127.0.0.1:8080', 'metavar': 'BIND_ADDRESS', 'help': 'The [address:]port that the web.py http-engine shall bind to (default: 127.0.0.1:8080)', }, + ] + p = argparse.ArgumentParser(description='X2Go Session Broker (Standalone Daemon)',\ + formatter_class=argparse.RawDescriptionHelpFormatter, \ + add_help=True, argument_default=None) + p_daemon = p.add_argument_group('standalone-daemon arguments') + + for (p_group, opts) in ( (p_daemon, daemon_options), ): + for opt in opts: + args = opt['args'] + del opt['args'] + p_group.add_argument(*args, **opt) + + cmdline_args = p.parse_args() + + ### FIXME: not working yet for the config_file diversion via -C option + broker_kwargs = { + 'config_file': cmdline_args.config_file, } - def _gen_http_header(self): - - for http_header_item in self.http_header_items.keys(): - web.header(http_header_item, self.http_header_items[http_header_item]) - - def GET(self): - - data = web.input() - output = '' - - self._gen_http_header() - - - # FIXME: the ,,testcon'' task can be object to DoS attacks... - if hasattr(data, 'task') and data.task == 'testcon': - - ### - ### TEST THE CONNECTION - ### - - return self.broker_backend.test_connection() - - if hasattr(data, 'user') and hasattr(data, 'password') and self.broker_backend.check_access(username=data.user, password=data.password): - - ### - ### PERFORM INITIAL AUTHENTICATION - ### - - output += "<strong>Access granted</strong><br />" - output += "AUTHID: {authid}<br />".format(authid=self.broker_backend.get_next_authid(username=data.user)) - return self.page(self.html_header_items, output) - - else: - return self.page(self.html_header_items, "<hr>Access denied") - - if hasattr(data, 'user') and hasattr(data, 'authid'): - - ### - ### X2GO BROKER TASKS - ### - - if self.broker_backend.check_access(username=data.user, authid=data.authid): - - if hasattr(data, 'task'): - task = data.task - - if task == 'listsessions': - - output += self.broker_backend.list_sessions() - - if task == 'selectsession': - - if hasattr(data, 'sid'): - - output += self.broker_backend.select_session(session_name=data.sid) - - if task == 'setpass': - - if hasattr(data, 'oldpass') and hasattr(data, 'newpass'): - - output += self.broker_backend.change_password(new=data.newpass, old=data.oldpass) - - return self.page(self.html_header_items, output) - - else: - return self.page(self.html_header_items, "<hr>Access denied") +# import classes serving the different web.py URLs +from x2gobroker.web.html import * +#from x2gobroker.web.json import * +# define the web.py URLs +urls = ( '/html/(.*)', 'X2GoBrokerWebHtml', +# '/json/(.*)', 'X2GoBrokerWebJson', + ) +# run the web.py standalone daemon... if __name__ == "__main__": - import setproctitle - setproctitle.setproctitle(os.path.basename(sys.argv[0])) + if len(sys.argv) <= 1: + sys.argv.append('') + sys.argv.append('') + sys.argv[1] = cmdline_args.bind + sys.argv[2:] = [] app = web.application(urls, globals()) app.internalerror = web.debugerror app.run() diff --git a/x2gobroker/__init__.py b/x2gobroker/backends/__init__.py similarity index 97% copy from x2gobroker/__init__.py copy to x2gobroker/backends/__init__.py index ad8c1e4..11b7f8f 100644 --- a/x2gobroker/__init__.py +++ b/x2gobroker/backends/__init__.py @@ -18,6 +18,3 @@ # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. -__VERSION__ = '0.0.0.1' - - diff --git a/x2gobroker/base.py b/x2gobroker/backends/base.py similarity index 61% rename from x2gobroker/base.py rename to x2gobroker/backends/base.py index a1fe0ca..f5228f7 100644 --- a/x2gobroker/base.py +++ b/x2gobroker/backends/base.py @@ -28,6 +28,9 @@ __NAME__ = 'x2gobroker-pylib' import types import uuid +# X2Go Broker modules +import x2gobroker.config + class X2GoBroker(object): """\ X2GoBrokerBASE is an abstract class for X2Go broker implementations. @@ -40,12 +43,16 @@ class X2GoBroker(object): L{ldap.X2GoBroker} """ - def __init__(self): + + def __init__(self, config_file="/etc/x2go/x2gobroker.conf"): """\ Initialize a new X2GoBroker instance to control X2Go session through an X2Go Client with an intermediate session broker. + """ - self._authid_dict = {} + self.config = x2gobroker.config.X2GoBrokerConfigFile() + + self._dynamic_authid_map = {} def __del__(self): """\ @@ -54,6 +61,29 @@ class X2GoBroker(object): """ pass + def get_global_config(self): + """\ + Get the global section of the configuration file. + + @return: all global configuration parameters + @rtype: C{dict} + + """ + return self.config.get_global_config() + + def get_backend_config(self, backend='zeroconf'): + """\ + Get the configuration section of a specific backend. + + @param backend: the name of the backend + @type backend: C{str} + + @return: all backend configuration parameters + @rtype: C{dict} + + """ + return self.config.get_backend_config(backend=backend) + def test_connection(self): #if($cgi->param('task') eq 'testcon') #{ @@ -66,7 +96,7 @@ class X2GoBroker(object): #} return 'OK' - def check_access(self, username='', password='', authid=None): + def check_access(self, username='', password='', authid=None, ): """\ Check if a given user with a given password may gain access to the X2Go session broker. @@ -80,32 +110,44 @@ class X2GoBroker(object): @rtype: C{bool} """ + ### FOR INTRANET LOAD BALANCER WE MAY JUST ALLOW ACCESS TO EVERYONE + ### This is handled through the config file, normally /etc/x2go/x2gobroker.conf - ### IMPLEMENT YOUR AUTHENTICATION LOGIC FIRST, then call base.X2GoBroker.check_access - ### to finalize the authentication process. + if not self.config.get_value('global', 'check-credentials'): + return True - # Before calling this code, you have to evaluate username and password. - # - # If the credentials (username, password) are considered as valid then - # set authid to True. - # - # If credentials are invalid, set authid to None. + ### IMPLEMENT YOUR AUTHENTICATION LOGIC IN THE self._check_access(**kwargs) METHOD + ### when inheriting from the base.X2GoBroker class. - if type(authid) is types.StringType: - authid = unicode(authid) + access = False + access = self._check_access(username=username, password=password, authid=authid) - if type(authid) is types.UnicodeType: + # using authid as extra security? + if self.config.get_value('global', 'use-authid'): - if authid == self._authid_dict[username]: - self._authid_dict[username] = uuid.uuid5(namespace=authid, name=username) - return True + if type(authid) is types.StringType: + authid = unicode(authid) - elif type(authid) is types.BooleanType and authid is True: - # generate a first uuid, initialize the connection - self._authid_dict[username] = uuid.uuid4() - return True + if self.config.get_value('global', 'use-static-authid'): - return False + # evaluate access based on static authentication ID feature + access = access and ( authid == self.config.get_value('global', 'authid') ) + + else: + + # evaluate access based on dynamic authentication ID feature + if self._dynamic_authid_map.has_key(username): + access = access and ( authid == self._dynamic_authid_map[username] ) + if access: + self._dynamic_authid_map[username] = uuid.uuid5(namespace=authid, name=username) + + else: + access = access and ( authid == self.config.get_value('global', 'authid') ) + if access: + # generate a first uuid, initialize the dynamic authencation ID security feature + self._dynamic_authid_map[username] = uuid.uuid4() + + return access def get_next_authid(self, username): """\ @@ -119,7 +161,7 @@ class X2GoBroker(object): """ try: - return self._authid_dict[username] + return self._dynamic_authid_map[username] except KeyError: return None diff --git a/x2gobroker/ldap.py b/x2gobroker/backends/ldap.py similarity index 100% rename from x2gobroker/ldap.py rename to x2gobroker/backends/ldap.py diff --git a/x2gobroker/simple.py b/x2gobroker/backends/simple.py similarity index 71% rename from x2gobroker/simple.py rename to x2gobroker/backends/simple.py index ea3fc87..923676e 100644 --- a/x2gobroker/simple.py +++ b/x2gobroker/backends/simple.py @@ -25,21 +25,21 @@ X2goBrokerSIMPLE class - a simple X2GoBroker implementations that uses text-base __NAME__ = 'x2gobroker-pylib' # modules +import pam + +# Python X2GoBroker modules import x2gobroker.base class X2GoBroker(x2gobroker.base.X2GoBroker): """\ """ - def __init__(self): - """\ - - """ - x2gobroker.base.X2GoBroker.__init__(self) - - def __del__(self): - """\ + def check_access(self, username='', password='', authid=None): - """ - x2gobroker.base.X2GoBroker.__del__(self) + # do a simple PAM authentication against the PAM service ,,x2gobroker'' + if username and password: + if pam.authenticate(username, password, service="x2gobroker"): + return True + return False + return x2gobroker.base.X2GoBroker.check_access(self, username=username, password=password, authid=authid) diff --git a/x2gobroker/zeroconf.py b/x2gobroker/backends/zeroconf.py similarity index 77% rename from x2gobroker/zeroconf.py rename to x2gobroker/backends/zeroconf.py index af0da2d..3598e68 100644 --- a/x2gobroker/zeroconf.py +++ b/x2gobroker/backends/zeroconf.py @@ -28,23 +28,19 @@ __NAME__ = 'x2gobroker-pylib' import pam import subprocess -import x2gobroker.base +import base from x2gobroker.defaults import X2GOBROKER_AGENT_CMD as _X2GOBROKER_AGENT_CMD -class X2GoBroker(x2gobroker.base.X2GoBroker): +class X2GoBroker(base.X2GoBroker): - def check_access(self, username='', password='', authid=None): + def _check_access(self, username='', password='', authid=None): # do a simple PAM authentication against the PAM service ,,x2gobroker'' - if authid is None: - if username and password: - if pam.authenticate(username, password, service="x2gobroker"): - authid = True - else: - authid = None - - return x2gobroker.base.X2GoBroker.check_access(self, username=username, password=password, authid=authid) + if username and password: + if pam.authenticate(username, password, service="x2gobroker"): + return True + return False def list_sessions(self, username): diff --git a/x2gobroker/config.py b/x2gobroker/config.py new file mode 100644 index 0000000..99531ae --- /dev/null +++ b/x2gobroker/config.py @@ -0,0 +1,308 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2012 by Mike Gabriel <mike.gabriel@das-netzwerkteam.de> +# +# X2Go Session Broker is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# X2Go Session Broker is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. +# +# This code was initially written by for the Python X2Go project: +# 2010 Dick Kniep <dick.kniep@lindix.nl> +# + +"""\ +X2goProcessIniFile - helper class for parsing .ini files + +""" +__NAME__ = 'x2goinifiles-pylib' + +# modules +import os +import ConfigParser +import types +import cStringIO + +# Python X2GoBroker modules +import x2gobroker.utils + +from x2gobroker.defaults import X2GOBROKER_HOME as _X2GOBROKER_HOME +from x2gobroker.defaults import X2GOBROKER_CONFIG_DEFAULTS as _X2GOBROKER_CONFIG_DEFAULTS + +class X2GoBrokerConfigFile(object): + """ + Class for processing a ini-file like configuration file. + + If entries are omitted in such a config file, they are filled with + default values (as hard-coded in Python X2goBroker), so the resulting objects + always contain the same fields. + + The default values are also used to define a data type for each configuration + option. An on-the-fly type conversion takes place when loading the configuration + file. + + """ + defaultValues = { + 'none': { + 'none': 'empty', + }, + } + write_user_config = False + user_config_file = None + + def __init__(self, config_files='/etc/x2go/x2gobroker.conf', defaults=None): + """\ + @param config_files: a list of configuration file names (e.g. a global filename and a user's home + directory filename) + @type config_files: C{list} + @param defaults: a cascaded Python dicitionary structure with ini file defaults (to override + Python X2goBroker's hard-coded defaults in L{defaults} + @type defaults: C{dict} + + """ + # make sure a None type gets turned into list type + if not config_files: + config_files = [] + + # allow string/unicode objects as config_files, as well + if type(config_files) in (types.StringType, types.UnicodeType): + config_files = [config_files] + self.config_files = config_files + + if x2gobroker.utils._checkConfigFileDefaults(defaults): + self.defaultValues = defaults + else: + self.defaultValues = _X2GOBROKER_CONFIG_DEFAULTS + + # we purposefully do not inherit the ConfigParser class + # here as we do not want to run into name conflicts between + # X2GoBroker config file options and method / property names in + # SafeConfigParser... This is a pre-cautious approach... + self.iniConfig = ConfigParser.ConfigParser(self.defaultValues) + self.iniConfig.optionxform = str + + _create_file = False + for file_name in self.config_files: + if file_name.startswith(_X2GOBROKER_HOME): + if not os.path.exists(file_name): + x2gobroker.utils.touch_file(file_name) + _create_file = True + break + + self.load() + + if _create_file: + self.write_user_config = True + self.write() + + def __repr__(self): + result = 'X2goConfigFile(' + for p in dir(self): + if '__' in p or not p in self.__dict__ or type(p) is types.InstanceType: continue + result += p + '=' + str(self.__dict__[p]) + ',' + result = result.strip(',') + return result + ')' + + def load(self): + """\ + R(e-r)ead configuration file(s). + + """ + _found_config_files = self.iniConfig.read(self.config_files) + + for file_name in _found_config_files: + if file_name.startswith(os.path.normpath(_X2GOBROKER_HOME)): + # we will use the first file found in the user's home dir for writing modifications + self.user_config_file = file_name + break + + self.config_files = _found_config_files + self._fill_defaults() + + def _storeValue(self, section, key, value): + """\ + Stores a value for a given section and key. + + This methods affects a SafeConfigParser object held in + RAM. No configuration file is affected by this + method. To write the configuration to disk use + the L{write()} method. + + @param section: the ini file section + @type section: C{str} + @param key: the ini file key in the given section + @type key: C{str} + @param value: the value for the given section and key + @type value: C{str}, C{list}, C{booAl}, ... + + """ + if type(value) == type(u''): + value = value.encode(x2gobroker.utils.get_encoding()) + if type(value) is types.BooleanType: + self.iniConfig.set(section, key, str(int(value))) + elif type(value) in (types.ListType, types.TupleType): + self.iniConfig.set(section, key, ", ".join(value)) + else: + self.iniConfig.set(section, key, str(value)) + + def _fill_defaults(self): + """\ + Fills a C{SafeConfigParser} object with the default config file + values as pre-defined in Python X2GoBroker or. This SafeConfigParser + object is held in RAM. No configuration file is affected by this + method. + + """ + for section, sectionvalue in self.defaultValues.items(): + for key, value in sectionvalue.items(): + if self.iniConfig.has_option(section, key): continue + if not self.iniConfig.has_section(section): + self.iniConfig.add_section(section) + self._storeValue(section, key, value) + + def update_value(self, section, key, value): + """\ + Change a value for a given section and key. This method + does not have any effect on configuration files. + + @param section: the ini file section + @type section: C{str} + @param key: the ini file key in the given section + @type key: C{str} + @param value: the value for the given section and key + @type value: C{str}, C{list}, C{bool}, ... + + """ + if not self.iniConfig.has_section(section): + self.iniConfig.add_section(section) + self._storeValue(section, key, value) + self.write_user_config = True + + def write(self): + """\ + Write the ini file modifications (SafeConfigParser object) from RAM to disk. + + For writing the first of the C{config_files} specified on instance construction + that is writable will be used. + + """ + if self.user_config_file and self.write_user_config: + fd = open(self.user_config_file, 'wb') + self.iniConfig.write(fd) + fd.close() + self.write_user_config = False + + def get_type(self, section, key): + """\ + Retrieve a value type for a given section and key. The returned + value type is based on the default values dictionary. + + @param section: the ini file section + @type section: C{str} + @param key: the ini file key in the given section + @type key: C{str} + + @return: a Python variable type + @rtype: class + + """ + return type(self.defaultValues[section][key]) + + def get_value(self, section, key, key_type=None): + """\ + Retrieve a value for a given section and key. + + @param section: the ini file section + @type section: C{str} + @param key: the ini file key in the given section + @type key: C{str} + + @return: the value for the given section and key + @rtype: class + + """ + if key_type is None: + key_type = self.get_type(section, key) + if self.iniConfig.has_option(section, key): + if key_type is types.BooleanType: + return self.iniConfig.getboolean(section, key) + elif key_type is types.IntType: + return self.iniConfig.getint(section, key) + elif key_type is types.ListType: + _val = self.iniConfig.get(section, key) + _val = _val.strip() + if _val.startswith('[') and _val.endswith(']'): + return eval(_val) + elif ',' in _val: + _val = [ v.strip() for v in _val.split(',') ] + else: + _val = [ _val ] + return _val + else: + _val = self.iniConfig.get(section, key) + return _val.decode(x2gobroker.utils.get_encoding()) + get = get_value + __call__ = get_value + + def get_section(self, section): + """\ + Get all keys and values for a certain section of the config file. + + @param section: the name of the section to get + @type section: C{str} + + @return: the section with all keys and values + @rtype: C{dict} + + """ + _section_config = {} + for option in self.iniConfig.options(section): + if option not in self.iniConfig.sections(): + _section_config[option] = self.get(section, option, key_type=self.get_type(section, option)) + + return _section_config + + def get_global_config(self): + """\ + Get the global section of the configuration file. + + @return: all global configuration parameters + @rtype: C{dict} + + """ + return self.get_section('global') + + def get_backend_config(self, backend='zeroconf'): + """\ + Get the configuration section of a specific backend. + + @param backend: the name of the backend + @type backend: C{str} + + @return: all backend configuration parameters + @rtype: C{dict} + + """ + return self.get_section(backend) + + @property + def printable_config_file(self): + """\ + Returns a printable configuration file as a multi-line string. + + """ + stdout = cStringIO.StringIO() + self.iniConfig.write(stdout) + _ret_val = stdout.getvalue() + stdout.close() + return _ret_val diff --git a/x2gobroker/defaults.py b/x2gobroker/defaults.py index d3de516..b9dc3c6 100644 --- a/x2gobroker/defaults.py +++ b/x2gobroker/defaults.py @@ -19,8 +19,13 @@ # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. +# modules +import os import uuid +# the home directory of the user that the daemon/cgi runs as +X2GOBROKER_HOME = os.path.normpath(os.path.expanduser('~')) + # FIXME: this path must not be hard-coded X2GOBROKER_AGENT_CMD="/usr/lib/x2gobroker-agent" @@ -30,14 +35,14 @@ X2GOBROKER_CONFIG_DEFAULTS = { 'backend': 'zeroconf', 'check-credentials': True, 'use-authid': False, - 'use-static-authid': true, - 'static-authid': uuid.uuid4(), + 'use-static-authid': True, + 'authid': uuid.uuid4(), 'enable-html-output': True, 'enable-json-output': False, }, 'zeroconf': { 'enable': True, - } + }, 'simple': { 'enable': False, 'session-profiles': '/etc/x2go/x2gobroker-simple-sessionprofiles.conf', @@ -48,7 +53,7 @@ X2GOBROKER_CONFIG_DEFAULTS = { 'session-profiles': '/etc/x2go/x2gobroker-loadbalancer-sessionprofiles.conf', 'server-list-PROFILENAME1': ['server1.profile1.mydomain.tld','server2.profile1.mydomain.tld','server3.profile1.mydomain.tld',], 'server-list-PROFILENAME2': ['server1.profile2.mydomain.tld','server2.profile2.mydomain.tld',], - } + }, 'ldap': { 'enable': False, 'uri': 'ldap://localhost:389', diff --git a/x2gobroker/utils.py b/x2gobroker/utils.py new file mode 100644 index 0000000..1a74ed8 --- /dev/null +++ b/x2gobroker/utils.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2012 by Mike Gabriel <mike.gabriel@das-netzwerkteam.de> +# Copyright (C) 2012 by Oleksandr Shneyder <oleksandr.shneyder@obviously-nice.de> +# Copyright (C) 2012 by Heinz-Markus Graesing <heinz-m.graesing@obviously-nice.de> +# +# X2Go Session Broker is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# X2Go Session Broker is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +import os +import sys +import types +import locale + +def _checkConfigFileDefaults(data_structure): + """\ + Check an ini-file-like data structure. + + @param data_structure: an ini-file-like data structure + @type data_structure: C{dict} of C{dict}s + + @return: C{True} if C{data_structure} matches that of an ini file data structure + @rtype: C{bool} + + """ + if data_structure is None: + return False + if type(data_structure) is not types.DictType: + return False + for sub_dict in data_structure.values(): + if type(sub_dict) is not types.DictType: + return False + return True + + +def touch_file(filename, mode='a'): + """\ + Imitates the behaviour of the GNU/touch command. + + @param filename: name of the file to touch + @type filename: C{str} + @param mode: the file mode (as used for Python file objects) + @type mode: C{str} + + """ + if not os.path.isdir(os.path.dirname(filename)): + os.makedirs(os.path.dirname(filename), mode=00700) + f = open(filename, mode=mode) + f.close() + + +def get_encoding(): + """\ + Detect systems default character encoding. + + @return: The system's local character encoding. + @rtype: C{str} + + """ + try: + encoding = locale.getdefaultlocale()[1] + if encoding is None: + raise BaseException + except: + try: + encoding = sys.getdefaultencoding() + except: + encoding = 'ascii' + return encoding + diff --git a/x2gobroker/__init__.py b/x2gobroker/web/__init__.py similarity index 97% copy from x2gobroker/__init__.py copy to x2gobroker/web/__init__.py index ad8c1e4..11b7f8f 100644 --- a/x2gobroker/__init__.py +++ b/x2gobroker/web/__init__.py @@ -18,6 +18,3 @@ # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. -__VERSION__ = '0.0.0.1' - - diff --git a/sbin/x2gobroker b/x2gobroker/web/html.py old mode 100755 new mode 100644 similarity index 53% copy from sbin/x2gobroker copy to x2gobroker/web/html.py index 0b42c84..f83bc1b --- a/sbin/x2gobroker +++ b/x2gobroker/web/html.py @@ -20,33 +20,10 @@ # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. -import sys -import os +# modules import web -try: - import x2gobroker -except ImportError: - sys.path.insert(0, os.path.join(os.getcwd(), '..')) - -# FIXME: here we have to add some code that genuinely detects the session broker backend... -broker_backend = "zeroconf" - -# load the requested broker -if broker_backend == "zeroconf": - import x2gobroker.zeroconf as broker -elif broker_backend == "simple": - import x2gobroker.simple as broker -elif broker_backend == "ldap": - import x2gobroker.ldap as broker - - -urls = ( '/', 'x2gobroker' ) - - -class x2gobroker: - - broker_backend = broker.X2GoBroker() +class X2GoBrokerWebHtml: http_header_items = { 'Content-Type': 'text/html; charset=utf-8', @@ -83,72 +60,86 @@ $output for http_header_item in self.http_header_items.keys(): web.header(http_header_item, self.http_header_items[http_header_item]) - def GET(self): + def GET(self, backend): + + if not backend: + backend = 'zeroconf' + else: + backend = backend.rstrip('/') + + # silence pyflakes... + broker_backend = None + exec("import x2gobroker.backends.{backend}".format(backend=backend)) + exec("broker_backend = x2gobroker.backends.{backend}.X2GoBroker()".format(backend=backend)) + global_config = broker_backend.get_global_config() + backend_config = broker_backend.get_backend_config(backend) data = web.input() + try: username = data.user + except AttributeError: username = '' + try: password = data.password + except AttributeError: password = '' + try: authid = data.authid + except AttributeError: authid = '' + try: task = data.task + except AttributeError: task = '' + try: session_name = data.sid + except AttributeError: session_name = '' + try: new_password = data.newpass + except AttributeError: new_password = '' + output = '' self._gen_http_header() - # FIXME: the ,,testcon'' task can be object to DoS attacks... - if hasattr(data, 'task') and data.task == 'testcon': + if task == 'testcon': ### ### TEST THE CONNECTION ### - return self.broker_backend.test_connection() + return broker_backend.test_connection() - if hasattr(data, 'user') and hasattr(data, 'password') and self.broker_backend.check_access(username=data.user, password=data.password): + if broker_backend.check_access(username=username, password=password, authid=authid): - ### - ### PERFORM INITIAL AUTHENTICATION - ### + if not task: + ### + ### PERFORM A TEST AUTHENTICATION + ### - output += "<strong>Access granted</strong><br />" - output += "AUTHID: {authid}<br />".format(authid=self.broker_backend.get_next_authid(username=data.user)) - return self.page(self.html_header_items, output) + if global_config['use-authid']: - else: - return self.page(self.html_header_items, "<hr>Access denied") - - if hasattr(data, 'user') and hasattr(data, 'authid'): - - ### - ### X2GO BROKER TASKS - ### - - if self.broker_backend.check_access(username=data.user, authid=data.authid): + ### FIXME: make up a nice protocol for this, disabled for now + #output += "AUTHID: {authid}<br />".format(authid=broker_backend.get_next_authid(username=data.user)) + pass - if hasattr(data, 'task'): - task = data.task + output += "<strong>Access granted</strong><br />" + return self.page(self.html_header_items, output) - if task == 'listsessions': + else: - output += self.broker_backend.list_sessions() + ### + ### X2GO BROKER TASKS + ### - if task == 'selectsession': + if task == 'listsessions': - if hasattr(data, 'sid'): + output += broker_backend.list_sessions() - output += self.broker_backend.select_session(session_name=data.sid) + if task == 'selectsession': - if task == 'setpass': + if session_name: - if hasattr(data, 'oldpass') and hasattr(data, 'newpass'): + output += broker_backend.select_session(session_name=session_name) - output += self.broker_backend.change_password(new=data.newpass, old=data.oldpass) + if task == 'setpass': - return self.page(self.html_header_items, output) + if new_password: - else: - return self.page(self.html_header_items, "<hr>Access denied") + output += broker_backend.change_password(new_password=new_password) + return self.page(self.html_header_items, output) -if __name__ == "__main__": - import setproctitle - setproctitle.setproctitle(os.path.basename(sys.argv[0])) - app = web.application(urls, globals()) - app.internalerror = web.debugerror - app.run() + else: + return self.page(self.html_header_items, "<hr>Access denied") diff --git a/x2gobroker/__init__.py b/x2gobroker/web/json.py similarity index 70% copy from x2gobroker/__init__.py copy to x2gobroker/web/json.py index ad8c1e4..b879b77 100644 --- a/x2gobroker/__init__.py +++ b/x2gobroker/web/json.py @@ -1,7 +1,9 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python +# This file is part of the X2Go Project - http://www.x2go.org +# Copyright (C) 2011-2012 by Oleksandr Shneyder <oleksandr.shneyder@obviously-nice.de> +# Copyright (C) 2011-2012 by Heinz-Markus Graesing <heinz-m.graesing@obviously-nice.de> # Copyright (C) 2012 by Mike Gabriel <mike.gabriel@das-netzwerkteam.de> -# Copyright (C) 2012 by Oleksandr Shneyder <oleksandr.shneyder@obviously-nice.de> # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -18,6 +20,10 @@ # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. -__VERSION__ = '0.0.0.1' +# modules +import web +class X2GoBrokerWebJson: + # MUSIC OF THE FUTURE + pass \ No newline at end of file hooks/post-receive -- x2gobroker.git (HTTP(S) Session broker for X2Go) This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "x2gobroker.git" (HTTP(S) Session broker for X2Go).