From a85e289927d22cd92d97886fbb4925149a58c39e Mon Sep 17 00:00:00 2001 From: Michael Hierweck Date: Wed, 23 Mar 2016 13:18:26 +0100 Subject: [PATCH] Initial release. --- .gitignore | 11 +++ CHANGES.txt | 8 ++ CONTRIBUTORS.txt | 1 + README.txt | 37 ++++++++ docs/LICENSE.LGPL | 165 +++++++++++++++++++++++++++++++++ docs/LICENSE.txt | 15 +++ setup.py | 44 +++++++++ src/hs/__init__.py | 4 + src/hs/admin/__init__.py | 4 + src/hs/admin/api/__init__.py | 4 + src/hs/admin/api/api.py | 59 ++++++++++++ src/hs/admin/api/dispatcher.py | 28 ++++++ src/hs/admin/api/exceptions.py | 18 ++++ src/hs/admin/api/method.py | 17 ++++ src/hs/admin/api/module.py | 43 +++++++++ src/hs/admin/api/proxy.py | 39 ++++++++ src/hs/admin/api/session.py | 42 +++++++++ 17 files changed, 539 insertions(+) create mode 100644 .gitignore create mode 100644 CHANGES.txt create mode 100644 CONTRIBUTORS.txt create mode 100644 README.txt create mode 100644 docs/LICENSE.LGPL create mode 100644 docs/LICENSE.txt create mode 100644 setup.py create mode 100644 src/hs/__init__.py create mode 100644 src/hs/admin/__init__.py create mode 100644 src/hs/admin/api/__init__.py create mode 100644 src/hs/admin/api/api.py create mode 100644 src/hs/admin/api/dispatcher.py create mode 100644 src/hs/admin/api/exceptions.py create mode 100644 src/hs/admin/api/method.py create mode 100644 src/hs/admin/api/module.py create mode 100644 src/hs/admin/api/proxy.py create mode 100644 src/hs/admin/api/session.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..878619b --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +*.py[co] + +# Packages +*.egg +*.egg-info +*.tar.gz +dist +build +sdist +bdist +deb_dist diff --git a/CHANGES.txt b/CHANGES.txt new file mode 100644 index 0000000..0bd24f5 --- /dev/null +++ b/CHANGES.txt @@ -0,0 +1,8 @@ +Changelog +========= + +0.1 +--- + +- Initial release. + [Michael Hierweck , Hostsharing eG] diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt new file mode 100644 index 0000000..d56decd --- /dev/null +++ b/CONTRIBUTORS.txt @@ -0,0 +1 @@ +Michael Hierweck , Hostsharing eG. diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..381f095 --- /dev/null +++ b/README.txt @@ -0,0 +1,37 @@ +Introduction +============ + +This package provides a Python implementation of the Hostsharing HSAdmin API. + +Example +------- + +>>> from hs.admin.api import API +>>> +>>> api = API(cas=dict(uri='https://login.hostsharing.net/cas/v1/tickets', +... service='https://config.hostsharing.net:443/hsar/backend'), +... credentials=dict(username='xyz00', +... password='top-secret'), +... backends=['https://config.hostsharing.net:443/hsar/xmlrpc/hsadmin', +... 'https://config2.hostsharing.net:443/hsar/xmlrpc/hsadmin']) +>>> +>>> dir(api) +['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'contact', 'customer', 'domain', 'emailaddress', 'emailalias', 'get_module', 'hive', 'list_modules', 'mandat', 'modules', 'mysqldb', 'mysqluser', 'pac', 'postgresqldb', 'postgresqluser', 'property', 'q', 'role', 'user'] +>>> +>>> dir(api.mysqluser) +['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'add', 'delete', 'get_method', 'get_property', 'list_methods', 'list_properties', 'methods', 'name', 'properties', 'search', 'update'] +>>> +>>> print api.mysqluser.properties +{'pac': {'validationRegexp': '[a-z0-9]*', 'name': 'pac', 'searchable': 'equals', 'readwriteable': 'read', 'minLength': '0', 'displaySequence': '2', 'displayVisible': 'always', 'maxLength': '999', 'type': 'string'}, 'instance': {'validationRegexp': '[a-zA-Z]*', 'name': 'instance', 'searchable': 'equals', 'readwriteable': 'read', 'minLength': '0', 'displaySequence': '2', 'displayVisible': 'always', 'maxLength': '999', 'type': 'string'}, 'password': {'validationRegexp': "[^']{6,}", 'name': 'password', 'searchable': 'equals', 'readwriteable': 'none', 'minLength': '0', 'displaySequence': '2', 'displayVisible': 'always', 'maxLength': '999', 'type': 'string'}, 'name': {'validationRegexp': '[a-z0-9]{5}_[a-z0-9_]{1,}', 'name': 'name', 'searchable': 'equals', 'readwriteable': 'writeonce', 'minLength': '0', 'displaySequence': '1', 'displayVisible': 'always', 'maxLength': '999', 'type': 'string'}} +>>> +>>> print api.user.search() +[{'comment': 'xyz00', 'shell': '/bin/bash', 'locked': False, 'name': 'xyz00', 'quota_hardlimit': '0', 'userid': '104192', 'quota_softlimit': '0', 'pac': 'xyz00', 'id': '12110', 'homedir': '/home/pacs/xyz00'}, {'comment': 'xyz00-admin', 'shell': '/bin/bash', 'locked': False, 'name': 'xyz00-admin', 'quota_hardlimit': '0', 'userid': '104225', 'quota_softlimit': '0', 'pac': 'xyz00', 'id': '12184', 'homedir': '/home/pacs/xyz00/users/admin'}] +>>> +>>> print api.user.search(where={'name': 'xyz00-admin'}) +[{'comment': 'xyz00-admin', 'shell': '/bin/bash', 'locked': False, 'name': 'xyz00-admin', 'quota_hardlimit': '0', 'userid': '104225', 'quota_softlimit': '0', 'pac': 'xyz00', 'id': '12184', 'homedir': '/home/pacs/xyz00/users/admin'}] +>>> +>>> api.user.update(where={'name': 'xyz00-admin'}, set={'password': 'less-secret'}) +>>> +>>> api.domain.add(set={'name': 'example.com', user: 'xyz00-admin'}) +>>> +>>> api.domain.delete(where={'name': 'example.com'}) diff --git a/docs/LICENSE.LGPL b/docs/LICENSE.LGPL new file mode 100644 index 0000000..65c5ca8 --- /dev/null +++ b/docs/LICENSE.LGPL @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/docs/LICENSE.txt b/docs/LICENSE.txt new file mode 100644 index 0000000..5e4a2d0 --- /dev/null +++ b/docs/LICENSE.txt @@ -0,0 +1,15 @@ +hs.admin.api Copyright 2015, Hostsharing eG + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. + +This program 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., 59 +Temple Place, Suite 330, Boston, MA 02111-1307 USA. diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..3c68740 --- /dev/null +++ b/setup.py @@ -0,0 +1,44 @@ +from setuptools import setup, find_packages +import os + +version = '0.1' + +long_description = ( + open('README.txt').read() + + '\n' + + 'Contributors\n' + '============\n' + + '\n' + + open('CONTRIBUTORS.txt').read() + + '\n' + + open('CHANGES.txt').read() + + '\n') + +setup(name='hs.admin.api', + version=version, + description="Hostsharing HSAdmin API", + long_description=long_description, + # Get more strings from + # http://pypi.python.org/pypi?%3Aaction=list_classifiers + classifiers=[ + "Programming Language :: Python", + ], + keywords='Hostsharing HSAdmin API', + author='Hostsharing eG', + author_email='info@hostsharing.net', + url='https://www.hostsharing.net', + license='lgpl', + packages=find_packages('src'), + package_dir = {'': 'src'}, + namespace_packages=['hs', 'hs.admin'], + include_package_data=True, + zip_safe=False, + install_requires=[ + 'setuptools', + 'requests', + # -*- Extra requirements: -*- + ], + entry_points=""" + # -*- Entry points: -*- + """, + ) diff --git a/src/hs/__init__.py b/src/hs/__init__.py new file mode 100644 index 0000000..45871cd --- /dev/null +++ b/src/hs/__init__.py @@ -0,0 +1,4 @@ +""" hs namespace package +""" + +__import__('pkg_resources').declare_namespace(__name__) diff --git a/src/hs/admin/__init__.py b/src/hs/admin/__init__.py new file mode 100644 index 0000000..1b67390 --- /dev/null +++ b/src/hs/admin/__init__.py @@ -0,0 +1,4 @@ +""" hs.admin namespace package +""" + +__import__('pkg_resources').declare_namespace(__name__) diff --git a/src/hs/admin/api/__init__.py b/src/hs/admin/api/__init__.py new file mode 100644 index 0000000..ed4e2b3 --- /dev/null +++ b/src/hs/admin/api/__init__.py @@ -0,0 +1,4 @@ +""" hs.admin.api namespace package +""" + +from .api import API diff --git a/src/hs/admin/api/api.py b/src/hs/admin/api/api.py new file mode 100644 index 0000000..3ea3cc7 --- /dev/null +++ b/src/hs/admin/api/api.py @@ -0,0 +1,59 @@ +""" This module provides a class that implements the HSAdmin API. +""" + +from xmlrpclib import ServerProxy + +from .dispatcher import Dispatcher +from .session import Session +from .proxy import Proxy +from .module import Module + + + +class API(object): + """ This class implements the HSAdmin API. + """ + + def __init__(self, cas, credentials, backends): + session = Session(cas['uri'], + cas['service'], + credentials['username'], + credentials['password']) + + modules = dict() + meta = dict() + for backend in backends: + remote = getattr(ServerProxy(backend), 'property.search') + props = remote(session.get_user(), session.get_ticket(), dict()) + meta[backend] = dict() + for module in set([prop['module'] for prop in props]): + modules[module] = backend + meta[backend][module] = dict() + for prop in [prop for prop in props + if prop.has_key('module') and (prop['module'] == module)]: + del prop['module'] + meta[backend][module][prop['name']] = prop + + dispatcher = Dispatcher(modules) + proxy = Proxy(session, dispatcher) + + self.modules = dict() + for module in dispatcher.get_modules(): + self.modules[module] = Module(proxy, + module, + meta[dispatcher.get_backend(module)][module], + ['search', 'add', 'update', 'delete']) + setattr(self, module, self.modules[module]) + + + def list_modules(self): + """ Return the list of modules """ + + return self.modules.keys() + + + def get_module(self, name): + """ Returns a module instance by name """ + + return self.modules[name] + diff --git a/src/hs/admin/api/dispatcher.py b/src/hs/admin/api/dispatcher.py new file mode 100644 index 0000000..af5ba01 --- /dev/null +++ b/src/hs/admin/api/dispatcher.py @@ -0,0 +1,28 @@ +""" This module provides class that provides information to dispatches to the relevant backend. +""" + + +class Dispatcher(object): + """ This class provides information to dispatch to the relevant backend. + """ + + def __init__(self, modules): + self.modules = modules + + + def get_backend(self, module): + """ Returns the backend which is relevant for the remote module """ + + return self.modules[module] + + + def get_backends(self): + """ Returns the list of available backends """ + + return set(self.modules.values()) + + + def get_modules(self): + """ Returns the list of available remote modules """ + + return set(self.modules.keys()) diff --git a/src/hs/admin/api/exceptions.py b/src/hs/admin/api/exceptions.py new file mode 100644 index 0000000..3464a58 --- /dev/null +++ b/src/hs/admin/api/exceptions.py @@ -0,0 +1,18 @@ +""" This module provides exception classes. +""" + + + +class LoginError(Exception): + """ Raised when the login failed. + """ + + +class SessionError(Exception): + """ Raised when the session failed. + """ + + +class ProxyError(Exception): + """ Raised when an remote backend invokation failed. + """ diff --git a/src/hs/admin/api/method.py b/src/hs/admin/api/method.py new file mode 100644 index 0000000..b280b62 --- /dev/null +++ b/src/hs/admin/api/method.py @@ -0,0 +1,17 @@ +""" This module provides a directly callable class that implements a remote method. +""" + + + +class Method(object): + """ This directly callable class implements a remote method. + """ + + def __init__(self, proxy, module, name): + self.module = module + self.name = name + + self.__class__.__call__ = lambda self, where=None, set=None: proxy(self.module.name, + self.name, + where, + set) diff --git a/src/hs/admin/api/module.py b/src/hs/admin/api/module.py new file mode 100644 index 0000000..6f59856 --- /dev/null +++ b/src/hs/admin/api/module.py @@ -0,0 +1,43 @@ +""" This module provides a class that representing a remote module. +""" + +from .method import Method + + + +class Module(object): + """ This class represents a remote module. + """ + + def __init__(self, proxy, name, properties, methods): + self.name = name + self.properties = properties + + self.methods = dict() + for method in methods: + self.methods[name] = Method(proxy, self, method) + setattr(self, method, self.methods[name]) + + + def list_methods(self): + """ Returns the list of methods """ + + return self.methods.keys() + + + def get_method(self, name): + """ Returns a method instance by name """ + + return self.methods[name] + + + def list_properties(self): + """ Returns the list of properties """ + + return self.properties.key() + + + def get_property(self, name): + """ Returns a property instance by name """ + + return self.properties[name] diff --git a/src/hs/admin/api/proxy.py b/src/hs/admin/api/proxy.py new file mode 100644 index 0000000..8dadd60 --- /dev/null +++ b/src/hs/admin/api/proxy.py @@ -0,0 +1,39 @@ +""" This module provides a directly callable class that implements a remote method invokation. +""" + +from xmlrpclib import ServerProxy +from xmlrpclib import Fault + +from .exceptions import ProxyError + + + +class Proxy(object): + """ This directly callable class implements a remote method invokation. + """ + + def __init__(self, session, dispatcher): + self.session = session + self.dispatcher = dispatcher + + + def __call__(self, module, method, where=None, set=None): + backend = self.dispatcher.get_backend(module) + remote = getattr(ServerProxy(backend), module + '.' + method) + ticket = self.session.get_ticket() + user = self.session.get_user() + + try: + if (where is None) and (set is None): + result = remote(user, ticket) + elif where is None: + result = remote(user, ticket, set) + elif set is None: + result = remote(user, ticket, where) + else: + result = remote(user, ticket, set, where) + + except Fault as fault: + raise ProxyError(fault.faultString) + + return result diff --git a/src/hs/admin/api/session.py b/src/hs/admin/api/session.py new file mode 100644 index 0000000..4432e22 --- /dev/null +++ b/src/hs/admin/api/session.py @@ -0,0 +1,42 @@ +""" This module provides a class that implements a remote user session. +""" + +from requests import delete, post + +from .exceptions import LoginError, SessionError + + + +class Session(object): + """ This class implements a remote user session. + """ + + def __init__(self, provider, service, username, password): + payload = {'username': username, + 'password': password} + result = post(provider, data=payload) + if result.status_code != 201: + raise LoginError('Acquisition of ticket granting ticket failed.') + self.service = service + self.tgt = result.headers['location'] + self.user = username + + + def __exit__(self, exc_type, exc_value, traceback): + delete(self.tgt) + + + def get_ticket(self): + """ Returns an one-time ticket for a remote user action """ + + payload = {'service': self.service} + result = post(self.tgt, data=payload) + if result.status_code != 200: + raise SessionError('Acquisition of session ticket failed.') + return result.text + + + def get_user(self): + """ Returns the user name """ + + return self.user