This is an automated email from the git hooks/post-receive script. x2go pushed a commit to branch master in repository x2gobroker. commit 3ec8cdae957979fe8dd994b37da7aa26b2a20a40 Author: Mike Gabriel <mike.gabriel@das-netzwerkteam.de> Date: Thu Jan 31 20:50:08 2019 +0100 Finalize API documentation. --- debian/changelog | 1 + x2gobroker/agent.py | 37 +++++++--- x2gobroker/authmechs/base_authmech.py | 1 - x2gobroker/authservice.py | 8 ++ x2gobroker/basicauth.py | 3 + x2gobroker/brokers/base_broker.py | 69 +++++++++++++++--- x2gobroker/brokers/inifile_broker.py | 70 ++++++++++++++++++ x2gobroker/brokers/zeroconf_broker.py | 33 +++++++++ x2gobroker/client/plain.py | 28 +++++++ x2gobroker/nameservices/base_nameservice.py | 93 ++++++++++++++++++++++++ x2gobroker/nameservices/libnss_nameservice.py | 40 ++++++++++ x2gobroker/nameservices/testsuite_nameservice.py | 45 +++++++++++- x2gobroker/optional_scripts/base_script.py | 37 ++++++++++ x2gobroker/web/extras.py | 7 ++ x2gobroker/web/json.py | 38 ++++++++-- x2gobroker/web/plain.py | 36 +++++++-- x2gobroker/web/uccs.py | 48 ++++++++++++ x2gobroker/x2gobroker_exceptions.py | 21 +++++- 18 files changed, 577 insertions(+), 38 deletions(-) diff --git a/debian/changelog b/debian/changelog index 04df2b4..694f32f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -99,6 +99,7 @@ x2gobroker (0.0.4.0-0x2go1) UNRELEASED; urgency=medium Popen.terminate() (which is unneeded anyway) the process after its execution. - Ignore SSH broker events for now. Not sure if we will ever support that. + - Finalize API documentation. * debian/*: + Trigger Makefile's install target and install those files. Drop debhelper from-source-installation magic. diff --git a/x2gobroker/agent.py b/x2gobroker/agent.py index 22a5527..3647b8b 100644 --- a/x2gobroker/agent.py +++ b/x2gobroker/agent.py @@ -48,6 +48,9 @@ def has_remote_broker_agent_setup(): - Check for available SSH private keys. - Nothing else, so far... + :returns: ``True``, if the broker supports remote broker agent calls + :rtype: ``bool`` + """ home = os.path.expanduser("~") if os.path.exists(os.path.join(home, '.ssh', 'id_rsa')): @@ -76,6 +79,10 @@ def call_broker_agent(username, task, cmdline_args=[], remote_agent=None, logger :raises X2GoBrokerAgentException: if the call to the remote broker agents fails. + :returns: ``(<success>, <data>)``, a tuple with the <success> flag as first item + and the data retrieved from the broker agent as second item + :rtype: ``tuple`` + """ if remote_agent in ('LOCAL', None): result = _call_local_broker_agent(username=username, task=task, cmdline_args=cmdline_args, logger=logger) @@ -99,6 +106,10 @@ def _call_local_broker_agent(username, task, cmdline_args=[], logger=None): :raises X2GoBrokerAgentException: if the call to the remote broker agents fails. + :returns: ``(<success>, <data>)``, a tuple with the <success> flag as first item + and the data retrieved from the broker agent as second item + :rtype: ``tuple`` + """ if logger is None: logger = logger_broker @@ -173,6 +184,10 @@ def _call_remote_broker_agent(username, task, cmdline_args=[], remote_agent=None :raises X2GoBrokerAgentException: if the call to the remote broker agents fails. + :returns: ``(<success>, <data>)``, a tuple with the <success> flag as first item + and the data retrieved from the broker agent as second item + :rtype: ``tuple`` + """ if logger is None: logger = logger_broker @@ -288,7 +303,7 @@ def list_sessions(username, remote_agent=None, logger=None, **kwargs): :param logger: logger instance to report log messages to :type logger: :class:`logging.<Some>Logger` - :returns: (<success>, <list-of-sessions>), a tuple with the <success> flag as first item + :returns: ``(<success>, <list-of-sessions>)``, a tuple with the <success> flag as first item and a session ``list`` as second item :rtype: ``tuple`` @@ -311,7 +326,7 @@ def suspend_session(username, session_name, remote_agent=None, logger=None, **kw :param logger: logger instance to report log messages to :type logger: :class:`logging.<Some>Logger` - :returns: (<success>, ), a tuple with the <success> flag as first item + :returns: ``(<success>, [])``, a tuple with the <success> flag as first item :rtype: ``tuple`` """ @@ -333,7 +348,7 @@ def terminate_session(username, session_name, remote_agent=None, logger=None, ** :param logger: logger instance to report log messages to :type logger: :class:`logging.<Some>Logger` - :returns: (<success>, ), a tuple with the <success> flag as first item + :returns: ``(<success>, [])``, a tuple with the <success> flag as first item :rtype: ``tuple`` """ @@ -356,7 +371,7 @@ def has_sessions(username, remote_agent=None, logger=None, **kwargs): :param logger: logger instance to report log messages to :type logger: :class:`logging.<Some>Logger` - :returns: (<success>, <has-running-sessions>, <has-suspended-session>), a tuple of two Boolean values + :returns: ``(<success>, <has-running-sessions>, <has-suspended-session>)``, a tuple of two Boolean values :rtype: ``tuple`` """ @@ -385,7 +400,7 @@ def find_busy_servers(username, remote_agent=None, logger=None, **kwargs): :param logger: logger instance to report log messages to :type logger: :class:`logging.<Some>Logger` - :returns: (<success>, <server-usage>), a tuple with the <success> flag as first item + :returns: ``(<success>, <server-usage>)``, a tuple with the <success> flag as first item and a dict reflecting the relative server usage :rtype: ``tuple`` @@ -418,7 +433,7 @@ def check_load(remote_agent=None, logger=None, **kwargs): :param logger: logger instance to report log messages to :type logger: :class:`logging.<Some>Logger` - :returns: (<success>, <load-factor>), a tuple with the <success> flag as first item + :returns: ``(<success>, <load-factor>)``, a tuple with the <success> flag as first item and the queried server's load factor as second item :rtype: ``tuple`` @@ -475,7 +490,7 @@ def add_authorized_key(username, pubkey_hash, authorized_keys_file='%h/.x2go/aut :param logger: logger instance to report log messages to :type logger: :class:`logging.<Some>Logger` - :returns: (<success>, ), a tuple with the <success> flag as first item + :returns: ``(<success>, [])``, a tuple with the <success> flag as first item :rtype: ``tuple`` """ @@ -501,7 +516,7 @@ def delete_authorized_key(username, pubkey_hash, authorized_keys_file='%h/.x2go/ :param logger: logger instance to report log messages to :type logger: :class:`logging.<Some>Logger` - :returns: (<success>, ), a tuple with the <success> flag as first item + :returns: ``(<success>, [])``, a tuple with the <success> flag as first item :rtype: ``tuple`` """ @@ -536,7 +551,7 @@ def get_servers(username, remote_agent=None, logger=None, **kwargs): :param logger: logger instance to report log messages to :type logger: :class:`logging.<Some>Logger` - :returns: (<success>, <server-list>), a tuple with the <success> flag as first item + :returns: ``(<success>, <server-list>)``, a tuple with the <success> flag as first item and the list of used X2Go Servers as second item :rtype: ``tuple`` @@ -572,7 +587,7 @@ def tasks_available(username, remote_agent=None, logger=None, **kwargs): :param logger: logger instance to report log messages to :type logger: :class:`logging.<Some>Logger` - :returns: (<success>, <server-list>), a tuple with the <success> flag as first item + :returns: ``(<success>, <server-list>)``, a tuple with the <success> flag as first item and a list of available broker agent tasks as second item :rtype: ``tuple`` @@ -597,7 +612,7 @@ def genkeypair(local_username, client_address, key_type='RSA', logger=None): :param logger: logger instance to report log messages to :type logger: :class:`logging.<Some>Logger` - :returns: two-item tuple: (<pubkey>, <privkey>) + :returns: two-item tuple: ``(<pubkey>, <privkey>)`` :rtype: ``tuple`` """ diff --git a/x2gobroker/authmechs/base_authmech.py b/x2gobroker/authmechs/base_authmech.py index 2973e23..47d006d 100644 --- a/x2gobroker/authmechs/base_authmech.py +++ b/x2gobroker/authmechs/base_authmech.py @@ -49,6 +49,5 @@ class X2GoBrokerAuthMech(object): :returns: Authentication failure (always!) :rtype: ``bool`` - """ return False diff --git a/x2gobroker/authservice.py b/x2gobroker/authservice.py index 2bac9bc..01cbf95 100644 --- a/x2gobroker/authservice.py +++ b/x2gobroker/authservice.py @@ -135,6 +135,10 @@ class AuthClient(asyncore.dispatcher_with_send): self.logger.info('authentication failure for \'{user}\' with password \'<hidden>\' against PAM service \'{service}\''.format(user=user, service=service)) def handle_close(self): + """\ + Close the connected :class:``AuthClient`` connection. + + """ self.close() @@ -170,5 +174,9 @@ class AuthService(asyncore.dispatcher_with_send): self.listen(1) def handle_accept(self): + """\ + Handle accepted connection requests. + + """ conn, _ = self.accept() AuthClient(conn, logger=self.logger) diff --git a/x2gobroker/basicauth.py b/x2gobroker/basicauth.py index d48117b..f4da6aa 100644 --- a/x2gobroker/basicauth.py +++ b/x2gobroker/basicauth.py @@ -28,6 +28,9 @@ def require_basic_auth(realm, validate_callback): """\ Handler for ``http(s)://`` BasisAuth processing. + This function is used as a decorator for web request handler classes + (such as tornado.web.RequestHandler). + :param realm: authentication realm :type realm: ``str`` :param validate_callback: callback function for validating credentials diff --git a/x2gobroker/brokers/base_broker.py b/x2gobroker/brokers/base_broker.py index 562620c..f70c558 100644 --- a/x2gobroker/brokers/base_broker.py +++ b/x2gobroker/brokers/base_broker.py @@ -1039,7 +1039,8 @@ class X2GoBroker(object): :param profile_id: choose remote agent for this profile ID :type profile_id: ``str`` - :param exclude_agents: a list of remote agent dict objects to be exclude from the random choice + :param exclude_agents: a list of remote agent dict objects to be exclude + from the random choice :type exclude_agents: ``list`` :returns: remote agent to use for queries for profile ID @@ -1280,7 +1281,8 @@ class X2GoBroker(object): def list_profiles(self, username): """\ - Retrieve a list of available session profiles for the authenticated user. + Retrieve a list of available session profiles for the + authenticated user. :param username: query session profile list for this user :type username: ``str`` @@ -1299,12 +1301,14 @@ class X2GoBroker(object): def select_session(self, profile_id, username=None, pubkey=None): """\ - Start/resume a session by selecting a profile name offered by the X2Go client. + Start/resume a session by selecting a profile name offered by the + X2Go client. - The X2Go server that the session is launched on is selected automatically by the X2Go session - broker. + The X2Go server that the session is launched on is selected + automatically by the X2Go session broker. - :param profile_id: the selected profile ID. This matches one of the dictionary keys offered by the ``list_profiles`` method + :param profile_id: the selected profile ID. This matches one of the dictionary + keys offered by the ``list_profiles`` method :type profile_id: ``str`` :param username: specify X2Go Server username that this operation runs for :type username: ``str`` @@ -1312,6 +1316,9 @@ class X2GoBroker(object): temporarily install into a remote X2Go Server for non-interactive login :type pubkey: ``str`` + :returns: the seclected session (X2Go session ID) + :rtype: ``str`` + """ try: profile = self.get_profile(profile_id) @@ -1706,21 +1713,59 @@ class X2GoBroker(object): def change_password(self, new='', old=''): """\ - Modify the authenticated user's password on the X2Go infrastructure (normally, one user - in one X2Go site setup should have the same password on all machines). + Modify the authenticated user's password on the X2Go + infrastructure (normally, one user in one X2Go site setup should + have the same password on all machines). + + This function is a dummy function and needs to be overridden in + specific broker backend implementations + + :param new: the new password that is to be set + :type new: ``str`` + :param old: the currently set password + :type old: ``str`` + + :returns: whether the password change has been successful + :rtype: ``bool`` """ return False def run_optional_script(self, script_type, username, password, task, profile_id, ip, cookie, authed=None, server=None): """\ - Run all optional scripts of type script_type. Called with 3 different script types: + Run all optional scripts of type script_type. Called with 3 + different script types: - pre_auth_scripts - before authentication happens - - post_auth_scripts - after authentication but before anything else occurs - - select_session_scripts - after load balancing before a specific server is sent to the client + - post_auth_scripts - after authentication but before + anything else occurs + - select_session_scripts - after load balancing before a + specific server is sent to the client + + These scripts allow for both addional actions to be performed as + well as the mangling of any relevant fields. - These scripts allow for both addional actions to be performed as well as the mangling of any relevant fields. + :param script_type: name of the script type to be executed (``pre_auth_scripts``, ``post_auth_scripts``, ``select_session_scripts``) + :type script_type: ``str`` + :param username: name of the X2Go session user a script will run for + :type username: ``str`` + :param password: password for the X2Go session + :type password: ``str`` + :param task: the broker task that currently being processed + :type task: ``str`` + :param profile_id: the session profile ID that is being operated upon + :type profile_id: ``str`` + :param ip: the client machine's IP address + :type ip: ``str`` + :param cookie: the currently valid authentication cookie + :type cookie: ``str`` + :param authed: authentication status (already authenticated or not) + :type authed: ``bool`` + :param server: hostname or IP address of the X2Go server being operated upon + :type server: ``str`` + + :returns: Pass-through of the return value returned by the to-be-run optional script (i.e., success or failure) + :rtype: ``bool`` """ diff --git a/x2gobroker/brokers/inifile_broker.py b/x2gobroker/brokers/inifile_broker.py index 72ddd9f..918d0d7 100644 --- a/x2gobroker/brokers/inifile_broker.py +++ b/x2gobroker/brokers/inifile_broker.py @@ -37,11 +37,21 @@ import x2gobroker.x2gobroker_exceptions from configparser import NoSectionError class X2GoBroker(base.X2GoBroker): + """\ + :class:`x2gobroker.brokers.inifile_broker.X2GoBroker` implements a broker backend + retrieving its session profile and ACL configuration from a file in + INI file format. + + """ backend_name = 'inifile' def __init__(self, profile_config_file=None, profile_config_defaults=None, **kwargs): """\ + Initialize a new INI file based X2GoBroker instance to control + X2Go session through an X2Go Client with an intermediate session + broker + :param profile_config_file: path to the backend's session profile configuration (x2gobroker-sessionprofiles.conf) :type profile_config_file: ``str`` :param profile_config_defaults: Default settings for session profile configuration parameters. @@ -55,11 +65,32 @@ class X2GoBroker(base.X2GoBroker): self.session_profiles = x2gobroker.config.X2GoBrokerConfigFile(config_files=profile_config_file, defaults=profile_config_defaults) def get_profile_ids(self): + """\ + Retrieve the complete list of session profile IDs. + + With the ``inifile`` broker backend, the profile IDs are the + names of the INI file's sections. + :returns: list of profile IDs + :rtype: ``list`` + + """ return self.session_profiles.list_sections() def get_profile_defaults(self): + """\ + Get the session profile defaults, i.e. profile options that all + configured session profiles have in common. + The defaults are hard-coded in :mod:`x2gobroker.defaults` for class + :class:`x2gobroker.brokers.base_broker.X2GoBroker`. With the ``inifile`` + backend, they can be overridden/customized under the INI file's + ``[DEFAULT]`` section. + + :returns: a dictionary containing the session profile defaults + :rtype: ``dict`` + + """ profile_defaults = self.session_profiles.get_defaults() for key in list(profile_defaults.keys()): if key.startswith('acl-'): @@ -70,7 +101,20 @@ class X2GoBroker(base.X2GoBroker): return profile_defaults def get_profile(self, profile_id): + """\ + Get the session profile for profile ID <profile_id>. + + With the ``inifile`` broker backend, the session profile + parameters are the given ``<parameter>=<value>`` pairs under the section + ``[<profile_id>]``. + + :param profile_id: the ID of a profile + :type profile_id: ``str`` + :returns: a dictionary representing the session profile for ID <profile_id> + :rtype: ``dict`` + + """ try: profile = self.session_profiles.get_section(profile_id) except NoSectionError: @@ -118,7 +162,20 @@ class X2GoBroker(base.X2GoBroker): return profile def get_profile_broker(self, profile_id): + """\ + Get broker-specific session profile options from the session profile with profile ID <profile_id>. + + With the ``inifile`` broker backend, these broker specific + options are ``<param>=<value>`` pairs prefixed like this: + ``broker-<param>=<value>`` + + :param profile_id: the ID of a profile + :type profile_id: ``str`` + + :returns: a dictionary representing the session profile for ID <profile_id> + :rtype: ``dict`` + """ profile = self.session_profiles.get_section(profile_id) for key in list(profile.keys()): if not key.startswith('broker-'): @@ -128,7 +185,20 @@ class X2GoBroker(base.X2GoBroker): return profile def get_profile_acls(self, profile_id): + """\ + Get the ACLs for session profile with profile ID <profile_id>. + + With the ``inifile`` broker backend, these ACL specific options + are ``<param>=<value>`` pairs prefixed like this: + ``acl-<param>=<value>`` + :param profile_id: the ID of a profile + :type profile_id: ``str`` + + :returns: a dictionary representing the ACLs for session profile with ID <profile_id> + :rtype: ``dict`` + + """ profile = self.session_profiles.get_section(profile_id) for key in list(profile.keys()): if not key.startswith('acl-'): diff --git a/x2gobroker/brokers/zeroconf_broker.py b/x2gobroker/brokers/zeroconf_broker.py index 656f0dc..697a800 100644 --- a/x2gobroker/brokers/zeroconf_broker.py +++ b/x2gobroker/brokers/zeroconf_broker.py @@ -34,7 +34,20 @@ class X2GoBroker(base.X2GoBroker): backend_name = 'zeroconf' def list_profiles(self, username): + """\ + Retrieve a list of session profiles for the authenticated user. + + With the ``zeroconf`` broker backend, this list of session profiles is + hard-coded. This if for testing purposes, only. + + :param username: query session profile list for this user + :type username: ``str`` + + :returns: list of profile dictionaries + :rtype: ``dict`` + + """ _list_of_profiles = { uuid.uuid4(): { 'user': '', @@ -81,7 +94,27 @@ class X2GoBroker(base.X2GoBroker): return list_of_profiles def select_session(self, profile_id, username=None, **kwargs): + """\ + Start/resume a session by selecting a profile name offered by the + X2Go client. + + With the ``zeroconf`` broker backend, the X2Go server that the + session is launched on is hard-coded (localhost, port 22). This + is for testing purposes only. + + :param profile_id: the selected profile ID. This matches one of the dictionary + keys offered by the ``list_profiles`` method + :type profile_id: ``str`` + :param username: specify X2Go Server username that this operation runs for + :type username: ``str`` + :param pubkey: The broker clients may send us a public key that we may + temporarily install into a remote X2Go Server for non-interactive login + :type pubkey: ``str`` + + :returns: the seclected session (X2Go session ID) + :rtype: ``str`` + """ selectprofile_output = { 'server': 'localhost', 'port': 22, diff --git a/x2gobroker/client/plain.py b/x2gobroker/client/plain.py index fc8ddd9..ec4e8d5 100644 --- a/x2gobroker/client/plain.py +++ b/x2gobroker/client/plain.py @@ -30,8 +30,36 @@ def _override_do_authenticate(username='', password=''): return True class X2GoBrokerClient(object): + """\ + Implementation of a command line interface to X2Go Session Broker. + This CLI can be evoked over an SSH connection. This provides the + so-called SSH mode for X2Go Session Broker. + + """ def get(self, args): + """\ + + Analogy to the http request get method of the HTTP X2Go Session + Broker, this method expects a set of arguments (i.e., an instance + of :class:``argparse.ArgumentParser``) and process the given + arguments. + + Well-known arguments are: + + * ``args.user`` - broker user on whose behalf to operate + * ``args.login`` - X2Go Server user for whom to perform the task + * ``args.auth_cookie`` - authentication cookie + * ``args.task`` - broker backend task to perform + * ``args.profile_id`` - session profile ID + + :param args: an :class:``argparse.ArgumentParser`` object provide by the ``x2gobroker`` command line script + :type args: ``obj`` + + :returns: output as expected by the calling client side + :rtype: ``str`` + + """ backend = args.backend if not backend: diff --git a/x2gobroker/nameservices/base_nameservice.py b/x2gobroker/nameservices/base_nameservice.py index 0dc99a2..2f7920b 100644 --- a/x2gobroker/nameservices/base_nameservice.py +++ b/x2gobroker/nameservices/base_nameservice.py @@ -20,28 +20,121 @@ class X2GoBrokerNameService(object): def has_user(self, username): + """\ + Provide information, if the broker knows a given user (or not). + + :param username: name of the user to check + :type username: ``str`` + + :returns: ``True`` if the user is known to the broker, ``False`` if not + :rtype: ``bool`` + + """ return username in self.get_users() def get_users(self): + """\ + Retrieve list of users known to the broker. + + :returns: list of known user names + :rtype: ``list`` + + """ return [] def get_primary_group(self, username): + """\ + Get the primary group of a given user. If the nameservices + backend in use does not support primary groups, an empty string + will be returned. + + :param username: name of the user to get the primary group for + :type username: ``str`` + + :returns: name of the primary group of the given user + :rtype: ``str`` + + """ return '' def has_group(self, group): + """\ + Provide information, if the broker knows a given group (or not). + + :param group: name of the group to check + :type group: ``str`` + + :returns: ``True`` if the group is known to the broker, ``False`` if not + :rtype: ``bool`` + + """ return group in self.get_groups() def get_groups(self): + """\ + Retrieve list of groups known to the broker. + + :returns: list of known group names + :rtype: ``list`` + + """ return [] def is_group_member(self, username, group, primary_groups=False): + """\ + Check, if a given user is member of a given group. + + Optionally, primary group memberships can be considered (or not). + + :param username: name of the user to check + :type username: ``str`` + :param group: name of the group to check + :type group: ``str`` + :param primary_groups: take primary group membership into consideration + or not + :type primary_groups: ``bool`` + + :returns: ``True`` if the user is member of the given group, ``False`` if not + :rtype: ``bool`` + + """ _members = self.get_group_members(group, primary_groups=primary_groups) return username in _members def get_group_members(self, group, primary_groups=False): + """\ + Retrieve a list of users being members of a given group. + + Optionally, primary group memberships can be considered (or not). + + :param group: name of the group to retrieve members of + :type group: ``str`` + :param primary_groups: take primary group membership into consideration + or not + :type primary_groups: ``bool`` + + :returns: list of users that are members of the given group + :rtype: ``list`` + + """ return [] def get_user_groups(self, username, primary_groups=False): + """\ + Retrieve a list of groups that a given user is member of. + + Optionally, primary group memberships can be considered (or not). + + :param username: name of the user to retrieve groupm memberships of + :type username: ``str`` + :param primary_groups: take primary group membership into consideration + or not + :type primary_groups: ``bool`` + + :returns: list of groups that the given user is member of + :rtype: ``list`` + + """ _groups = [] for _group in self.get_groups(): if self.is_group_member(username=username, group=_group, primary_groups=primary_groups): diff --git a/x2gobroker/nameservices/libnss_nameservice.py b/x2gobroker/nameservices/libnss_nameservice.py index 5db99e9..3c7622a 100644 --- a/x2gobroker/nameservices/libnss_nameservice.py +++ b/x2gobroker/nameservices/libnss_nameservice.py @@ -30,16 +30,56 @@ _grp = grp.getgrall() class X2GoBrokerNameService(base.X2GoBrokerNameService): def get_users(self): + """\ + Retrieve list of users from the POSIX nameservices system. + + :returns: list of known user names + :rtype: ``list`` + + """ return [ p.pw_name for p in _pwd ] def get_primary_group(self, username): + """\ + Get the primary group of a given POSIX user. + + :param username: name of the user to get the primary group for + :type username: ``str`` + + :returns: name of the primary group of the given user + :rtype: ``str`` + + """ prim_gid_number = [ p.pw_gid for p in _pwd if p.pw_name == username ][0] return [ g.gr_name for g in _grp if g.gr_gid == prim_gid_number ][0] def get_groups(self): + """\ + Retrieve list of groups from the POSIX nameservices system. + + :returns: list of known group names + :rtype: ``list`` + + """ return [ g.gr_name for g in _grp ] def get_group_members(self, group, primary_groups=False): + """\ + Retrieve a list of POSIX users being members of a given POSIX + group. + + Optionally, primary group memberships can be considered (or not). + + :param group: name of the group to retrieve members of + :type group: ``str`` + :param primary_groups: take primary group membership into consideration + or not + :type primary_groups: ``bool`` + + :returns: list of users that are members of the given group + :rtype: ``list`` + + """ _members_from_primgroups = [] if primary_groups: for username in self.get_users(): diff --git a/x2gobroker/nameservices/testsuite_nameservice.py b/x2gobroker/nameservices/testsuite_nameservice.py index a6be6ad..70e5d96 100644 --- a/x2gobroker/nameservices/testsuite_nameservice.py +++ b/x2gobroker/nameservices/testsuite_nameservice.py @@ -33,15 +33,59 @@ _groups = { class X2GoBrokerNameService(base.X2GoBrokerNameService): def get_users(self): + """\ + Retrieve hard-coded list of users that we can use for + unit testing. + + :returns: list of known user names + :rtype: ``list`` + + """ return _users def get_primary_group(self, username): + """\ + In POSIX, the primary group name is equal to the user name. + As this is the only straw we can grab during unit tests, we + return the username here. + + :param username: name of the user to get the primary group for + :type username: ``str`` + + :returns: name of the primary group of the given user + :rtype: ``str`` + + """ return username def get_groups(self): + """\ + Retrieve hard-coded list of groups that we can use for unit + testing. + + :returns: list of known group names + :rtype: ``list`` + + """ return list(_groups.keys()) + _users def get_group_members(self, group, primary_groups=False): + """\ + Retrieve a list of users being members of a given group. For unit + testing, the group membership relations have been hard-coded. + + Optionally, primary group memberships can be considered (or not). + + :param group: name of the group to retrieve members of + :type group: ``str`` + :param primary_groups: take primary group membership into consideration + or not + :type primary_groups: ``bool`` + + :returns: list of users that are members of the given group + :rtype: ``list`` + + """ _members = [] if group in list(_groups.keys()): _members.extend(_groups[group]) @@ -50,4 +94,3 @@ class X2GoBrokerNameService(base.X2GoBrokerNameService): if group == self.get_primary_group(username): _members.append(username) return _members - diff --git a/x2gobroker/optional_scripts/base_script.py b/x2gobroker/optional_scripts/base_script.py index 4a64160..92b31e5 100755 --- a/x2gobroker/optional_scripts/base_script.py +++ b/x2gobroker/optional_scripts/base_script.py @@ -19,6 +19,43 @@ # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. class X2GoBrokerOptionalScript(object): + """\ + X2Go Session Broker supports running optional Python code that a site admin + can add to the broker installation files. + + All those optional scripts need to be inherited from this class. + + """ def run_me(self, username, password, task, profile_id, ip, cookie, authed, server): + """\ + Dummy :function:``run_me()`` function. If you deploy your own + optional scripts with X2Go Session Broker, make sure that your + class overrides this function. The broker frontends will try to + execute code presented under this method name at pre-auth, + post-auth and session-selected. + + :param script_type: name of the script type to be executed (``pre_auth_scripts``, ``post_auth_scripts``, ``select_session_scripts``) + :type script_type: ``str`` + :param username: name of the X2Go session user a script will run for + :type username: ``str`` + :param password: password for the X2Go session + :type password: ``str`` + :param task: the broker task that currently being processed + :type task: ``str`` + :param profile_id: the session profile ID that is being operated upon + :type profile_id: ``str`` + :param ip: the client machine's IP address + :type ip: ``str`` + :param cookie: the currently valid authentication cookie + :type cookie: ``str`` + :param authed: authentication status (already authenticated or not) + :type authed: ``bool`` + :param server: hostname or IP address of the X2Go server being operated upon + :type server: ``str`` + + :returns: Pass-through of the return value returned by the to-be-run optional script (i.e., success or failure) + :rtype: ``bool`` + + """ return username, password, task, profile_id, ip, cookie, authed, server diff --git a/x2gobroker/web/extras.py b/x2gobroker/web/extras.py index 4e1e8fd..627ff78 100644 --- a/x2gobroker/web/extras.py +++ b/x2gobroker/web/extras.py @@ -34,7 +34,10 @@ class _RequestHandler(tornado.web.RequestHandler): class X2GoBrokerItWorks(_RequestHandler): + """\ + HTTP request handler that simply replies with an "It works" page. + """ http_header_items = { 'Content-Type': 'text/plain; charset=utf-8', 'Expires': '+1h', @@ -54,7 +57,11 @@ class X2GoBrokerItWorks(_RequestHandler): class X2GoBrokerPubKeyService(_RequestHandler): + """\ + HTTP request handler that provides X2Go Session Broker's SSH public + keys, if any are configured. + """ http_header_items = { 'Content-Type': 'text/plain; charset=utf-8', 'Expires': '+1h', diff --git a/x2gobroker/web/json.py b/x2gobroker/web/json.py index a802866..8e1853b 100644 --- a/x2gobroker/web/json.py +++ b/x2gobroker/web/json.py @@ -37,7 +37,16 @@ class _RequestHandler(tornado.web.RequestHandler): class X2GoBrokerWeb(_RequestHandler): + """\ + HTTP request handler that provides the JSON web frontend of the X2Go + Session Broker. + Currently, Python X2Go and all derived X2Go Client applications use + this web frontend / communication protocol format.. + + :raises tornado.web.HTTPError: on authentication failure a 401 error is raised + + """ http_header_items = { 'Content-Type': 'text/json; charset=utf-8', 'Expires': '+1h', @@ -48,20 +57,38 @@ class X2GoBrokerWeb(_RequestHandler): for http_header_item in list(self.http_header_items.keys()): self.set_header(http_header_item, self.http_header_items[http_header_item]) - def get(self, backend): + def get(self, path): + """\ + Implementation of the JSON based broker communication protocol as + used by Python X2Go (via POST requests). + + In debug mode you can test the broker's functionality using a + normal web browser via GET requests. + + :param path: URL path + :type path: ``str`` + + """ if x2gobroker.defaults.X2GOBROKER_DEBUG: logger_broker.warn('GET http request detected, if unwanted: disable X2GOBROKER_DEBUG') - return self.post(backend) + return self.post(path) raise tornado.web.HTTPError(405) - def post(self, backend): + def post(self, path): + """\ + Implementation of the JSON based broker communication protocol as + used by Python X2Go (via POST requests). + + :param path: URL path + :type path: ``str`` + """ self._gen_http_header() - if not backend: + if not path: backend = x2gobroker.defaults.X2GOBROKER_DEFAULT_BACKEND else: - backend = backend.rstrip('/') + backend = path.rstrip('/') if '/' in backend: backend = backend.split('/')[0] @@ -201,4 +228,3 @@ class X2GoBrokerWeb(_RequestHandler): return raise tornado.web.HTTPError(401) - diff --git a/x2gobroker/web/plain.py b/x2gobroker/web/plain.py index 9b1a17e..6183672 100644 --- a/x2gobroker/web/plain.py +++ b/x2gobroker/web/plain.py @@ -33,7 +33,15 @@ class _RequestHandler(tornado.web.RequestHandler): class X2GoBrokerWeb(_RequestHandler): + """\ + HTTP request handler that provides the plain text web frontend of the + X2Go Session Broker. + Currently, X2Go Client uses this webfrontend / communication protocol format. + + :raises tornado.web.HTTPError: on authentication failure a 401 error is raised + + """ http_header_items = { 'Content-Type': 'text/plain; charset=utf-8', 'Expires': '+1h', @@ -44,20 +52,38 @@ class X2GoBrokerWeb(_RequestHandler): for http_header_item in list(self.http_header_items.keys()): self.set_header(http_header_item, self.http_header_items[http_header_item]) - def get(self, backend): + def get(self, path): + """\ + Implementation of the plain text broker communication protocol as + used by X2Go Client (via POST requests). + + In debug mode you can test the broker's functionality using a + normal web browser via GET requests. + + :param path: URL path + :type path: ``str`` + + """ if x2gobroker.defaults.X2GOBROKER_DEBUG: logger_broker.warn('GET http request detected, if unwanted: disable X2GOBROKER_DEBUG') - return self.post(backend) + return self.post(path) raise tornado.web.HTTPError(405) - def post(self, backend): + def post(self, path): + """\ + Implementation of the plain text broker communication protocol as + used by X2Go Client (via POST requests). + + :param path: URL path + :type path: ``str`` + """ self._gen_http_header() - if not backend: + if not path: backend = x2gobroker.defaults.X2GOBROKER_DEFAULT_BACKEND else: - backend = backend.rstrip('/') + backend = path.rstrip('/') if '/' in backend: backend = backend.split('/')[0] diff --git a/x2gobroker/web/uccs.py b/x2gobroker/web/uccs.py index 5bef36e..c1189f5 100644 --- a/x2gobroker/web/uccs.py +++ b/x2gobroker/web/uccs.py @@ -30,7 +30,18 @@ import x2gobroker.basicauth def credentials_validate(username, password): + """\ + Helper function to validate some given credentials. + :param username: the username + :type username: ``str`` + :param password: the user's password + :type password: ``str`` + + :returns: ``(<username>, <success>)`` tuple + :rtype: ``(str, bool)`` + + """ import x2gobroker.brokers.base_broker # FIXME: with the below hack, the backend broker detection in X2GoBrokerWeb is disabled, only global options # from x2gobroker.conf are available here... @@ -54,19 +65,50 @@ class _RequestHandler(tornado.web.RequestHandler): class X2GoBrokerWeb(_RequestHandler): def get(self, path): + """\ + With the UCCS protocol, a HEAD request is sent to the server's + base URL (sort of as an is-alive ping). + + If X2Go Session Broker runs in debug mode, the processing of + the HEAD request reponse is also made available to GET requests. + This means, you can open the base URL in a normal web browser and + get a reply. + + :param path: URL path + :type path: ``str`` + + """ if x2gobroker.defaults.X2GOBROKER_DEBUG: logger_broker.warn('GET http request detected, if unwanted: disable X2GOBROKER_DEBUG') return self.head(path) raise tornado.web.HTTPError(405) def head(self, path): + """\ + With the UCCS protocol, a HEAD request is sent to the server's + base URL (sort of as an is-alive ping). + + :param path: URL path + :type path: ``str`` + + """ self.write(str(datetime.datetime.utcnow())) return @x2gobroker.basicauth.require_basic_auth('Authentication required', credentials_validate) class X2GoBrokerWebAPI(tornado.web.RequestHandler): + """\ + HTTP request handler that provides the UCCS web frontend of the + X2Go Session Broker. + + Currently, Arctica Greeter with Remote Logon Service uses this + webfrontend / communication protocol format. + :raises tornado.web.HTTPError: on authentication failure a 401 + error is raised; on invalid API versions, a 404 error is raised. + + """ def __init__(self, *args, **kwargs): # latest API version is 5 self.api_version = 5 @@ -85,7 +127,13 @@ class X2GoBrokerWebAPI(tornado.web.RequestHandler): self.set_header(http_header_item, self.http_header_items[http_header_item]) def get(self, *args, **kwargs): + """\ + Implementation of the UCCS broker API versions 4 (final) and 5 (in development). + + :param path: URL path + :type path: ``str`` + """ self._gen_http_header() backend = args[0] diff --git a/x2gobroker/x2gobroker_exceptions.py b/x2gobroker/x2gobroker_exceptions.py index b766089..b6e866e 100644 --- a/x2gobroker/x2gobroker_exceptions.py +++ b/x2gobroker/x2gobroker_exceptions.py @@ -20,9 +20,26 @@ from x2gobroker.loggers import logger_error class X2GoBrokerBaseException(BaseException): + """\ + Base exception for all X2Go Session Broker exceptions." + + """ def __init__(self, *args, **kwargs): BaseException.__init__(self, *args, **kwargs) logger_error.error('exception raised: {exception}("{msg}")'.format(exception=type(self).__name__, msg=str(self))) -class X2GoBrokerAgentException(X2GoBrokerBaseException): pass -class X2GoBrokerProfileException(X2GoBrokerBaseException): pass +class X2GoBrokerAgentException(X2GoBrokerBaseException): + """\ + X2Go Broker Agent exception class. Used for failures during broker + <-> broker agent communications. + + """ + pass + +class X2GoBrokerProfileException(X2GoBrokerBaseException): + """\ + X2Go Broker exception class for session profile problems. Used for + failures when parsing and processing session profiles. + + """ + pass -- Alioth's /home/x2go-admin/maintenancescripts/git/hooks/post-receive-email on /srv/git/code.x2go.org/x2gobroker.git