[Kimchi-devel] [PATCHv4 5/8] Split users and groups for permission query

Aline Manera alinefm at linux.vnet.ibm.com
Mon Nov 17 15:48:35 UTC 2014


On 11/14/2014 08:37 AM, lvroyce at linux.vnet.ibm.com wrote:
> 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/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
> +
> +
> + 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)
> +
> +
> + 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/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')




More information about the Kimchi-devel mailing list