[PATCHv4 0/8] LDAP support in backend

From: Royce Lv <lvroyce@linux.vnet.ibm.com> v3>v4, Move authentication type report to capabilities. Be back compatible for old access label. Change try/exception logic in ldap authentication. Royce Lv (8): Add configuration of LDAP Split PAM and LDAP authentication Add LDAP authentication Fix test cases for authentication Split users and groups for permission query Move validation to authorizaiton change vm permission tag Update test model to fix user/group listing contrib/DEBIAN/control.in | 1 + contrib/kimchi.spec.fedora.in | 1 + contrib/kimchi.spec.suse.in | 1 + docs/API.md | 20 +++--- docs/README.md | 36 +++++----- src/kimchi.conf.in | 17 +++++ src/kimchi/auth.py | 155 +++++++++++++++++++++++++++++++----------- src/kimchi/config.py.in | 5 ++ src/kimchi/control/auth.py | 42 ++++++++++++ src/kimchi/control/host.py | 14 ---- src/kimchi/i18n.py | 1 + src/kimchi/model/config.py | 1 + src/kimchi/model/groups.py | 68 ++++++++++++++++++ src/kimchi/model/host.py | 19 ------ src/kimchi/model/users.py | 90 ++++++++++++++++++++++++ src/kimchi/model/vms.py | 62 ++++++++++++----- tests/test_model.py | 3 +- tests/test_rest.py | 4 +- tests/utils.py | 43 ++++++++---- 19 files changed, 451 insertions(+), 132 deletions(-) create mode 100644 src/kimchi/control/auth.py create mode 100644 src/kimchi/model/groups.py create mode 100644 src/kimchi/model/users.py -- 1.8.3.2

From: Royce Lv <lvroyce@linux.vnet.ibm.com> Add LDAP configuration to specify LDAP server, search base and filter for query user. Signed-off-by: Royce Lv <lvroyce@linux.vnet.ibm.com> --- src/kimchi.conf.in | 14 ++++++++++++++ src/kimchi/config.py.in | 5 +++++ 2 files changed, 19 insertions(+) diff --git a/src/kimchi.conf.in b/src/kimchi.conf.in index ea39292..62eb40b 100644 --- a/src/kimchi.conf.in +++ b/src/kimchi.conf.in @@ -43,3 +43,17 @@ [display] # Port for websocket proxy to listen on #display_proxy_port = 64667 + +[authentication] +# Authentication method, available option: pam, ldap. +# method = pam + +# If specified method to ldap, following fields need to be specified. +# ldap server domain name used to authenticate. +# ldap_server = "localhost" + +# Search tree base in ldap +# ldap_search_base = "ou=People, dc=kimchi, dc=org" + +# User id filter +# ldap_search_filter = "uid=%(username)s" diff --git a/src/kimchi/config.py.in b/src/kimchi/config.py.in index 06765a2..88de1aa 100644 --- a/src/kimchi/config.py.in +++ b/src/kimchi/config.py.in @@ -285,6 +285,11 @@ def _get_config(): config.set("server", "environment", "production") config.set("server", "federation", "off") config.set('server', 'max_body_size', '4*1024*1024') + config.add_section("authentication") + config.set("authentication", "method", "pam") + config.set("authentication", "ldap_server", "") + config.set("authentication", "ldap_search_base", "") + config.set("authentication", "ldap_search_filter", "") config.add_section("logging") config.set("logging", "log_dir", paths.log_dir) config.set("logging", "log_level", DEFAULT_LOG_LEVEL) -- 1.8.3.2

From: Royce Lv <lvroyce@linux.vnet.ibm.com> Split PAM authentication implementation and abstract a common class. Signed-off-by: Royce Lv <lvroyce@linux.vnet.ibm.com> --- docs/API.md | 1 + src/kimchi/auth.py | 111 +++++++++++++++++++++++++++++---------------- src/kimchi/model/config.py | 1 + 3 files changed, 73 insertions(+), 40 deletions(-) diff --git a/docs/API.md b/docs/API.md index 9b866f3..9bfbf4f 100644 --- a/docs/API.md +++ b/docs/API.md @@ -669,6 +669,7 @@ creation. * repo_mngt_tool: 'deb', 'yum' or None - when the repository management tool is not identified * federation: 'on' if federation feature is enabled, 'off' otherwise. + * auth: authentication type, 'pam' and 'ldap' are supported. * **POST**: *See Configuration Actions* **Actions (POST):** diff --git a/src/kimchi/auth.py b/src/kimchi/auth.py index c8801a5..5986094 100644 --- a/src/kimchi/auth.py +++ b/src/kimchi/auth.py @@ -31,6 +31,7 @@ import urllib2 from kimchi import template +from kimchi.config import config from kimchi.exception import InvalidOperation, OperationFailed from kimchi.utils import get_all_tabs, run_command @@ -59,6 +60,22 @@ def debug(msg): class User(object): + @classmethod + def get(cls, auth_args): + auth_type = auth_args.pop('auth_type') + for klass in cls.__subclasses__(): + if auth_type == klass.auth_type: + try: + if not klass.authenticate(**auth_args): + debug("cannot verify user with the given password") + return None + except OperationFailed: + raise + return klass(auth_args['username']) + + +class PAMUser(User): + auth_type = "pam" def __init__(self, username): self.user = {} @@ -124,40 +141,54 @@ class User(object): def get_user(self): return self.user + @staticmethod + def authenticate(username, password, service="passwd"): + '''Returns True if authenticate is OK via PAM.''' + def _pam_conv(auth, query_list, userData=None): + resp = [] + for i in range(len(query_list)): + query, qtype = query_list[i] + if qtype == PAM.PAM_PROMPT_ECHO_ON: + resp.append((username, 0)) + elif qtype == PAM.PAM_PROMPT_ECHO_OFF: + resp.append((password, 0)) + elif qtype == PAM.PAM_PROMPT_ERROR_MSG: + cherrypy.log.error_log.error( + "PAM authenticate prompt error: %s" % query) + resp.append(('', 0)) + elif qtype == PAM.PAM_PROMPT_TEXT_INFO: + resp.append(('', 0)) + else: + return None + return resp + + auth = PAM.pam() + auth.start(service) + auth.set_item(PAM.PAM_USER, username) + auth.set_item(PAM.PAM_CONV, _pam_conv) + try: + auth.authenticate() + except PAM.error, (resp, code): + msg_args = {'username': username, 'code': code} + raise OperationFailed("KCHAUTH0001E", msg_args) -def authenticate(username, password, service="passwd"): - '''Returns True if authenticate is OK via PAM.''' - def _pam_conv(auth, query_list, userData=None): - resp = [] - for i in range(len(query_list)): - query, qtype = query_list[i] - if qtype == PAM.PAM_PROMPT_ECHO_ON: - resp.append((username, 0)) - elif qtype == PAM.PAM_PROMPT_ECHO_OFF: - resp.append((password, 0)) - elif qtype == PAM.PAM_PROMPT_ERROR_MSG: - cherrypy.log.error_log.error("PAM authenticate prompt error " - "message: %s" % query) - resp.append(('', 0)) - elif qtype == PAM.PAM_PROMPT_TEXT_INFO: - resp.append(('', 0)) - else: - return None - return resp - - auth = PAM.pam() - auth.start(service) - auth.set_item(PAM.PAM_USER, username) - auth.set_item(PAM.PAM_CONV, _pam_conv) - - try: - auth.authenticate() - except PAM.error: - raise - - return True + return True +class LDAPUser(User): + auth_type = "ldap" + def __init__(self, username): + self.user = {} + self.user[USER_NAME] = username + self.user[USER_GROUPS] = None + # FIXME: user roles will be changed according roles assignment after + # objstore is integrated + self.user[USER_ROLES] = dict.fromkeys(tabs, 'user') + + @staticmethod + def authenticate(username, password): + return False + def from_browser(): # Enable Basic Authentication for REST tools. # Ajax request sent from jQuery in browser will have "X-Requested-With" @@ -216,15 +247,15 @@ def check_auth_httpba(): def login(username, password, **kwargs): - try: - if not authenticate(username, password): - debug("User cannot be verified with the supplied password") - return None - except PAM.error, (resp, code): - msg_args = {'username': username, 'code': code} - raise OperationFailed("KCHAUTH0001E", msg_args) - - user = User(username) + auth_args = {'auth_type': config.get("authentication", "method"), + 'username': username, + 'password': password} + + user = User.get(auth_args) + if not user: + debug("User cannot be verified with the supplied password") + return None + debug("User verified, establishing session") cherrypy.session.acquire_lock() cherrypy.session.regenerate() diff --git a/src/kimchi/model/config.py b/src/kimchi/model/config.py index 7479050..6a7df41 100644 --- a/src/kimchi/model/config.py +++ b/src/kimchi/model/config.py @@ -115,6 +115,7 @@ class CapabilitiesModel(object): 'update_tool': update_tool, 'repo_mngt_tool': repo_mngt_tool, 'federation': kconfig.get("server", "federation"), + 'auth': kconfig.get("authentication", "method"), 'kernel_vfio': self.kernel_vfio, } -- 1.8.3.2

From: Royce Lv <lvroyce@linux.vnet.ibm.com> Add LDAP authentication, also deals with invalid user, LDAP search base configure error and other LDAP errors. Signed-off-by: Royce Lv <lvroyce@linux.vnet.ibm.com> --- contrib/DEBIAN/control.in | 1 + contrib/kimchi.spec.fedora.in | 1 + contrib/kimchi.spec.suse.in | 1 + docs/README.md | 36 ++++++++++++++++++--------------- src/kimchi.conf.in | 3 +++ src/kimchi/auth.py | 46 +++++++++++++++++++++++++++++++++++++++++-- src/kimchi/i18n.py | 1 + 7 files changed, 71 insertions(+), 18 deletions(-) diff --git a/contrib/DEBIAN/control.in b/contrib/DEBIAN/control.in index ecddc71..7e0ed8d 100644 --- a/contrib/DEBIAN/control.in +++ b/contrib/DEBIAN/control.in @@ -26,6 +26,7 @@ Depends: python-cherrypy3 (>= 3.2.0), firewalld, nginx, python-guestfs, + python-ldap, libguestfs-tools, spice-html5 Build-Depends: libxslt, diff --git a/contrib/kimchi.spec.fedora.in b/contrib/kimchi.spec.fedora.in index 236c862..c1929f8 100644 --- a/contrib/kimchi.spec.fedora.in +++ b/contrib/kimchi.spec.fedora.in @@ -28,6 +28,7 @@ Requires: nfs-utils Requires: nginx Requires: iscsi-initiator-utils Requires: policycoreutils-python +Requires: python-ldap Requires: python-libguestfs Requires: libguestfs-tools BuildRequires: libxslt diff --git a/contrib/kimchi.spec.suse.in b/contrib/kimchi.spec.suse.in index 96d3b4c..be75590 100644 --- a/contrib/kimchi.spec.suse.in +++ b/contrib/kimchi.spec.suse.in @@ -22,6 +22,7 @@ Requires: python-psutil >= 0.6.0 Requires: python-jsonschema >= 1.3.0 Requires: python-ethtool Requires: python-ipaddr +Requires: python-ldap Requires: python-lxml Requires: python-xml Requires: nfs-client diff --git a/docs/README.md b/docs/README.md index d18a946..de6db91 100644 --- a/docs/README.md +++ b/docs/README.md @@ -49,11 +49,12 @@ Install Dependencies $ sudo yum install gcc make autoconf automake gettext-devel git \ python-cherrypy python-cheetah libvirt-python \ - libvirt python-imaging PyPAM m2crypto \ - python-jsonschema rpm-build qemu-kvm python-psutil \ - python-ethtool sos python-ipaddr python-lxml \ - nfs-utils iscsi-initiator-utils libxslt pyparted \ - nginx policycoreutils-python python-libguestfs \ + libvirt python-imaging \ + PyPAM m2crypto python-jsonschema rpm-build \ + qemu-kvm python-psutil python-ethtool sos \ + python-ipaddr python-ldap python-lxml nfs-utils \ + iscsi-initiator-utils libxslt pyparted nginx \ + policycoreutils-python python-libguestfs \ libguestfs-tools python-requests python-websockify \ novnc @@ -77,11 +78,12 @@ for more information on how to configure your system to access this repository. $ sudo apt-get install gcc make autoconf automake gettext git \ python-cherrypy3 python-cheetah python-libvirt \ - libvirt-bin python-imaging python-pam \ - python-m2crypto python-jsonschema qemu-kvm \ - libtool python-psutil python-ethtool \ - sosreport python-ipaddr python-lxml nfs-common \ - open-iscsi lvm2 xsltproc python-parted nginx \ + libvirt-bin python-imaging \ + python-pam python-m2crypto python-jsonschema \ + qemu-kvm libtool python-psutil python-ethtool \ + sosreport python-ipaddr python-ldap \ + python-lxml nfs-common open-iscsi lvm2 xsltproc \ + python-parted nginx \ firewalld python-guestfs libguestfs-tools \ python-requests websockify novnc spice-html5 @@ -93,12 +95,14 @@ for more information on how to configure your system to access this repository. $ sudo zypper install gcc make autoconf automake gettext-tools git \ python-CherryPy python-Cheetah libvirt-python \ - libvirt python-imaging python-pam python-M2Crypto \ - python-jsonschema rpm-build kvm python-psutil \ - python-ethtool python-ipaddr python-lxml \ - nfs-client open-iscsi libxslt-tools python-xml \ - python-parted nginx python-libguestfs \ - guestfs-tools python-requests python-websockify novnc + libvirt python-imaging \ + python-pam python-M2Crypto python-jsonschema \ + rpm-build kvm python-psutil python-ethtool \ + python-ipaddr python-ldap python-lxml nfs-client \ + open-iscsi libxslt-tools python-xml \ + python-parted nginx \ + python-libguestfs guestfs-tools python-requests \ + python-websockify novnc Packages version requirement: python-psutil >= 0.6.0 diff --git a/src/kimchi.conf.in b/src/kimchi.conf.in index 62eb40b..003ce4a 100644 --- a/src/kimchi.conf.in +++ b/src/kimchi.conf.in @@ -57,3 +57,6 @@ # User id filter # ldap_search_filter = "uid=%(username)s" + +# User IDs regarded as kimchi admin +# ldap_admin_id = "foo@foo.com, bar@bar.com" diff --git a/src/kimchi/auth.py b/src/kimchi/auth.py index 5986094..5abfe18 100644 --- a/src/kimchi/auth.py +++ b/src/kimchi/auth.py @@ -20,6 +20,7 @@ import base64 import cherrypy import fcntl +import ldap import multiprocessing import os import PAM @@ -177,17 +178,58 @@ class PAMUser(User): class LDAPUser(User): auth_type = "ldap" + def __init__(self, username): self.user = {} self.user[USER_NAME] = username - self.user[USER_GROUPS] = None + self.user[USER_GROUPS] = list() # FIXME: user roles will be changed according roles assignment after # objstore is integrated self.user[USER_ROLES] = dict.fromkeys(tabs, 'user') @staticmethod def authenticate(username, password): - return False + ldap_server = config.get("authentication", "ldap_server").strip('"') + ldap_search_base = config.get( + "authentication", "ldap_search_base").strip('"') + ldap_search_filter = config.get( + "authentication", "ldap_search_filter", + vars={"username": username.encode("utf-8")}).strip('"') + + connect = ldap.open(ldap_server) + try: + result = connect.search_s( + ldap_search_base, ldap.SCOPE_SUBTREE, ldap_search_filter) + if len(result) == 0: + entity = ldap_search_filter % {'username': username} + raise ldap.LDAPError("Invalid ldap entity:%s" % entity) + + connect.bind_s(result[0][0], password) + connect.unbind_s() + return True + except ldap.INVALID_CREDENTIALS: + # invalid user password + raise OperationFailed("KCHAUTH0002E") + except ldap.NO_SUCH_OBJECT: + # ldap search base specified wrongly. + raise OperationFailed("KCHAUTH0005E", + {"item": 'ldap_search_base',"value": ldap_search_base}) + except ldap.LDAPError, e: + arg = {"username": username, "code": e.message} + raise OperationFailed("KCHAUTH0001E", arg) + + def get_groups(self): + return self.user[USER_GROUPS] + + def get_roles(self): + admin_id = config.get("authentication", "ldap_admin_id").strip('"') + if self.user[USER_NAME] in admin_id.split(','): + self.user[USER_ROLES] = dict.fromkeys(tabs, 'admin') + return self.user[USER_ROLES] + + def get_user(self): + return self.user + def from_browser(): # Enable Basic Authentication for REST tools. diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py index e823f2b..9f4ae79 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -40,6 +40,7 @@ messages = { "KCHAUTH0001E": _("Authentication failed for user '%(username)s'. [Error code: %(code)s]"), "KCHAUTH0002E": _("You are not authorized to access Kimchi"), "KCHAUTH0003E": _("Specify %(item)s to login into Kimchi"), + "KCHAUTH0005E": _("Invalid LDAP configuration: %(item)s : %(value)s"), "KCHDEVS0001E": _('Unknown "_cap" specified'), "KCHDEVS0002E": _('"_passthrough" should be "true" or "false"'), -- 1.8.3.2

From: Royce Lv <lvroyce@linux.vnet.ibm.com> Authentication function moved to class, so build a fake user class to cover test. Signed-off-by: Royce Lv <lvroyce@linux.vnet.ibm.com> --- tests/utils.py | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/tests/utils.py b/tests/utils.py index 9133904..f846d48 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -35,7 +35,8 @@ from lxml import etree import kimchi.mockmodel import kimchi.server -from kimchi.config import paths +from kimchi.config import config, paths +from kimchi.auth import User, USER_NAME, USER_GROUPS, USER_ROLES, tabs from kimchi.exception import OperationFailed from kimchi.utils import kimchi_log @@ -169,29 +170,43 @@ def get_remote_iso_path(): return remote_path -def patch_auth(sudo=True): - """ - Override the authenticate function with a simple test against an - internal dict of users and passwords. - """ +class FakeUser(User): + auth_type = "fake" + sudo = True + + def __init__(self, username): + self.user = {} + self.user[USER_NAME] = username + self.user[USER_GROUPS] = None + self.user[USER_ROLES] = dict.fromkeys(tabs, 'user') - def _get_groups(self): + def get_groups(self): return ['groupA', 'groupB', 'wheel'] - def _has_sudo(self, result): - result.value = sudo + def get_roles(self): + if self.sudo: + self.user[USER_ROLES] = dict.fromkeys(tabs, 'admin') + return self.user[USER_ROLES] + + def get_user(self): + return self.user - def _authenticate(username, password, service="passwd"): + @staticmethod + def authenticate(username, password, service="passwd"): try: return kimchi.mockmodel.fake_user[username] == password except KeyError, e: raise OperationFailed("KCHAUTH0001E", {'username': 'username', 'code': e.message}) - import kimchi.auth - kimchi.auth.authenticate = _authenticate - kimchi.auth.User.get_groups = _get_groups - kimchi.auth.User._has_sudo = _has_sudo + +def patch_auth(sudo=True): + """ + Override the authenticate function with a simple test against an + internal dict of users and passwords. + """ + config.set("authentication", "method", "fake") + FakeUser.sudo = sudo def normalize_xml(xml_str): -- 1.8.3.2

From: Royce Lv <lvroyce@linux.vnet.ibm.com> Put ldap validation in a single function to resue in authorization. Tested: 1. LDAP: GET /users?_user_id=a_valid_user_id GET /users?_user_id=invalid_user_id GET /groups 2. PAM: GET /users GET /groups Signed-off-by: Royce Lv <lvroyce@linux.vnet.ibm.com> --- docs/API.md | 19 +++++----- src/kimchi/control/auth.py | 42 ++++++++++++++++++++++ src/kimchi/control/host.py | 14 -------- src/kimchi/i18n.py | 1 + src/kimchi/model/groups.py | 68 +++++++++++++++++++++++++++++++++++ src/kimchi/model/host.py | 19 ---------- src/kimchi/model/users.py | 90 ++++++++++++++++++++++++++++++++++++++++++++++ src/kimchi/model/vms.py | 4 +-- tests/test_rest.py | 4 +-- 9 files changed, 216 insertions(+), 45 deletions(-) create mode 100644 src/kimchi/control/auth.py create mode 100644 src/kimchi/model/groups.py create mode 100644 src/kimchi/model/users.py diff --git a/docs/API.md b/docs/API.md index 9bfbf4f..20166fd 100644 --- a/docs/API.md +++ b/docs/API.md @@ -819,23 +819,26 @@ Contains information of host. * swupdate: Start the update of packages in background and return a Task resource * task resource. * See Resource: Task * -### Resource: Host users +### Resource: Users -**URI:** /host/users -List of system users in the host. +**URI:** /users +List of available users. **Methods:** -* **GET**: Retrieve list of system users in the host. +* **GET**: Retrieve list of available users. + * Parameters: + * _user_id: Validate whether user exists. + Essential for 'ldap' authentication. -### Resource: Host groups +### Resource: Groups -**URI:** /host/groups -List of system groups in the host. +**URI:** /groups +List of available groups. **Methods:** -* **GET**: Retrieve list of system groups in the host. +* **GET**: Retrieve list of available groups, only support 'pam' authentication. ### Resource: HostStats diff --git a/src/kimchi/control/auth.py b/src/kimchi/control/auth.py new file mode 100644 index 0000000..ebc019e --- /dev/null +++ b/src/kimchi/control/auth.py @@ -0,0 +1,42 @@ +# +# Project Kimchi +# +# Copyright IBM, Corp. 2013-2014 +# +# This library 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 2.1 of the License, or (at your option) any later version. +# +# This library 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 library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +from kimchi.control.base import SimpleCollection +from kimchi.control.utils import get_class_name, model_fn, UrlSubNode +from kimchi.template import render + + +@UrlSubNode('users', True) +class Users(SimpleCollection): + def __init__(self, model): + super(Users, self).__init__(model) + self.role_key = 'guests' + + def get(self, filter_params): + res_list = [] + get_list = getattr(self.model, model_fn(self, 'get_list')) + res_list = get_list(*self.model_args, **filter_params) + return render(get_class_name(self), res_list) + + +@UrlSubNode('groups', True) +class Groups(SimpleCollection): + def __init__(self, model): + super(Groups, self).__init__(model) + self.role_key = 'guests' diff --git a/src/kimchi/control/host.py b/src/kimchi/control/host.py index 4362da7..5a185e3 100644 --- a/src/kimchi/control/host.py +++ b/src/kimchi/control/host.py @@ -36,8 +36,6 @@ class Host(Resource): self.devices = Devices(self.model) self.packagesupdate = PackagesUpdate(self.model) self.repositories = Repositories(self.model) - self.users = Users(self.model) - self.groups = Groups(self.model) self.swupdate = self.generate_action_handler_task('swupdate') @property @@ -159,15 +157,3 @@ class Repository(Resource): @property def data(self): return self.info - - -class Users(SimpleCollection): - def __init__(self, model): - super(Users, self).__init__(model) - self.role_key = 'guests' - - -class Groups(SimpleCollection): - def __init__(self, model): - super(Groups, self).__init__(model) - self.role_key = 'guests' diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py index 9f4ae79..d6ec10c 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -40,6 +40,7 @@ messages = { "KCHAUTH0001E": _("Authentication failed for user '%(username)s'. [Error code: %(code)s]"), "KCHAUTH0002E": _("You are not authorized to access Kimchi"), "KCHAUTH0003E": _("Specify %(item)s to login into Kimchi"), + "KCHAUTH0004E": _("User %(user_id)s not found with given LDAP settings."), "KCHAUTH0005E": _("Invalid LDAP configuration: %(item)s : %(value)s"), "KCHDEVS0001E": _('Unknown "_cap" specified'), diff --git a/src/kimchi/model/groups.py b/src/kimchi/model/groups.py new file mode 100644 index 0000000..5c45b5e --- /dev/null +++ b/src/kimchi/model/groups.py @@ -0,0 +1,68 @@ +# +# Project Kimchi +# +# Copyright IBM, Corp. 2014 +# +# This library 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 2.1 of the License, or (at your option) any later version. +# +# This library 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 library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +import grp +import pwd + +from kimchi.config import config + + +class GroupsModel(object): + def __init__(self, **args): + auth_type = config.get("authentication", "method") + for klass in GroupsModel.__subclasses__(): + if auth_type == klass.auth_type: + self.grp = klass(**args) + + + def get_list(self, **args): + if hasattr(self.grp, '_get_list'): + return self.grp._get_list(**args) + else: + return list() + + def validate(self, gid): + return self.grp._validate(gid) + + +class PAMGroupsModel(GroupsModel): + auth_type = 'pam' + + def __init__(self, **kargs): + pass + + def _get_list(self): + return [group.gr_name for group in grp.getgrall()] + + def _validate(self, gid): + try: + grp.getgrnam(gid) + except KeyError: + return False + return True + + +class LDAPGroupsModel(GroupsModel): + auth_type = 'ldap' + + def __init__(self, **kargs): + pass + + def _validate(self, gid): + return False diff --git a/src/kimchi/model/host.py b/src/kimchi/model/host.py index 8cddcdc..009f448 100644 --- a/src/kimchi/model/host.py +++ b/src/kimchi/model/host.py @@ -17,12 +17,10 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -import grp import libvirt import os import time import platform -import pwd from collections import defaultdict import psutil @@ -457,20 +455,3 @@ class RepositoryModel(object): raise InvalidOperation('KCHREPOS0014E') return self._repositories.removeRepository(repo_id) - - -class UsersModel(object): - def __init__(self, **kargs): - pass - - def get_list(self): - return [user.pw_name for user in pwd.getpwall() - if user.pw_shell.rsplit("/")[-1] not in ["nologin", "false"]] - - -class GroupsModel(object): - def __init__(self, **kargs): - pass - - def get_list(self): - return [group.gr_name for group in grp.getgrall()] diff --git a/src/kimchi/model/users.py b/src/kimchi/model/users.py new file mode 100644 index 0000000..1422bae --- /dev/null +++ b/src/kimchi/model/users.py @@ -0,0 +1,90 @@ +# +# Project Kimchi +# +# Copyright IBM, Corp. 2014 +# +# This library 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 2.1 of the License, or (at your option) any later version. +# +# This library 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 library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +import ldap +import pwd + +from kimchi.config import config +from kimchi.exception import NotFoundError + + +class UsersModel(object): + def __init__(self, **args): + auth_type = config.get("authentication", "method") + for klass in UsersModel.__subclasses__(): + if auth_type == klass.auth_type: + self.user = klass(**args) + + def get_list(self, **args): + return self.user._get_list(**args) + + def validate(self, user): + return self.user._validate(user) + + +class PAMUsersModel(UsersModel): + auth_type = 'pam' + + def __init__(self, **kargs): + pass + + def _get_list(self): + return [user.pw_name for user in pwd.getpwall() + if user.pw_shell.rsplit("/")[-1] not in ["nologin", "false"]] + + def _validate(self, user): + try: + return user in self._get_list() + except: + return False + + +class LDAPUsersModel(UsersModel): + auth_type = 'ldap' + + def __init__(self, **kargs): + pass + + def _get_list(self, _user_id=''): + return self._get_user(_user_id) + + def _validate(self, user): + try: + self._get_user(user) + return True + except NotFoundError: + return False + + def _get_user(self, _user_id): + ldap_server = config.get("authentication", "ldap_server").strip('"') + ldap_search_base = config.get( + "authentication", "ldap_search_base").strip('"') + ldap_search_filter = config.get( + "authentication", "ldap_search_filter", + vars={"username": _user_id.encode("utf-8")}).strip('"') + + connect = ldap.open(ldap_server) + try: + result = connect.search_s( + ldap_search_base, ldap.SCOPE_SUBTREE, ldap_search_filter) + if len(result) == 0: + raise NotFoundError("KCHAUTH0004E", {'user_id': _user_id}) + return result[0][1] + except ldap.NO_SUCH_OBJECT: + raise NotFoundError("KCHAUTH0004E", {'user_id': _user_id}) diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index d194049..a841dbd 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -261,8 +261,8 @@ class VMModel(object): self.conn = kargs['conn'] self.objstore = kargs['objstore'] self.vmscreenshot = VMScreenshotModel(**kargs) - self.users = import_class('kimchi.model.host.UsersModel')(**kargs) - self.groups = import_class('kimchi.model.host.GroupsModel')(**kargs) + self.users = import_class('kimchi.model.users.UsersModel')(**kargs) + self.groups = import_class('kimchi.model.groups.GroupsModel')(**kargs) self.vms = VMsModel(**kargs) self.task = TaskModel(**kargs) self.storagepool = model.storagepools.StoragePoolModel(**kargs) diff --git a/tests/test_rest.py b/tests/test_rest.py index 6770647..7737953 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -278,7 +278,7 @@ class RestTests(unittest.TestCase): self.assertEquals(params[key], vm[key]) # change only VM users - groups are not changed (default is empty) - resp = self.request('/host/users', '{}', 'GET') + resp = self.request('/users', '{}', 'GET') users = json.loads(resp.read()) req = json.dumps({'users': users}) resp = self.request('/vms/∨м-црdαtеd', req, 'PUT') @@ -287,7 +287,7 @@ class RestTests(unittest.TestCase): self.assertEquals(users, info['users']) # change only VM groups - users are not changed (default is empty) - resp = self.request('/host/groups', '{}', 'GET') + resp = self.request('/groups', '{}', 'GET') groups = json.loads(resp.read()) req = json.dumps({'groups': groups}) resp = self.request('/vms/∨м-црdαtеd', req, 'PUT') -- 1.8.3.2

On 11/14/2014 08:37 AM, lvroyce@linux.vnet.ibm.com wrote:
From: Royce Lv <lvroyce@linux.vnet.ibm.com>
Put ldap validation in a single function to resue in authorization. Tested: 1. LDAP: GET /users?_user_id=a_valid_user_id GET /users?_user_id=invalid_user_id GET /groups 2. PAM: GET /users GET /groups
Signed-off-by: Royce Lv <lvroyce@linux.vnet.ibm.com> --- docs/API.md | 19 +++++----- src/kimchi/control/auth.py | 42 ++++++++++++++++++++++ src/kimchi/control/host.py | 14 -------- src/kimchi/i18n.py | 1 + src/kimchi/model/groups.py | 68 +++++++++++++++++++++++++++++++++++ src/kimchi/model/host.py | 19 ---------- src/kimchi/model/users.py | 90 ++++++++++++++++++++++++++++++++++++++++++++++ src/kimchi/model/vms.py | 4 +-- tests/test_rest.py | 4 +-- 9 files changed, 216 insertions(+), 45 deletions(-) create mode 100644 src/kimchi/control/auth.py create mode 100644 src/kimchi/model/groups.py create mode 100644 src/kimchi/model/users.py
diff --git a/docs/API.md b/docs/API.md index 9bfbf4f..20166fd 100644 --- a/docs/API.md +++ b/docs/API.md @@ -819,23 +819,26 @@ Contains information of host. * swupdate: Start the update of packages in background and return a Task resource * task resource. * See Resource: Task *
-### Resource: Host users +### Resource: Users
-**URI:** /host/users -List of system users in the host. +**URI:** /users +List of available users.
**Methods:**
-* **GET**: Retrieve list of system users in the host. +* **GET**: Retrieve list of available users. + * Parameters: + * _user_id: Validate whether user exists. + Essential for 'ldap' authentication.
-### Resource: Host groups +### Resource: Groups
-**URI:** /host/groups -List of system groups in the host. +**URI:** /groups +List of available groups.
**Methods:**
-* **GET**: Retrieve list of system groups in the host. +* **GET**: Retrieve list of available groups, only support 'pam' authentication.
### Resource: HostStats
diff --git a/src/kimchi/control/auth.py b/src/kimchi/control/auth.py
Split it into 2 files: control/users.py and control/groups.py We are using one file per URI.
new file mode 100644 index 0000000..ebc019e --- /dev/null +++ b/src/kimchi/control/auth.py @@ -0,0 +1,42 @@ +# +# Project Kimchi +# +# Copyright IBM, Corp. 2013-2014 +# +# This library 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 2.1 of the License, or (at your option) any later version. +# +# This library 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 library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +from kimchi.control.base import SimpleCollection +from kimchi.control.utils import get_class_name, model_fn, UrlSubNode +from kimchi.template import render + + +@UrlSubNode('users', True) +class Users(SimpleCollection): + def __init__(self, model): + super(Users, self).__init__(model) + self.role_key = 'guests' + + def get(self, filter_params): + res_list = [] + get_list = getattr(self.model, model_fn(self, 'get_list')) + res_list = get_list(*self.model_args, **filter_params) + return render(get_class_name(self), res_list) + + +@UrlSubNode('groups', True) +class Groups(SimpleCollection): + def __init__(self, model): + super(Groups, self).__init__(model) + self.role_key = 'guests' diff --git a/src/kimchi/control/host.py b/src/kimchi/control/host.py index 4362da7..5a185e3 100644 --- a/src/kimchi/control/host.py +++ b/src/kimchi/control/host.py @@ -36,8 +36,6 @@ class Host(Resource): self.devices = Devices(self.model) self.packagesupdate = PackagesUpdate(self.model) self.repositories = Repositories(self.model) - self.users = Users(self.model) - self.groups = Groups(self.model) self.swupdate = self.generate_action_handler_task('swupdate')
@property @@ -159,15 +157,3 @@ class Repository(Resource): @property def data(self): return self.info - - -class Users(SimpleCollection): - def __init__(self, model): - super(Users, self).__init__(model) - self.role_key = 'guests' - - -class Groups(SimpleCollection): - def __init__(self, model): - super(Groups, self).__init__(model) - self.role_key = 'guests' diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py index 9f4ae79..d6ec10c 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -40,6 +40,7 @@ messages = { "KCHAUTH0001E": _("Authentication failed for user '%(username)s'. [Error code: %(code)s]"), "KCHAUTH0002E": _("You are not authorized to access Kimchi"), "KCHAUTH0003E": _("Specify %(item)s to login into Kimchi"), + "KCHAUTH0004E": _("User %(user_id)s not found with given LDAP settings."), "KCHAUTH0005E": _("Invalid LDAP configuration: %(item)s : %(value)s"),
"KCHDEVS0001E": _('Unknown "_cap" specified'), diff --git a/src/kimchi/model/groups.py b/src/kimchi/model/groups.py new file mode 100644 index 0000000..5c45b5e --- /dev/null +++ b/src/kimchi/model/groups.py @@ -0,0 +1,68 @@ +# +# Project Kimchi +# +# Copyright IBM, Corp. 2014 +# +# This library 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 2.1 of the License, or (at your option) any later version. +# +# This library 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 library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +import grp +import pwd + +from kimchi.config import config + + +class GroupsModel(object): + def __init__(self, **args): + auth_type = config.get("authentication", "method") + for klass in GroupsModel.__subclasses__(): + if auth_type == klass.auth_type: + self.grp = klass(**args) + + + def get_list(self, **args): + if hasattr(self.grp, '_get_list'): + return self.grp._get_list(**args) + else: + return list() + + def validate(self, gid): + return self.grp._validate(gid) + + +class PAMGroupsModel(GroupsModel): + auth_type = 'pam' + + def __init__(self, **kargs): + pass + + def _get_list(self): + return [group.gr_name for group in grp.getgrall()] + + def _validate(self, gid): + try: + grp.getgrnam(gid) + except KeyError: + return False + return True + + +class LDAPGroupsModel(GroupsModel): + auth_type = 'ldap' + + def __init__(self, **kargs): + pass + + def _validate(self, gid): + return False diff --git a/src/kimchi/model/host.py b/src/kimchi/model/host.py index 8cddcdc..009f448 100644 --- a/src/kimchi/model/host.py +++ b/src/kimchi/model/host.py @@ -17,12 +17,10 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-import grp import libvirt import os import time import platform -import pwd from collections import defaultdict
import psutil @@ -457,20 +455,3 @@ class RepositoryModel(object): raise InvalidOperation('KCHREPOS0014E')
return self._repositories.removeRepository(repo_id) - - -class UsersModel(object): - def __init__(self, **kargs): - pass - - def get_list(self): - return [user.pw_name for user in pwd.getpwall() - if user.pw_shell.rsplit("/")[-1] not in ["nologin", "false"]] - - -class GroupsModel(object): - def __init__(self, **kargs): - pass - - def get_list(self): - return [group.gr_name for group in grp.getgrall()] diff --git a/src/kimchi/model/users.py b/src/kimchi/model/users.py new file mode 100644 index 0000000..1422bae --- /dev/null +++ b/src/kimchi/model/users.py @@ -0,0 +1,90 @@ +# +# Project Kimchi +# +# Copyright IBM, Corp. 2014 +# +# This library 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 2.1 of the License, or (at your option) any later version. +# +# This library 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 library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +import ldap +import pwd + +from kimchi.config import config +from kimchi.exception import NotFoundError + + +class UsersModel(object): + def __init__(self, **args): + auth_type = config.get("authentication", "method") + for klass in UsersModel.__subclasses__(): + if auth_type == klass.auth_type: + self.user = klass(**args) + + def get_list(self, **args): + return self.user._get_list(**args) + + def validate(self, user): + return self.user._validate(user) + + +class PAMUsersModel(UsersModel): + auth_type = 'pam' + + def __init__(self, **kargs): + pass + + def _get_list(self): + return [user.pw_name for user in pwd.getpwall() + if user.pw_shell.rsplit("/")[-1] not in ["nologin", "false"]] + + def _validate(self, user): + try: + return user in self._get_list() + except: + return False + + +class LDAPUsersModel(UsersModel): + auth_type = 'ldap' + + def __init__(self, **kargs): + pass + + def _get_list(self, _user_id=''): + return self._get_user(_user_id) + + def _validate(self, user): + try: + self._get_user(user) + return True + except NotFoundError: + return False + + def _get_user(self, _user_id): + ldap_server = config.get("authentication", "ldap_server").strip('"') + ldap_search_base = config.get( + "authentication", "ldap_search_base").strip('"') + ldap_search_filter = config.get( + "authentication", "ldap_search_filter", + vars={"username": _user_id.encode("utf-8")}).strip('"') + + connect = ldap.open(ldap_server) + try: + result = connect.search_s( + ldap_search_base, ldap.SCOPE_SUBTREE, ldap_search_filter) + if len(result) == 0: + raise NotFoundError("KCHAUTH0004E", {'user_id': _user_id}) + return result[0][1] + except ldap.NO_SUCH_OBJECT: + raise NotFoundError("KCHAUTH0004E", {'user_id': _user_id}) diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index d194049..a841dbd 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -261,8 +261,8 @@ class VMModel(object): self.conn = kargs['conn'] self.objstore = kargs['objstore'] self.vmscreenshot = VMScreenshotModel(**kargs) - self.users = import_class('kimchi.model.host.UsersModel')(**kargs) - self.groups = import_class('kimchi.model.host.GroupsModel')(**kargs) + self.users = import_class('kimchi.model.users.UsersModel')(**kargs) + self.groups = import_class('kimchi.model.groups.GroupsModel')(**kargs) self.vms = VMsModel(**kargs) self.task = TaskModel(**kargs) self.storagepool = model.storagepools.StoragePoolModel(**kargs) diff --git a/tests/test_rest.py b/tests/test_rest.py index 6770647..7737953 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -278,7 +278,7 @@ class RestTests(unittest.TestCase): self.assertEquals(params[key], vm[key])
# change only VM users - groups are not changed (default is empty) - resp = self.request('/host/users', '{}', 'GET') + resp = self.request('/users', '{}', 'GET') users = json.loads(resp.read()) req = json.dumps({'users': users}) resp = self.request('/vms/∨м-црdαtеd', req, 'PUT') @@ -287,7 +287,7 @@ class RestTests(unittest.TestCase): self.assertEquals(users, info['users'])
# change only VM groups - users are not changed (default is empty) - resp = self.request('/host/groups', '{}', 'GET') + resp = self.request('/groups', '{}', 'GET') groups = json.loads(resp.read()) req = json.dumps({'groups': groups}) resp = self.request('/vms/∨м-црdαtеd', req, 'PUT')

From: Royce Lv <lvroyce@linux.vnet.ibm.com> Put validation in user and group class instead of validate in metadata update, so that different type of authorization can use their own authentication to validate input value. Signed-off-by: Royce Lv <lvroyce@linux.vnet.ibm.com> --- src/kimchi/model/vms.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index a841dbd..5c3bd30 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -548,16 +548,16 @@ class VMModel(object): users = groups = None if "users" in params: users = params["users"] - invalid_users = set(users) - set(self.users.get_list()) - if len(invalid_users) != 0: - raise InvalidParameter("KCHVM0027E", - {'users': ", ".join(invalid_users)}) + for user in users: + if not self.users.validate(user): + raise InvalidParameter("KCHVM0027E", + {'users': user}) if "groups" in params: groups = params["groups"] - invalid_groups = set(groups) - set(self.groups.get_list()) - if len(invalid_groups) != 0: - raise InvalidParameter("KCHVM0028E", - {'groups': ", ".join(invalid_groups)}) + for group in groups: + if not self.groups.validate(group): + raise InvalidParameter("KCHVM0028E", + {'groups': group}) if users is None and groups is None: return -- 1.8.3.2

From: Royce Lv <lvroyce@linux.vnet.ibm.com> Add authorization type to vm tag, and update set/retrieve access tag accordingly. So that we can switch between different types of authentication. Signed-off-by: Royce Lv <lvroyce@linux.vnet.ibm.com> --- src/kimchi/model/vms.py | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index 5c3bd30..ca07b38 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -31,7 +31,7 @@ import libvirt from cherrypy.process.plugins import BackgroundTask from kimchi import model, vnc -from kimchi.config import READONLY_POOL_TYPE +from kimchi.config import READONLY_POOL_TYPE, config from kimchi.exception import InvalidOperation, InvalidParameter from kimchi.exception import NotFoundError, OperationFailed from kimchi.model.config import CapabilitiesModel @@ -46,6 +46,7 @@ from kimchi.utils import add_task, get_next_clone_name, import_class from kimchi.utils import kimchi_log, run_setfacl_set_attr from kimchi.utils import template_name_from_uri from kimchi.xmlutils.utils import xpath_get_text, xml_item_update +from kimchi.xmlutils.utils import dictize DOM_STATE_MAP = {0: 'nostate', @@ -535,17 +536,21 @@ class VMModel(object): rollback.prependDefer(_rollback_objstore) def _build_access_elem(self, users, groups): - access = E.access() + auth = config.get("authentication", "method") + auth_elem = E.auth(type=auth) for user in users: - access.append(E.user(user)) + auth_elem.append(E.user(user)) for group in groups: - access.append(E.group(group)) + auth_elem.append(E.group(group)) + access = E.access() + access.append(auth_elem) return access def _vm_update_access_metadata(self, dom, params): users = groups = None + old_users = old_groups = list() if "users" in params: users = params["users"] for user in users: @@ -564,8 +569,19 @@ class VMModel(object): access_xml = (get_metadata_node(dom, "access") or """<access></access>""") - old_users = xpath_get_text(access_xml, "/access/user") - old_groups = xpath_get_text(access_xml, "/access/group") + auth = config.get("authentication", "method") + access_info = dictize(access_xml) + auth = config.get("authentication", "method") + if ('auth' in access_info['access'] and + ('type' in access_info['access']['auth'] or + len(access_info['access']['auth']) > 1)): + old_users = xpath_get_text(access_xml, "/access/auth[@type='%s']/user" % auth) + old_groups = xpath_get_text(access_xml, "/access/auth[@type='%s']/group" % auth) + elif auth == 'pam': + # Compatible to old permission tagging + old_users = xpath_get_text(access_xml, "/access/user") + old_groups = xpath_get_text(access_xml, "/access/group") + users = old_users if users is None else users groups = old_groups if groups is None else groups @@ -701,8 +717,18 @@ class VMModel(object): access_xml = (get_metadata_node(dom, "access") or """<access></access>""") - users = xpath_get_text(access_xml, "/access/user") - groups = xpath_get_text(access_xml, "/access/group") + access_info = dictize(access_xml) + auth = config.get("authentication", "method") + users = groups = list() + if ('auth' in access_info['access'] and + ('type' in access_info['access']['auth'] or + len(access_info['access']['auth']) > 1)): + users = xpath_get_text(access_xml, "/access/auth[@type='%s']/user" % auth) + groups = xpath_get_text(access_xml, "/access/auth[@type='%s']/group" % auth) + elif auth == 'pam': + # Compatible to old permission tagging + users = xpath_get_text(access_xml, "/access/user") + groups = xpath_get_text(access_xml, "/access/group") return {'name': name, 'state': state, -- 1.8.3.2

From: Royce Lv <lvroyce@linux.vnet.ibm.com> As test model includes test against pam user/group listing, using pam authentication instead of fake authentication. Signed-off-by: Royce Lv <lvroyce@linux.vnet.ibm.com> --- tests/test_model.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_model.py b/tests/test_model.py index f4d842f..1527735 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -38,7 +38,7 @@ import iso_gen import kimchi.objectstore import utils from kimchi import netinfo -from kimchi.config import paths +from kimchi.config import config, paths from kimchi.exception import ImageFormatError, InvalidOperation from kimchi.exception import InvalidParameter, NotFoundError, OperationFailed from kimchi.iscsi import TargetClient @@ -861,6 +861,7 @@ class ModelTests(unittest.TestCase): 'new-test', params) def test_vm_edit(self): + config.set("authentication", "method", "pam") inst = model.Model(None, objstore_loc=self.tmp_store) -- 1.8.3.2

Royce, I will apply patches 1-4 as they are ready. So you just need to resend patches 5-8. On 11/14/2014 08:37 AM, lvroyce@linux.vnet.ibm.com wrote:
From: Royce Lv <lvroyce@linux.vnet.ibm.com>
v3>v4, Move authentication type report to capabilities. Be back compatible for old access label. Change try/exception logic in ldap authentication. Royce Lv (8): Add configuration of LDAP Split PAM and LDAP authentication Add LDAP authentication Fix test cases for authentication Split users and groups for permission query Move validation to authorizaiton change vm permission tag Update test model to fix user/group listing
contrib/DEBIAN/control.in | 1 + contrib/kimchi.spec.fedora.in | 1 + contrib/kimchi.spec.suse.in | 1 + docs/API.md | 20 +++--- docs/README.md | 36 +++++----- src/kimchi.conf.in | 17 +++++ src/kimchi/auth.py | 155 +++++++++++++++++++++++++++++++----------- src/kimchi/config.py.in | 5 ++ src/kimchi/control/auth.py | 42 ++++++++++++ src/kimchi/control/host.py | 14 ---- src/kimchi/i18n.py | 1 + src/kimchi/model/config.py | 1 + src/kimchi/model/groups.py | 68 ++++++++++++++++++ src/kimchi/model/host.py | 19 ------ src/kimchi/model/users.py | 90 ++++++++++++++++++++++++ src/kimchi/model/vms.py | 62 ++++++++++++----- tests/test_model.py | 3 +- tests/test_rest.py | 4 +- tests/utils.py | 43 ++++++++---- 19 files changed, 451 insertions(+), 132 deletions(-) create mode 100644 src/kimchi/control/auth.py create mode 100644 src/kimchi/model/groups.py create mode 100644 src/kimchi/model/users.py

On 2014年11月18日 00:24, Aline Manera wrote:
Royce, I will apply patches 1-4 as they are ready. So you just need to resend patches 5-8.
So considerate! Thanks, Aline!
On 11/14/2014 08:37 AM, lvroyce@linux.vnet.ibm.com wrote:
From: Royce Lv <lvroyce@linux.vnet.ibm.com>
v3>v4, Move authentication type report to capabilities. Be back compatible for old access label. Change try/exception logic in ldap authentication. Royce Lv (8): Add configuration of LDAP Split PAM and LDAP authentication Add LDAP authentication Fix test cases for authentication Split users and groups for permission query Move validation to authorizaiton change vm permission tag Update test model to fix user/group listing
contrib/DEBIAN/control.in | 1 + contrib/kimchi.spec.fedora.in | 1 + contrib/kimchi.spec.suse.in | 1 + docs/API.md | 20 +++--- docs/README.md | 36 +++++----- src/kimchi.conf.in | 17 +++++ src/kimchi/auth.py | 155 +++++++++++++++++++++++++++++++----------- src/kimchi/config.py.in | 5 ++ src/kimchi/control/auth.py | 42 ++++++++++++ src/kimchi/control/host.py | 14 ---- src/kimchi/i18n.py | 1 + src/kimchi/model/config.py | 1 + src/kimchi/model/groups.py | 68 ++++++++++++++++++ src/kimchi/model/host.py | 19 ------ src/kimchi/model/users.py | 90 ++++++++++++++++++++++++ src/kimchi/model/vms.py | 62 ++++++++++++----- tests/test_model.py | 3 +- tests/test_rest.py | 4 +- tests/utils.py | 43 ++++++++---- 19 files changed, 451 insertions(+), 132 deletions(-) create mode 100644 src/kimchi/control/auth.py create mode 100644 src/kimchi/model/groups.py create mode 100644 src/kimchi/model/users.py
participants (3)
-
Aline Manera
-
lvroyce@linux.vnet.ibm.com
-
Royce Lv