[Kimchi-devel] [PATCHv5 1/4] Split users and groups for permission query

lvroyce0210 at gmail.com lvroyce0210 at gmail.com
Tue Nov 18 14:26:12 UTC 2014


From: Royce Lv <lvroyce at 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 at linux.vnet.ibm.com>
---
 docs/API.md                  | 19 ++++++----
 src/kimchi/control/groups.py | 28 ++++++++++++++
 src/kimchi/control/host.py   | 14 -------
 src/kimchi/control/users.py  | 35 +++++++++++++++++
 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 +-
 10 files changed, 237 insertions(+), 45 deletions(-)
 create mode 100644 src/kimchi/control/groups.py
 create mode 100644 src/kimchi/control/users.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 6c36bb1..2c6aa44 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -846,23 +846,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/groups.py b/src/kimchi/control/groups.py
new file mode 100644
index 0000000..f18b2ab
--- /dev/null
+++ b/src/kimchi/control/groups.py
@@ -0,0 +1,28 @@
+#
+# 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 UrlSubNode
+
+
+ at 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/control/users.py b/src/kimchi/control/users.py
new file mode 100644
index 0000000..45603d0
--- /dev/null
+++ b/src/kimchi/control/users.py
@@ -0,0 +1,35 @@
+#
+# 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
+
+
+ at 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)
diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py
index 8f6b67e..3d925b3 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 3b43b95..dff89cb 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
@@ -458,20 +456,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 d861157..7ad5858 100644
--- a/src/kimchi/model/vms.py
+++ b/src/kimchi/model/vms.py
@@ -263,8 +263,8 @@ class VMModel(object):
         self.objstore = kargs['objstore']
         self.caps = CapabilitiesModel(**kargs)
         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 0f6cd6d..2e5cff1 100644
--- a/tests/test_rest.py
+++ b/tests/test_rest.py
@@ -282,7 +282,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')
@@ -291,7 +291,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




More information about the Kimchi-devel mailing list