[PATCH 0/5 V3] authorization: Backend changes

From: Aline Manera <alinefm@linux.vnet.ibm.com> V2 -> V3: - Update backend authorization rules to reflect our last discussions - Use .getiterator() instead of .iter() while reading xml files - Get role according tab instead of sudo rights To do that I needed to add a new parameter to UrlSubNode() as Kimchi protects its API URIs and the user role is per tab, I need to know which URIs is used in each tab V1 -> V2: - Add "access" elements to describe role/view for each tab - Return a role map in /login For each tab, a role will be returned. That way we have more flexibility to change user role per tab - Add "access" parameter to VM.lookup() As the user will have full access to the VM assigned to it, return "access=full" for all them Aline Manera (5): authorization: Update authorization rules per API authorization: Update /login to return user roles instead of sudo parameter authorization: Add "access" elements to tabs.xml to describe user view authorization: Add "access" parameter to VM resource authorization: Get role according to tab instead of sudo rights config/ui/tabs.xml | 15 +++++++++++++ plugins/sample/ui/config/tab-ext.xml | 3 +++ src/kimchi/auth.py | 42 +++++++++++++++++++++++++----------- src/kimchi/control/debugreports.py | 2 +- src/kimchi/control/host.py | 2 +- src/kimchi/control/interfaces.py | 2 +- src/kimchi/control/networks.py | 2 +- src/kimchi/control/storagepools.py | 2 +- src/kimchi/control/storageservers.py | 2 +- src/kimchi/control/templates.py | 2 +- src/kimchi/control/utils.py | 4 +++- src/kimchi/control/vms.py | 2 +- src/kimchi/mockmodel.py | 3 ++- src/kimchi/model/vms.py | 3 ++- src/kimchi/server.py | 1 + src/kimchi/utils.py | 15 +++++++++++++ tests/test_authorization.py | 8 +++---- tests/test_mockmodel.py | 3 ++- tests/test_model.py | 3 ++- tests/test_rest.py | 8 +++++++ tests/utils.py | 6 +++--- 21 files changed, 97 insertions(+), 33 deletions(-) -- 1.9.3

From: Aline Manera <alinefm@linux.vnet.ibm.com> Each API must specify which requests methods are exclusive for the admin role. Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- src/kimchi/control/debugreports.py | 2 +- src/kimchi/control/host.py | 2 +- src/kimchi/control/interfaces.py | 2 +- src/kimchi/control/networks.py | 2 +- src/kimchi/control/storagepools.py | 2 +- src/kimchi/control/storageservers.py | 2 +- src/kimchi/control/templates.py | 2 +- tests/test_authorization.py | 8 ++++---- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/kimchi/control/debugreports.py b/src/kimchi/control/debugreports.py index 444cb07..d651eb1 100644 --- a/src/kimchi/control/debugreports.py +++ b/src/kimchi/control/debugreports.py @@ -22,7 +22,7 @@ from kimchi.control.utils import UrlSubNode -@UrlSubNode("debugreports", True, ['GET', 'PUT', 'POST']) +@UrlSubNode("debugreports", True, ['GET', 'PUT', 'POST', 'DELETE']) class DebugReports(AsyncCollection): def __init__(self, model): super(DebugReports, self).__init__(model) diff --git a/src/kimchi/control/host.py b/src/kimchi/control/host.py index ebf1bed..9158565 100644 --- a/src/kimchi/control/host.py +++ b/src/kimchi/control/host.py @@ -25,7 +25,7 @@ from kimchi.template import render -@UrlSubNode("host", True, ['POST']) +@UrlSubNode("host", True, ['GET', 'PUT', 'POST', 'DELETE']) class Host(Resource): def __init__(self, model, id=None): super(Host, self).__init__(model, id) diff --git a/src/kimchi/control/interfaces.py b/src/kimchi/control/interfaces.py index 3f353a9..6ae688d 100644 --- a/src/kimchi/control/interfaces.py +++ b/src/kimchi/control/interfaces.py @@ -21,7 +21,7 @@ from kimchi.control.utils import UrlSubNode -@UrlSubNode("interfaces") +@UrlSubNode("interfaces", True, ['GET']) class Interfaces(Collection): def __init__(self, model): super(Interfaces, self).__init__(model) diff --git a/src/kimchi/control/networks.py b/src/kimchi/control/networks.py index b905891..431a01f 100644 --- a/src/kimchi/control/networks.py +++ b/src/kimchi/control/networks.py @@ -21,7 +21,7 @@ from kimchi.control.utils import UrlSubNode -@UrlSubNode("networks", True, ['POST', 'DELETE']) +@UrlSubNode("networks", True, ['PUT', 'POST', 'DELETE']) class Networks(Collection): def __init__(self, model): super(Networks, self).__init__(model) diff --git a/src/kimchi/control/storagepools.py b/src/kimchi/control/storagepools.py index b75bca0..2adaa30 100644 --- a/src/kimchi/control/storagepools.py +++ b/src/kimchi/control/storagepools.py @@ -28,7 +28,7 @@ from kimchi.control.utils import UrlSubNode -@UrlSubNode("storagepools", True, ['POST', 'DELETE']) +@UrlSubNode("storagepools", True, ['PUT', 'POST', 'DELETE']) class StoragePools(Collection): def __init__(self, model): super(StoragePools, self).__init__(model) diff --git a/src/kimchi/control/storageservers.py b/src/kimchi/control/storageservers.py index 515120f..068f9ae 100644 --- a/src/kimchi/control/storageservers.py +++ b/src/kimchi/control/storageservers.py @@ -22,7 +22,7 @@ from kimchi.control.utils import get_class_name, model_fn, UrlSubNode -@UrlSubNode("storageservers", True) +@UrlSubNode("storageservers", True, ['GET']) class StorageServers(Collection): def __init__(self, model): super(StorageServers, self).__init__(model) diff --git a/src/kimchi/control/templates.py b/src/kimchi/control/templates.py index a535960..7a203a5 100644 --- a/src/kimchi/control/templates.py +++ b/src/kimchi/control/templates.py @@ -21,7 +21,7 @@ from kimchi.control.utils import UrlSubNode -@UrlSubNode("templates", True, ['PUT', 'DELETE']) +@UrlSubNode("templates", True, ['GET', 'PUT', 'POST', 'DELETE']) class Templates(Collection): def __init__(self, model): super(Templates, self).__init__(model) diff --git a/tests/test_authorization.py b/tests/test_authorization.py index 196625e..03f8a88 100644 --- a/tests/test_authorization.py +++ b/tests/test_authorization.py @@ -61,11 +61,11 @@ def setUp(self): def test_nonroot_access(self): # Non-root users can access static host information resp = self.request('/host', '{}', 'GET') - self.assertEquals(200, resp.status) + self.assertEquals(403, resp.status) # Non-root users can access host stats resp = self.request('/host/stats', '{}', 'GET') - self.assertEquals(200, resp.status) + self.assertEquals(403, resp.status) # Non-root users can not reboot/shutdown host system resp = self.request('/host/reboot', '{}', 'POST') @@ -102,10 +102,10 @@ def test_nonroot_access(self): # Non-root users can not update or delete a template # but he can get and create a new one resp = self.request('/templates', '{}', 'GET') - self.assertEquals(200, resp.status) + self.assertEquals(403, resp.status) req = json.dumps({'name': 'test', 'cdrom': '/nonexistent.iso'}) resp = self.request('/templates', req, 'POST') - self.assertEquals(201, resp.status) + self.assertEquals(403, resp.status) resp = self.request('/templates/test', '{}', 'PUT') self.assertEquals(403, resp.status) resp = self.request('/templates/test', '{}', 'DELETE') -- 1.9.3

Reviewed-by: Daniel Barboza <danielhb@linux.vnet.ibm.com> Tested-by: Daniel Barboza <danielhb@linux.vnet.ibm.com> On 07/16/2014 06:52 PM, alinefm@linux.vnet.ibm.com wrote:
From: Aline Manera <alinefm@linux.vnet.ibm.com>
Each API must specify which requests methods are exclusive for the admin role.
Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- src/kimchi/control/debugreports.py | 2 +- src/kimchi/control/host.py | 2 +- src/kimchi/control/interfaces.py | 2 +- src/kimchi/control/networks.py | 2 +- src/kimchi/control/storagepools.py | 2 +- src/kimchi/control/storageservers.py | 2 +- src/kimchi/control/templates.py | 2 +- tests/test_authorization.py | 8 ++++---- 8 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/src/kimchi/control/debugreports.py b/src/kimchi/control/debugreports.py index 444cb07..d651eb1 100644 --- a/src/kimchi/control/debugreports.py +++ b/src/kimchi/control/debugreports.py @@ -22,7 +22,7 @@ from kimchi.control.utils import UrlSubNode
-@UrlSubNode("debugreports", True, ['GET', 'PUT', 'POST']) +@UrlSubNode("debugreports", True, ['GET', 'PUT', 'POST', 'DELETE']) class DebugReports(AsyncCollection): def __init__(self, model): super(DebugReports, self).__init__(model) diff --git a/src/kimchi/control/host.py b/src/kimchi/control/host.py index ebf1bed..9158565 100644 --- a/src/kimchi/control/host.py +++ b/src/kimchi/control/host.py @@ -25,7 +25,7 @@ from kimchi.template import render
-@UrlSubNode("host", True, ['POST']) +@UrlSubNode("host", True, ['GET', 'PUT', 'POST', 'DELETE']) class Host(Resource): def __init__(self, model, id=None): super(Host, self).__init__(model, id) diff --git a/src/kimchi/control/interfaces.py b/src/kimchi/control/interfaces.py index 3f353a9..6ae688d 100644 --- a/src/kimchi/control/interfaces.py +++ b/src/kimchi/control/interfaces.py @@ -21,7 +21,7 @@ from kimchi.control.utils import UrlSubNode
-@UrlSubNode("interfaces") +@UrlSubNode("interfaces", True, ['GET']) class Interfaces(Collection): def __init__(self, model): super(Interfaces, self).__init__(model) diff --git a/src/kimchi/control/networks.py b/src/kimchi/control/networks.py index b905891..431a01f 100644 --- a/src/kimchi/control/networks.py +++ b/src/kimchi/control/networks.py @@ -21,7 +21,7 @@ from kimchi.control.utils import UrlSubNode
-@UrlSubNode("networks", True, ['POST', 'DELETE']) +@UrlSubNode("networks", True, ['PUT', 'POST', 'DELETE']) class Networks(Collection): def __init__(self, model): super(Networks, self).__init__(model) diff --git a/src/kimchi/control/storagepools.py b/src/kimchi/control/storagepools.py index b75bca0..2adaa30 100644 --- a/src/kimchi/control/storagepools.py +++ b/src/kimchi/control/storagepools.py @@ -28,7 +28,7 @@ from kimchi.control.utils import UrlSubNode
-@UrlSubNode("storagepools", True, ['POST', 'DELETE']) +@UrlSubNode("storagepools", True, ['PUT', 'POST', 'DELETE']) class StoragePools(Collection): def __init__(self, model): super(StoragePools, self).__init__(model) diff --git a/src/kimchi/control/storageservers.py b/src/kimchi/control/storageservers.py index 515120f..068f9ae 100644 --- a/src/kimchi/control/storageservers.py +++ b/src/kimchi/control/storageservers.py @@ -22,7 +22,7 @@ from kimchi.control.utils import get_class_name, model_fn, UrlSubNode
-@UrlSubNode("storageservers", True) +@UrlSubNode("storageservers", True, ['GET']) class StorageServers(Collection): def __init__(self, model): super(StorageServers, self).__init__(model) diff --git a/src/kimchi/control/templates.py b/src/kimchi/control/templates.py index a535960..7a203a5 100644 --- a/src/kimchi/control/templates.py +++ b/src/kimchi/control/templates.py @@ -21,7 +21,7 @@ from kimchi.control.utils import UrlSubNode
-@UrlSubNode("templates", True, ['PUT', 'DELETE']) +@UrlSubNode("templates", True, ['GET', 'PUT', 'POST', 'DELETE']) class Templates(Collection): def __init__(self, model): super(Templates, self).__init__(model) diff --git a/tests/test_authorization.py b/tests/test_authorization.py index 196625e..03f8a88 100644 --- a/tests/test_authorization.py +++ b/tests/test_authorization.py @@ -61,11 +61,11 @@ def setUp(self): def test_nonroot_access(self): # Non-root users can access static host information resp = self.request('/host', '{}', 'GET') - self.assertEquals(200, resp.status) + self.assertEquals(403, resp.status)
# Non-root users can access host stats resp = self.request('/host/stats', '{}', 'GET') - self.assertEquals(200, resp.status) + self.assertEquals(403, resp.status)
# Non-root users can not reboot/shutdown host system resp = self.request('/host/reboot', '{}', 'POST') @@ -102,10 +102,10 @@ def test_nonroot_access(self): # Non-root users can not update or delete a template # but he can get and create a new one resp = self.request('/templates', '{}', 'GET') - self.assertEquals(200, resp.status) + self.assertEquals(403, resp.status) req = json.dumps({'name': 'test', 'cdrom': '/nonexistent.iso'}) resp = self.request('/templates', req, 'POST') - self.assertEquals(201, resp.status) + self.assertEquals(403, resp.status) resp = self.request('/templates/test', '{}', 'PUT') self.assertEquals(403, resp.status) resp = self.request('/templates/test', '{}', 'DELETE')

From: Aline Manera <alinefm@linux.vnet.ibm.com> For now, Kimchi just supports 2 types of user roles: 'admin' - user has full control of Kimchi features and 'user' with limited access. But in future the idea is to have more and more roles so it is good to already provide the authorization support with that in mind. That way, instead of only returning if user has or not sudo permissions, the /login API will return the user role for each tab. If the user has sudo permissions he/she will have 'admin' role, otherwise, 'user' role. curl -H "Content-Type: application/json" -H "Accept: application/json" http://localhost:8010/login -d'{"username": "guest", "password": "guest-passwd"}' -X POST { "username": "guest", "roles": {"templates": "user", "host": "user", "storage": "user", "guests": "user", "network": "user"}, "groups": [] } curl -H "Content-Type: application/json" -H "Accept: application/json" http://localhost:8010/login -d'{"username": "sysadmin", "password": "sysadmin-passwd"}' -X POST { "username": "sysadmin", "roles": {"templates": "admin", "host": "admin", "storage": "admin", "guests": "admin", "network": "admin"}, "groups": [] } Having one role per tab, give us more flexibility to assign different roles to user. Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- src/kimchi/auth.py | 28 +++++++++++++++++++++++----- src/kimchi/utils.py | 15 +++++++++++++++ tests/test_rest.py | 8 ++++++++ tests/utils.py | 6 +++--- 4 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/kimchi/auth.py b/src/kimchi/auth.py index 6a4a610..bf048b2 100644 --- a/src/kimchi/auth.py +++ b/src/kimchi/auth.py @@ -32,14 +32,17 @@ from kimchi import template from kimchi.exception import InvalidOperation, OperationFailed -from kimchi.utils import run_command +from kimchi.utils import get_all_tabs, run_command USER_NAME = 'username' USER_GROUPS = 'groups' USER_SUDO = 'sudo' +USER_ROLES = 'roles' REFRESH = 'robot-refresh' +tabs = get_all_tabs() + def redirect_login(): url = "/login.html" @@ -62,22 +65,36 @@ def __init__(self, username): self.user = {} self.user[USER_NAME] = username self.user[USER_GROUPS] = None - self.user[USER_SUDO] = False + # after adding support to change user roles that info should be read + # from a specific objstore and fallback to default only if any entry is + # found + self.user[USER_ROLES] = dict.fromkeys(tabs, 'user') def get_groups(self): self.user[USER_GROUPS] = [g.gr_name for g in grp.getgrall() if self.user[USER_NAME] in g.gr_mem] return self.user[USER_GROUPS] + def get_roles(self): + if self.has_sudo(): + # after adding support to change user roles that info should be + # read from a specific objstore and fallback to default only if + # any entry is found + self.user[USER_ROLES] = dict.fromkeys(tabs, 'admin') + + return self.user[USER_ROLES] + def has_sudo(self): result = multiprocessing.Value('i', 0, lock=False) p = multiprocessing.Process(target=self._has_sudo, args=(result,)) p.start() p.join() - self.user[USER_SUDO] = bool(result.value) - return self.user[USER_SUDO] + + return result.value def _has_sudo(self, result): + result.value = False + _master, slave = pty.openpty() os.setsid() fcntl.ioctl(slave, termios.TIOCSCTTY, 0) @@ -94,7 +111,7 @@ def _has_sudo(self, result): self.user[USER_NAME]]) for line in out.split('\n'): if line and re.search("(ALL)", line): - result.value = 1 + result.value = True debug("User %s can run any command with sudo" % result.value) return @@ -219,6 +236,7 @@ def login(username, password, **kwargs): cherrypy.session[USER_NAME] = username cherrypy.session[USER_GROUPS] = user.get_groups() cherrypy.session[USER_SUDO] = user.has_sudo() + cherrypy.session[USER_ROLES] = user.get_roles() cherrypy.session[REFRESH] = time.time() cherrypy.session.release_lock() return user.get_user() diff --git a/src/kimchi/utils.py b/src/kimchi/utils.py index 97adbf8..a992189 100644 --- a/src/kimchi/utils.py +++ b/src/kimchi/utils.py @@ -27,6 +27,7 @@ import subprocess import traceback import urllib2 +import xml.etree.ElementTree as ET from multiprocessing import Process, Queue from threading import Timer @@ -108,6 +109,20 @@ def get_enabled_plugins(): except (TypeError, KeyError): continue +def get_all_tabs(): + files = [os.path.join(paths.prefix, 'config/ui/tabs.xml')] + + for plugin, _ in get_enabled_plugins(): + files.append(os.path.join(PluginPaths(plugin).ui_dir, + 'config/tab-ext.xml')) + + tabs = [] + for f in files: + root = ET.parse(f) + tabs.extend([t.text.lower() for t in root.getiterator('title')]) + + return tabs + def import_class(class_path): module_name, class_name = class_path.rsplit('.', 1) diff --git a/tests/test_rest.py b/tests/test_rest.py index 694d907..a96ccd4 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -1552,6 +1552,14 @@ def test_auth_session(self): req = json.dumps({'username': user, 'password': pw}) resp = self.request('/login', req, 'POST', hdrs) self.assertEquals(200, resp.status) + + user_info = json.loads(resp.read()) + self.assertEquals(sorted(user_info.keys()), + ['groups', 'roles', 'username']) + roles = user_info['roles'] + for tab, role in roles.iteritems(): + self.assertEquals(role, u'admin') + cookie = resp.getheader('set-cookie') hdrs['Cookie'] = cookie diff --git a/tests/utils.py b/tests/utils.py index fd9b23c..4853b7a 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -157,8 +157,8 @@ def patch_auth(sudo=True): def _get_groups(self): return None - def _has_sudo(self): - return sudo + def _has_sudo(self, result): + result.value = sudo def _authenticate(username, password, service="passwd"): try: @@ -170,7 +170,7 @@ def _authenticate(username, password, service="passwd"): import kimchi.auth kimchi.auth.authenticate = _authenticate kimchi.auth.User.get_groups = _get_groups - kimchi.auth.User.has_sudo = _has_sudo + kimchi.auth.User._has_sudo = _has_sudo def normalize_xml(xml_str): -- 1.9.3

Reviewed-by: Daniel Barboza <danielhb@linux.vnet.ibm.com> Tested-by: Daniel Barboza <danielhb@linux.vnet.ibm.com> On 07/16/2014 06:52 PM, alinefm@linux.vnet.ibm.com wrote:
From: Aline Manera <alinefm@linux.vnet.ibm.com>
For now, Kimchi just supports 2 types of user roles: 'admin' - user has full control of Kimchi features and 'user' with limited access. But in future the idea is to have more and more roles so it is good to already provide the authorization support with that in mind.
That way, instead of only returning if user has or not sudo permissions, the /login API will return the user role for each tab.
If the user has sudo permissions he/she will have 'admin' role, otherwise, 'user' role.
curl -H "Content-Type: application/json" -H "Accept: application/json" http://localhost:8010/login -d'{"username": "guest", "password": "guest-passwd"}' -X POST
{ "username": "guest", "roles": {"templates": "user", "host": "user", "storage": "user", "guests": "user", "network": "user"}, "groups": [] }
curl -H "Content-Type: application/json" -H "Accept: application/json" http://localhost:8010/login -d'{"username": "sysadmin", "password": "sysadmin-passwd"}' -X POST
{ "username": "sysadmin", "roles": {"templates": "admin", "host": "admin", "storage": "admin", "guests": "admin", "network": "admin"}, "groups": [] }
Having one role per tab, give us more flexibility to assign different roles to user.
Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- src/kimchi/auth.py | 28 +++++++++++++++++++++++----- src/kimchi/utils.py | 15 +++++++++++++++ tests/test_rest.py | 8 ++++++++ tests/utils.py | 6 +++--- 4 files changed, 49 insertions(+), 8 deletions(-)
diff --git a/src/kimchi/auth.py b/src/kimchi/auth.py index 6a4a610..bf048b2 100644 --- a/src/kimchi/auth.py +++ b/src/kimchi/auth.py @@ -32,14 +32,17 @@
from kimchi import template from kimchi.exception import InvalidOperation, OperationFailed -from kimchi.utils import run_command +from kimchi.utils import get_all_tabs, run_command
USER_NAME = 'username' USER_GROUPS = 'groups' USER_SUDO = 'sudo' +USER_ROLES = 'roles' REFRESH = 'robot-refresh'
+tabs = get_all_tabs() +
def redirect_login(): url = "/login.html" @@ -62,22 +65,36 @@ def __init__(self, username): self.user = {} self.user[USER_NAME] = username self.user[USER_GROUPS] = None - self.user[USER_SUDO] = False + # after adding support to change user roles that info should be read + # from a specific objstore and fallback to default only if any entry is + # found + self.user[USER_ROLES] = dict.fromkeys(tabs, 'user')
def get_groups(self): self.user[USER_GROUPS] = [g.gr_name for g in grp.getgrall() if self.user[USER_NAME] in g.gr_mem] return self.user[USER_GROUPS]
+ def get_roles(self): + if self.has_sudo(): + # after adding support to change user roles that info should be + # read from a specific objstore and fallback to default only if + # any entry is found + self.user[USER_ROLES] = dict.fromkeys(tabs, 'admin') + + return self.user[USER_ROLES] + def has_sudo(self): result = multiprocessing.Value('i', 0, lock=False) p = multiprocessing.Process(target=self._has_sudo, args=(result,)) p.start() p.join() - self.user[USER_SUDO] = bool(result.value) - return self.user[USER_SUDO] + + return result.value
def _has_sudo(self, result): + result.value = False + _master, slave = pty.openpty() os.setsid() fcntl.ioctl(slave, termios.TIOCSCTTY, 0) @@ -94,7 +111,7 @@ def _has_sudo(self, result): self.user[USER_NAME]]) for line in out.split('\n'): if line and re.search("(ALL)", line): - result.value = 1 + result.value = True debug("User %s can run any command with sudo" % result.value) return @@ -219,6 +236,7 @@ def login(username, password, **kwargs): cherrypy.session[USER_NAME] = username cherrypy.session[USER_GROUPS] = user.get_groups() cherrypy.session[USER_SUDO] = user.has_sudo() + cherrypy.session[USER_ROLES] = user.get_roles() cherrypy.session[REFRESH] = time.time() cherrypy.session.release_lock() return user.get_user() diff --git a/src/kimchi/utils.py b/src/kimchi/utils.py index 97adbf8..a992189 100644 --- a/src/kimchi/utils.py +++ b/src/kimchi/utils.py @@ -27,6 +27,7 @@ import subprocess import traceback import urllib2 +import xml.etree.ElementTree as ET from multiprocessing import Process, Queue from threading import Timer
@@ -108,6 +109,20 @@ def get_enabled_plugins(): except (TypeError, KeyError): continue
+def get_all_tabs(): + files = [os.path.join(paths.prefix, 'config/ui/tabs.xml')] + + for plugin, _ in get_enabled_plugins(): + files.append(os.path.join(PluginPaths(plugin).ui_dir, + 'config/tab-ext.xml')) + + tabs = [] + for f in files: + root = ET.parse(f) + tabs.extend([t.text.lower() for t in root.getiterator('title')]) + + return tabs +
def import_class(class_path): module_name, class_name = class_path.rsplit('.', 1) diff --git a/tests/test_rest.py b/tests/test_rest.py index 694d907..a96ccd4 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -1552,6 +1552,14 @@ def test_auth_session(self): req = json.dumps({'username': user, 'password': pw}) resp = self.request('/login', req, 'POST', hdrs) self.assertEquals(200, resp.status) + + user_info = json.loads(resp.read()) + self.assertEquals(sorted(user_info.keys()), + ['groups', 'roles', 'username']) + roles = user_info['roles'] + for tab, role in roles.iteritems(): + self.assertEquals(role, u'admin') + cookie = resp.getheader('set-cookie') hdrs['Cookie'] = cookie
diff --git a/tests/utils.py b/tests/utils.py index fd9b23c..4853b7a 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -157,8 +157,8 @@ def patch_auth(sudo=True): def _get_groups(self): return None
- def _has_sudo(self): - return sudo + def _has_sudo(self, result): + result.value = sudo
def _authenticate(username, password, service="passwd"): try: @@ -170,7 +170,7 @@ def _authenticate(username, password, service="passwd"): import kimchi.auth kimchi.auth.authenticate = _authenticate kimchi.auth.User.get_groups = _get_groups - kimchi.auth.User.has_sudo = _has_sudo + kimchi.auth.User._has_sudo = _has_sudo
def normalize_xml(xml_str):

From: Aline Manera <alinefm@linux.vnet.ibm.com> Kimchi has 2 user roles: "admin" with full control of Kimchi features and "user" with limited access To describe how each tab should be displayed to user, an "access" element was added to tabs.xml indicating which view mode each role has. <access role="..." mode="..."/> The "mode" attribute values are: - none: do not show the tab; - admin: full instance access; - read-only: read-only access; - byInstance: each resource will have its configuration sent by the backend ("access" parameter); The user will only be able to manage the guests he/she is assigned for, because that the guest tab has 'mode' == by-instance. That way each VM will have a new parameter "access" indicating if user has "full" or "read-only" access to this VM. As a user can edit a guest, he/she may need to know which networks and storage pools are configured, so set network and storage tab 'mode' to read-only. And as user should not perform any operation on host or templates, set their 'mode' attributes to 'none'. Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- config/ui/tabs.xml | 15 +++++++++++++++ plugins/sample/ui/config/tab-ext.xml | 3 +++ 2 files changed, 18 insertions(+) diff --git a/config/ui/tabs.xml b/config/ui/tabs.xml index b045521..f79684c 100644 --- a/config/ui/tabs.xml +++ b/config/ui/tabs.xml @@ -1,22 +1,37 @@ <?xml version="1.0" encoding="utf-8"?> <tabs> <tab> + <access role="admin" mode="admin"/> + <access role="user" mode="none"/> + <title>Host</title> <path>tabs/host.html</path> </tab> <tab> + <access role="admin" mode="admin"/> + <access role="user" mode="byInstance"/> + <title>Guests</title> <path>tabs/guests.html</path> </tab> <tab> + <access role="admin" mode="admin"/> + <access role="user" mode="none"/> + <title>Templates</title> <path>tabs/templates.html</path> </tab> <tab> + <access role="admin" mode="admin"/> + <access role="user" mode="read-only"/> + <title>Storage</title> <path>tabs/storage.html</path> </tab> <tab> + <access role="admin" mode="admin"/> + <access role="user" mode="read-only"/> + <title>Network</title> <path>tabs/network.html</path> </tab> diff --git a/plugins/sample/ui/config/tab-ext.xml b/plugins/sample/ui/config/tab-ext.xml index 8e0b3d3..a1fb1c2 100644 --- a/plugins/sample/ui/config/tab-ext.xml +++ b/plugins/sample/ui/config/tab-ext.xml @@ -1,6 +1,9 @@ <?xml version="1.0" encoding="utf-8"?> <tabs-ext> <tab> + <access role="admin" mode="admin"/> + <access role="user" mode="none"/> + <title>SampleTab</title> <path>plugins/sample/tab.html</path> </tab> -- 1.9.3

Reviewed-by: Daniel Barboza <danielhb@linux.vnet.ibm.com> Tested-by: Daniel Barboza <danielhb@linux.vnet.ibm.com> On 07/16/2014 06:52 PM, alinefm@linux.vnet.ibm.com wrote:
From: Aline Manera <alinefm@linux.vnet.ibm.com>
Kimchi has 2 user roles: "admin" with full control of Kimchi features and "user" with limited access To describe how each tab should be displayed to user, an "access" element was added to tabs.xml indicating which view mode each role has.
<access role="..." mode="..."/>
The "mode" attribute values are:
- none: do not show the tab; - admin: full instance access; - read-only: read-only access; - byInstance: each resource will have its configuration sent by the backend ("access" parameter);
The user will only be able to manage the guests he/she is assigned for, because that the guest tab has 'mode' == by-instance. That way each VM will have a new parameter "access" indicating if user has "full" or "read-only" access to this VM.
As a user can edit a guest, he/she may need to know which networks and storage pools are configured, so set network and storage tab 'mode' to read-only.
And as user should not perform any operation on host or templates, set their 'mode' attributes to 'none'.
Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- config/ui/tabs.xml | 15 +++++++++++++++ plugins/sample/ui/config/tab-ext.xml | 3 +++ 2 files changed, 18 insertions(+)
diff --git a/config/ui/tabs.xml b/config/ui/tabs.xml index b045521..f79684c 100644 --- a/config/ui/tabs.xml +++ b/config/ui/tabs.xml @@ -1,22 +1,37 @@ <?xml version="1.0" encoding="utf-8"?> <tabs> <tab> + <access role="admin" mode="admin"/> + <access role="user" mode="none"/> + <title>Host</title> <path>tabs/host.html</path> </tab> <tab> + <access role="admin" mode="admin"/> + <access role="user" mode="byInstance"/> + <title>Guests</title> <path>tabs/guests.html</path> </tab> <tab> + <access role="admin" mode="admin"/> + <access role="user" mode="none"/> + <title>Templates</title> <path>tabs/templates.html</path> </tab> <tab> + <access role="admin" mode="admin"/> + <access role="user" mode="read-only"/> + <title>Storage</title> <path>tabs/storage.html</path> </tab> <tab> + <access role="admin" mode="admin"/> + <access role="user" mode="read-only"/> + <title>Network</title> <path>tabs/network.html</path> </tab> diff --git a/plugins/sample/ui/config/tab-ext.xml b/plugins/sample/ui/config/tab-ext.xml index 8e0b3d3..a1fb1c2 100644 --- a/plugins/sample/ui/config/tab-ext.xml +++ b/plugins/sample/ui/config/tab-ext.xml @@ -1,6 +1,9 @@ <?xml version="1.0" encoding="utf-8"?> <tabs-ext> <tab> + <access role="admin" mode="admin"/> + <access role="user" mode="none"/> + <title>SampleTab</title> <path>plugins/sample/tab.html</path> </tab>

From: Aline Manera <alinefm@linux.vnet.ibm.com> As the guests tab has by-instance mode when a normal user is logged into Kimchi, each VM resource must specify the user access. By now, if a user has access to a VM he/she will have full access to it so add "access=full". Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- src/kimchi/mockmodel.py | 3 ++- src/kimchi/model/vms.py | 3 ++- tests/test_mockmodel.py | 3 ++- tests/test_model.py | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index d1cec70..0e45d1e 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -1050,7 +1050,8 @@ def __init__(self, uuid, name, template_info): 'graphics': {'type': 'vnc', 'listen': '0.0.0.0', 'port': None}, 'users': ['user1', 'user2', 'root'], - 'groups': ['group1', 'group2', 'admin'] + 'groups': ['group1', 'group2', 'admin'], + 'access': 'full' } self.info['graphics'].update(template_info['graphics']) diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index 17bda04..8c0dcb1 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -395,7 +395,8 @@ def lookup(self, name): "listen": graphics_listen, "port": graphics_port}, 'users': users, - 'groups': groups + 'groups': groups, + 'access': 'full' } def _vm_get_disk_paths(self, dom): diff --git a/tests/test_mockmodel.py b/tests/test_mockmodel.py index d43d37c..e04b740 100644 --- a/tests/test_mockmodel.py +++ b/tests/test_mockmodel.py @@ -138,7 +138,8 @@ def test_vm_info(self): self.assertEquals(u'test', vms[0]) keys = set(('name', 'state', 'stats', 'uuid', 'memory', 'cpus', - 'screenshot', 'icon', 'graphics', 'users', 'groups')) + 'screenshot', 'icon', 'graphics', 'users', 'groups', + 'access')) stats_keys = set(('cpu_utilization', 'net_throughput', 'net_throughput_peak', diff --git a/tests/test_model.py b/tests/test_model.py index 2c77514..30daafa 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -63,7 +63,8 @@ def test_vm_info(self): self.assertEquals('test', vms[0]) keys = set(('name', 'state', 'stats', 'uuid', 'memory', 'cpus', - 'screenshot', 'icon', 'graphics', 'users', 'groups')) + 'screenshot', 'icon', 'graphics', 'users', 'groups', + 'access')) stats_keys = set(('cpu_utilization', 'net_throughput', 'net_throughput_peak', -- 1.9.3

Reviewed-by: Daniel Barboza <danielhb@linux.vnet.ibm.com> Tested-by: Daniel Barboza <danielhb@linux.vnet.ibm.com> On 07/16/2014 06:52 PM, alinefm@linux.vnet.ibm.com wrote:
From: Aline Manera <alinefm@linux.vnet.ibm.com>
As the guests tab has by-instance mode when a normal user is logged into Kimchi, each VM resource must specify the user access. By now, if a user has access to a VM he/she will have full access to it so add "access=full".
Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- src/kimchi/mockmodel.py | 3 ++- src/kimchi/model/vms.py | 3 ++- tests/test_mockmodel.py | 3 ++- tests/test_model.py | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index d1cec70..0e45d1e 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -1050,7 +1050,8 @@ def __init__(self, uuid, name, template_info): 'graphics': {'type': 'vnc', 'listen': '0.0.0.0', 'port': None}, 'users': ['user1', 'user2', 'root'], - 'groups': ['group1', 'group2', 'admin'] + 'groups': ['group1', 'group2', 'admin'], + 'access': 'full' } self.info['graphics'].update(template_info['graphics'])
diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index 17bda04..8c0dcb1 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -395,7 +395,8 @@ def lookup(self, name): "listen": graphics_listen, "port": graphics_port}, 'users': users, - 'groups': groups + 'groups': groups, + 'access': 'full' }
def _vm_get_disk_paths(self, dom): diff --git a/tests/test_mockmodel.py b/tests/test_mockmodel.py index d43d37c..e04b740 100644 --- a/tests/test_mockmodel.py +++ b/tests/test_mockmodel.py @@ -138,7 +138,8 @@ def test_vm_info(self): self.assertEquals(u'test', vms[0])
keys = set(('name', 'state', 'stats', 'uuid', 'memory', 'cpus', - 'screenshot', 'icon', 'graphics', 'users', 'groups')) + 'screenshot', 'icon', 'graphics', 'users', 'groups', + 'access'))
stats_keys = set(('cpu_utilization', 'net_throughput', 'net_throughput_peak', diff --git a/tests/test_model.py b/tests/test_model.py index 2c77514..30daafa 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -63,7 +63,8 @@ def test_vm_info(self): self.assertEquals('test', vms[0])
keys = set(('name', 'state', 'stats', 'uuid', 'memory', 'cpus', - 'screenshot', 'icon', 'graphics', 'users', 'groups')) + 'screenshot', 'icon', 'graphics', 'users', 'groups', + 'access'))
stats_keys = set(('cpu_utilization', 'net_throughput', 'net_throughput_peak',

From: Aline Manera <alinefm@linux.vnet.ibm.com> Kimchi will allow one user role per tab but it protects its resources through API URIs. That way we need to map API URIs per tab to get the user role. Do that by adding a new parameter to UrlSubNode() to describe which tab the URI is used. And then use that information to get the user role for each request. Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- src/kimchi/auth.py | 14 ++++++-------- src/kimchi/control/debugreports.py | 2 +- src/kimchi/control/host.py | 2 +- src/kimchi/control/interfaces.py | 2 +- src/kimchi/control/networks.py | 2 +- src/kimchi/control/storagepools.py | 2 +- src/kimchi/control/storageservers.py | 2 +- src/kimchi/control/templates.py | 2 +- src/kimchi/control/utils.py | 4 +++- src/kimchi/control/vms.py | 2 +- src/kimchi/server.py | 1 + 11 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/kimchi/auth.py b/src/kimchi/auth.py index bf048b2..404bab3 100644 --- a/src/kimchi/auth.py +++ b/src/kimchi/auth.py @@ -37,7 +37,6 @@ USER_NAME = 'username' USER_GROUPS = 'groups' -USER_SUDO = 'sudo' USER_ROLES = 'roles' REFRESH = 'robot-refresh' @@ -235,7 +234,6 @@ def login(username, password, **kwargs): cherrypy.session.regenerate() cherrypy.session[USER_NAME] = username cherrypy.session[USER_GROUPS] = user.get_groups() - cherrypy.session[USER_SUDO] = user.has_sudo() cherrypy.session[USER_ROLES] = user.get_roles() cherrypy.session[REFRESH] = time.time() cherrypy.session.release_lock() @@ -250,26 +248,26 @@ def logout(): cherrypy.lib.sessions.close() -def has_permission(admin_methods): +def has_permission(admin_methods, tab): cherrypy.session.acquire_lock() - has_sudo = cherrypy.session.get(USER_SUDO, None) + role = cherrypy.session.get(USER_ROLES, {}).get(tab, 'user') cherrypy.session.release_lock() return not admin_methods or \ cherrypy.request.method not in admin_methods or \ - (cherrypy.request.method in admin_methods and has_sudo) + (cherrypy.request.method in admin_methods and role == "admin") -def kimchiauth(admin_methods=None): +def kimchiauth(admin_methods=None, tab=None): debug("Entering kimchiauth...") session_missing = cherrypy.session.missing if check_auth_session(): - if not has_permission(admin_methods): + if not has_permission(admin_methods, tab): raise cherrypy.HTTPError(403) return if check_auth_httpba(): - if not has_permission(admin_methods): + if not has_permission(admin_methods, tab): raise cherrypy.HTTPError(403) return diff --git a/src/kimchi/control/debugreports.py b/src/kimchi/control/debugreports.py index d651eb1..f0d5dcf 100644 --- a/src/kimchi/control/debugreports.py +++ b/src/kimchi/control/debugreports.py @@ -22,7 +22,7 @@ from kimchi.control.utils import UrlSubNode -@UrlSubNode("debugreports", True, ['GET', 'PUT', 'POST', 'DELETE']) +@UrlSubNode('debugreports', True, ['GET', 'PUT', 'POST', 'DELETE'], 'host') class DebugReports(AsyncCollection): def __init__(self, model): super(DebugReports, self).__init__(model) diff --git a/src/kimchi/control/host.py b/src/kimchi/control/host.py index 9158565..e1971cc 100644 --- a/src/kimchi/control/host.py +++ b/src/kimchi/control/host.py @@ -25,7 +25,7 @@ from kimchi.template import render -@UrlSubNode("host", True, ['GET', 'PUT', 'POST', 'DELETE']) +@UrlSubNode('host', True, ['GET', 'PUT', 'POST', 'DELETE'], 'host') class Host(Resource): def __init__(self, model, id=None): super(Host, self).__init__(model, id) diff --git a/src/kimchi/control/interfaces.py b/src/kimchi/control/interfaces.py index 6ae688d..4aa77b6 100644 --- a/src/kimchi/control/interfaces.py +++ b/src/kimchi/control/interfaces.py @@ -21,7 +21,7 @@ from kimchi.control.utils import UrlSubNode -@UrlSubNode("interfaces", True, ['GET']) +@UrlSubNode('interfaces', True, ['GET'], 'network') class Interfaces(Collection): def __init__(self, model): super(Interfaces, self).__init__(model) diff --git a/src/kimchi/control/networks.py b/src/kimchi/control/networks.py index 431a01f..6bcc871 100644 --- a/src/kimchi/control/networks.py +++ b/src/kimchi/control/networks.py @@ -21,7 +21,7 @@ from kimchi.control.utils import UrlSubNode -@UrlSubNode("networks", True, ['PUT', 'POST', 'DELETE']) +@UrlSubNode('networks', True, ['PUT', 'POST', 'DELETE'], 'network') class Networks(Collection): def __init__(self, model): super(Networks, self).__init__(model) diff --git a/src/kimchi/control/storagepools.py b/src/kimchi/control/storagepools.py index 2adaa30..8c8b522 100644 --- a/src/kimchi/control/storagepools.py +++ b/src/kimchi/control/storagepools.py @@ -28,7 +28,7 @@ from kimchi.control.utils import UrlSubNode -@UrlSubNode("storagepools", True, ['PUT', 'POST', 'DELETE']) +@UrlSubNode('storagepools', True, ['PUT', 'POST', 'DELETE'], 'storage') class StoragePools(Collection): def __init__(self, model): super(StoragePools, self).__init__(model) diff --git a/src/kimchi/control/storageservers.py b/src/kimchi/control/storageservers.py index 068f9ae..9c7bebc 100644 --- a/src/kimchi/control/storageservers.py +++ b/src/kimchi/control/storageservers.py @@ -22,7 +22,7 @@ from kimchi.control.utils import get_class_name, model_fn, UrlSubNode -@UrlSubNode("storageservers", True, ['GET']) +@UrlSubNode('storageservers', True, ['GET'], 'storage') class StorageServers(Collection): def __init__(self, model): super(StorageServers, self).__init__(model) diff --git a/src/kimchi/control/templates.py b/src/kimchi/control/templates.py index 7a203a5..907929f 100644 --- a/src/kimchi/control/templates.py +++ b/src/kimchi/control/templates.py @@ -21,7 +21,7 @@ from kimchi.control.utils import UrlSubNode -@UrlSubNode("templates", True, ['GET', 'PUT', 'POST', 'DELETE']) +@UrlSubNode('templates', True, ['GET', 'PUT', 'POST', 'DELETE'], 'templates') class Templates(Collection): def __init__(self, model): super(Templates, self).__init__(model) diff --git a/src/kimchi/control/utils.py b/src/kimchi/control/utils.py index 5154910..aa592ef 100644 --- a/src/kimchi/control/utils.py +++ b/src/kimchi/control/utils.py @@ -107,18 +107,20 @@ def validate_params(params, instance, action): class UrlSubNode(object): - def __init__(self, name, auth=False, admin_methods=None): + def __init__(self, name, auth=False, admin_methods=None, tab=None): """ admin_methods must be None, or a list containing zero or more of the string values ['GET', 'POST', 'PUT', 'DELETE'] """ self.name = name self.auth = auth + self.tab = tab self.admin_methods = admin_methods def __call__(self, fun): fun._url_sub_node_name = {"name": self.name} fun.url_auth = self.auth + fun.tab = self.tab fun.admin_methods = self.admin_methods return fun diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py index 508f478..cf427fa 100644 --- a/src/kimchi/control/vms.py +++ b/src/kimchi/control/vms.py @@ -22,7 +22,7 @@ from kimchi.control.vm import sub_nodes -@UrlSubNode("vms", True, ['POST', 'PUT', 'DELETE']) +@UrlSubNode('vms', True, ['POST', 'PUT', 'DELETE'], 'guests') class VMs(Collection): def __init__(self, model): super(VMs, self).__init__(model) diff --git a/src/kimchi/server.py b/src/kimchi/server.py index 7344349..b0e9474 100644 --- a/src/kimchi/server.py +++ b/src/kimchi/server.py @@ -130,6 +130,7 @@ def __init__(self, options): ident = "/%s" % ident cfg[ident] = {'tools.kimchiauth.on': True} if node.admin_methods: + cfg[ident]['tools.kimchiauth.tab'] = node.tab cfg[ident][ 'tools.kimchiauth.admin_methods'] = node.admin_methods -- 1.9.3

Reviewed-by: Daniel Barboza <danielhb@linux.vnet.ibm.com> Tested-by: Daniel Barboza <danielhb@linux.vnet.ibm.com> On 07/16/2014 06:52 PM, alinefm@linux.vnet.ibm.com wrote:
From: Aline Manera <alinefm@linux.vnet.ibm.com>
Kimchi will allow one user role per tab but it protects its resources through API URIs. That way we need to map API URIs per tab to get the user role. Do that by adding a new parameter to UrlSubNode() to describe which tab the URI is used. And then use that information to get the user role for each request.
Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- src/kimchi/auth.py | 14 ++++++-------- src/kimchi/control/debugreports.py | 2 +- src/kimchi/control/host.py | 2 +- src/kimchi/control/interfaces.py | 2 +- src/kimchi/control/networks.py | 2 +- src/kimchi/control/storagepools.py | 2 +- src/kimchi/control/storageservers.py | 2 +- src/kimchi/control/templates.py | 2 +- src/kimchi/control/utils.py | 4 +++- src/kimchi/control/vms.py | 2 +- src/kimchi/server.py | 1 + 11 files changed, 18 insertions(+), 17 deletions(-)
diff --git a/src/kimchi/auth.py b/src/kimchi/auth.py index bf048b2..404bab3 100644 --- a/src/kimchi/auth.py +++ b/src/kimchi/auth.py @@ -37,7 +37,6 @@
USER_NAME = 'username' USER_GROUPS = 'groups' -USER_SUDO = 'sudo' USER_ROLES = 'roles' REFRESH = 'robot-refresh'
@@ -235,7 +234,6 @@ def login(username, password, **kwargs): cherrypy.session.regenerate() cherrypy.session[USER_NAME] = username cherrypy.session[USER_GROUPS] = user.get_groups() - cherrypy.session[USER_SUDO] = user.has_sudo() cherrypy.session[USER_ROLES] = user.get_roles() cherrypy.session[REFRESH] = time.time() cherrypy.session.release_lock() @@ -250,26 +248,26 @@ def logout(): cherrypy.lib.sessions.close()
-def has_permission(admin_methods): +def has_permission(admin_methods, tab): cherrypy.session.acquire_lock() - has_sudo = cherrypy.session.get(USER_SUDO, None) + role = cherrypy.session.get(USER_ROLES, {}).get(tab, 'user') cherrypy.session.release_lock()
return not admin_methods or \ cherrypy.request.method not in admin_methods or \ - (cherrypy.request.method in admin_methods and has_sudo) + (cherrypy.request.method in admin_methods and role == "admin")
-def kimchiauth(admin_methods=None): +def kimchiauth(admin_methods=None, tab=None): debug("Entering kimchiauth...") session_missing = cherrypy.session.missing if check_auth_session(): - if not has_permission(admin_methods): + if not has_permission(admin_methods, tab): raise cherrypy.HTTPError(403) return
if check_auth_httpba(): - if not has_permission(admin_methods): + if not has_permission(admin_methods, tab): raise cherrypy.HTTPError(403) return
diff --git a/src/kimchi/control/debugreports.py b/src/kimchi/control/debugreports.py index d651eb1..f0d5dcf 100644 --- a/src/kimchi/control/debugreports.py +++ b/src/kimchi/control/debugreports.py @@ -22,7 +22,7 @@ from kimchi.control.utils import UrlSubNode
-@UrlSubNode("debugreports", True, ['GET', 'PUT', 'POST', 'DELETE']) +@UrlSubNode('debugreports', True, ['GET', 'PUT', 'POST', 'DELETE'], 'host') class DebugReports(AsyncCollection): def __init__(self, model): super(DebugReports, self).__init__(model) diff --git a/src/kimchi/control/host.py b/src/kimchi/control/host.py index 9158565..e1971cc 100644 --- a/src/kimchi/control/host.py +++ b/src/kimchi/control/host.py @@ -25,7 +25,7 @@ from kimchi.template import render
-@UrlSubNode("host", True, ['GET', 'PUT', 'POST', 'DELETE']) +@UrlSubNode('host', True, ['GET', 'PUT', 'POST', 'DELETE'], 'host') class Host(Resource): def __init__(self, model, id=None): super(Host, self).__init__(model, id) diff --git a/src/kimchi/control/interfaces.py b/src/kimchi/control/interfaces.py index 6ae688d..4aa77b6 100644 --- a/src/kimchi/control/interfaces.py +++ b/src/kimchi/control/interfaces.py @@ -21,7 +21,7 @@ from kimchi.control.utils import UrlSubNode
-@UrlSubNode("interfaces", True, ['GET']) +@UrlSubNode('interfaces', True, ['GET'], 'network') class Interfaces(Collection): def __init__(self, model): super(Interfaces, self).__init__(model) diff --git a/src/kimchi/control/networks.py b/src/kimchi/control/networks.py index 431a01f..6bcc871 100644 --- a/src/kimchi/control/networks.py +++ b/src/kimchi/control/networks.py @@ -21,7 +21,7 @@ from kimchi.control.utils import UrlSubNode
-@UrlSubNode("networks", True, ['PUT', 'POST', 'DELETE']) +@UrlSubNode('networks', True, ['PUT', 'POST', 'DELETE'], 'network') class Networks(Collection): def __init__(self, model): super(Networks, self).__init__(model) diff --git a/src/kimchi/control/storagepools.py b/src/kimchi/control/storagepools.py index 2adaa30..8c8b522 100644 --- a/src/kimchi/control/storagepools.py +++ b/src/kimchi/control/storagepools.py @@ -28,7 +28,7 @@ from kimchi.control.utils import UrlSubNode
-@UrlSubNode("storagepools", True, ['PUT', 'POST', 'DELETE']) +@UrlSubNode('storagepools', True, ['PUT', 'POST', 'DELETE'], 'storage') class StoragePools(Collection): def __init__(self, model): super(StoragePools, self).__init__(model) diff --git a/src/kimchi/control/storageservers.py b/src/kimchi/control/storageservers.py index 068f9ae..9c7bebc 100644 --- a/src/kimchi/control/storageservers.py +++ b/src/kimchi/control/storageservers.py @@ -22,7 +22,7 @@ from kimchi.control.utils import get_class_name, model_fn, UrlSubNode
-@UrlSubNode("storageservers", True, ['GET']) +@UrlSubNode('storageservers', True, ['GET'], 'storage') class StorageServers(Collection): def __init__(self, model): super(StorageServers, self).__init__(model) diff --git a/src/kimchi/control/templates.py b/src/kimchi/control/templates.py index 7a203a5..907929f 100644 --- a/src/kimchi/control/templates.py +++ b/src/kimchi/control/templates.py @@ -21,7 +21,7 @@ from kimchi.control.utils import UrlSubNode
-@UrlSubNode("templates", True, ['GET', 'PUT', 'POST', 'DELETE']) +@UrlSubNode('templates', True, ['GET', 'PUT', 'POST', 'DELETE'], 'templates') class Templates(Collection): def __init__(self, model): super(Templates, self).__init__(model) diff --git a/src/kimchi/control/utils.py b/src/kimchi/control/utils.py index 5154910..aa592ef 100644 --- a/src/kimchi/control/utils.py +++ b/src/kimchi/control/utils.py @@ -107,18 +107,20 @@ def validate_params(params, instance, action):
class UrlSubNode(object):
- def __init__(self, name, auth=False, admin_methods=None): + def __init__(self, name, auth=False, admin_methods=None, tab=None): """ admin_methods must be None, or a list containing zero or more of the string values ['GET', 'POST', 'PUT', 'DELETE'] """ self.name = name self.auth = auth + self.tab = tab self.admin_methods = admin_methods
def __call__(self, fun): fun._url_sub_node_name = {"name": self.name} fun.url_auth = self.auth + fun.tab = self.tab fun.admin_methods = self.admin_methods return fun
diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py index 508f478..cf427fa 100644 --- a/src/kimchi/control/vms.py +++ b/src/kimchi/control/vms.py @@ -22,7 +22,7 @@ from kimchi.control.vm import sub_nodes
-@UrlSubNode("vms", True, ['POST', 'PUT', 'DELETE']) +@UrlSubNode('vms', True, ['POST', 'PUT', 'DELETE'], 'guests') class VMs(Collection): def __init__(self, model): super(VMs, self).__init__(model) diff --git a/src/kimchi/server.py b/src/kimchi/server.py index 7344349..b0e9474 100644 --- a/src/kimchi/server.py +++ b/src/kimchi/server.py @@ -130,6 +130,7 @@ def __init__(self, options): ident = "/%s" % ident cfg[ident] = {'tools.kimchiauth.on': True} if node.admin_methods: + cfg[ident]['tools.kimchiauth.tab'] = node.tab cfg[ident][ 'tools.kimchiauth.admin_methods'] = node.admin_methods
participants (3)
-
Aline Manera
-
alinefm@linux.vnet.ibm.com
-
Daniel H Barboza