
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> --- src/kimchi/control/auth.py | 42 ++++++++++++++ src/kimchi/control/host.py | 14 ----- src/kimchi/i18n.py | 1 + src/kimchi/model/auth.py | 136 +++++++++++++++++++++++++++++++++++++++++++++ src/kimchi/model/host.py | 19 ------- src/kimchi/model/vms.py | 4 +- tests/test_rest.py | 4 +- 7 files changed, 183 insertions(+), 37 deletions(-) create mode 100644 src/kimchi/control/auth.py create mode 100644 src/kimchi/model/auth.py 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 f789259..4e5a59b 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -39,6 +39,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."), "KCHDEVS0001E": _('Unknown "_cap" specified'), "KCHDEVS0002E": _('"_passthrough" should be "true" or "false"'), diff --git a/src/kimchi/model/auth.py b/src/kimchi/model/auth.py new file mode 100644 index 0000000..d52c919 --- /dev/null +++ b/src/kimchi/model/auth.py @@ -0,0 +1,136 @@ +# +# 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 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}) + + +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/vms.py b/src/kimchi/model/vms.py index f2e4ae3..8f14f9e 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -249,8 +249,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.auth.UsersModel')(**kargs) + self.groups = import_class('kimchi.model.auth.GroupsModel')(**kargs) def update(self, name, params): dom = self.get_vm(name, self.conn) diff --git a/tests/test_rest.py b/tests/test_rest.py index 9bc930f..420bfd0 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -277,7 +277,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') @@ -286,7 +286,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