From 2435f589b99574dc8038c12704e163987c9800ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20G=C3=B3mez=20Garc=C3=ADa?= Date: Thu, 5 Mar 2015 15:20:46 +0100 Subject: [PATCH] Needs more testing, but permission delegation seems to work right now. --- server/src/uds/REST/__init__.py | 18 +- server/src/uds/REST/handlers.py | 11 + server/src/uds/REST/methods/authenticators.py | 7 +- server/src/uds/REST/methods/networks.py | 2 + server/src/uds/REST/methods/osmanagers.py | 8 +- server/src/uds/REST/methods/permissions.py | 57 +- server/src/uds/REST/methods/providers.py | 14 +- server/src/uds/REST/methods/services.py | 39 +- server/src/uds/REST/methods/services_pools.py | 7 +- server/src/uds/REST/methods/transports.py | 2 + server/src/uds/REST/model.py | 66 +- server/src/uds/core/util/ot.py | 2 +- server/src/uds/core/util/permissions.py | 33 +- server/src/uds/models/Permissions.py | 8 +- server/src/uds/static/adm/js/api.coffee | 25 +- .../static/adm/js/gui-d-authenticators.coffee | 5 +- .../static/adm/js/gui-d-connectivity.coffee | 2 + .../uds/static/adm/js/gui-d-osmanagers.coffee | 1 + .../uds/static/adm/js/gui-d-services.coffee | 8 +- .../static/adm/js/gui-d-servicespools.coffee | 681 +++++++++--------- .../src/uds/static/adm/js/gui-element.coffee | 134 ++-- .../uds/static/adm/js/gui-permissions.coffee | 63 +- .../templates/uds/admin/tmpl/permissions.html | 4 +- .../uds/admin/tmpl/permissions_add.html | 5 +- 24 files changed, 663 insertions(+), 539 deletions(-) diff --git a/server/src/uds/REST/__init__.py b/server/src/uds/REST/__init__.py index 31ec5baac..ba2cce506 100644 --- a/server/src/uds/REST/__init__.py +++ b/server/src/uds/REST/__init__.py @@ -37,11 +37,13 @@ from django.views.decorators.csrf import csrf_exempt from django.utils.decorators import method_decorator from django.utils.translation import ugettext as _, activate from django.conf import settings -from uds.REST.handlers import Handler, HandlerError, AccessDenied, NotFound, RequestError, ResponseError +from uds.REST.handlers import Handler, HandlerError, AccessDenied, NotFound, RequestError, ResponseError, NotSupportedError import time import logging +import six + logger = logging.getLogger(__name__) __all__ = [str(v) for v in ['Handler', 'Dispatcher']] @@ -142,18 +144,20 @@ class Dispatcher(View): response[k] = val return response except RequestError as e: - return http.HttpResponseBadRequest(unicode(e)) + return http.HttpResponseBadRequest(six.text_type(e)) except ResponseError as e: - return http.HttpResponseServerError(unicode(e)) + return http.HttpResponseServerError(six.text_type(e)) + except NotSupportedError as e: + return http.HttpResponseBadRequest(six.text_type(e)) except AccessDenied as e: - return http.HttpResponseForbidden(unicode(e)) + return http.HttpResponseForbidden(six.text_type(e)) except NotFound as e: - return http.HttpResponseNotFound(unicode(e)) + return http.HttpResponseNotFound(six.text_type(e)) except HandlerError as e: - return http.HttpResponseBadRequest(unicode(e)) + return http.HttpResponseBadRequest(six.text_type(e)) except Exception as e: logger.exception('Error processing request') - return http.HttpResponseServerError(unicode(e)) + return http.HttpResponseServerError(six.text_type(e)) @staticmethod def registerSubclasses(classes): diff --git a/server/src/uds/REST/handlers.py b/server/src/uds/REST/handlers.py index 542669461..612de13cd 100644 --- a/server/src/uds/REST/handlers.py +++ b/server/src/uds/REST/handlers.py @@ -82,6 +82,13 @@ class ResponseError(HandlerError): pass +class NotSupportedError(HandlerError): + ''' + Some elements do not support some operations (as searching over an authenticator that does not supports it) + ''' + pass + + class Handler(object): ''' REST requests handler base class @@ -110,6 +117,7 @@ class Handler(object): self._kwargs = kwargs self._headers = {} self._authToken = None + self._user = None if self.authenticated: # Only retrieve auth related data on authenticated handlers try: self._authToken = self._request.META.get(AUTH_TOKEN_HEADER, '') @@ -129,6 +137,8 @@ class Handler(object): if self.needs_staff and not self.getValue('staff_member'): raise AccessDenied() + self._user = self.getUser() + def headers(self): ''' Returns the headers of the REST request (all) @@ -253,6 +263,7 @@ class Handler(object): ''' If user is staff member, returns his Associated user on auth ''' + logger.debug('REST : {}'.format(self._session)) authId = self.getValue('auth') username = self.getValue('username') # Maybe it's root user?? diff --git a/server/src/uds/REST/methods/authenticators.py b/server/src/uds/REST/methods/authenticators.py index ee57be104..c31b5c8ef 100644 --- a/server/src/uds/REST/methods/authenticators.py +++ b/server/src/uds/REST/methods/authenticators.py @@ -39,6 +39,7 @@ from uds.core import auths from users_groups import Users, Groups from uds.REST import NotFound from uds.REST.model import ModelHandler +from uds.core.util import permissions import logging @@ -93,10 +94,12 @@ class Authenticators(ModelHandler): 'small_name': auth.small_name, 'users_count': auth.users.count(), 'type': type_.type(), + 'permission': permissions.getEffectivePermission(self._user, auth) } # Custom "search" method def search(self, item): + self.ensureAccess(item, permissions.PERMISSION_READ) try: type_ = self._params['type'] if type_ not in ('user', 'group'): @@ -108,7 +111,7 @@ class Authenticators(ModelHandler): canDoSearch = type_ == 'user' and (auth.searchUsers != auths.Authenticator.searchUsers) or (auth.searchGroups != auths.Authenticator.searchGroups) if canDoSearch is False: - self.invalidRequestException() + self.notSupported() if type_ == 'user': return auth.searchUsers(term) @@ -121,6 +124,8 @@ class Authenticators(ModelHandler): from uds.core.Environment import Environment authType = auths.factory().lookup(type_) + self.ensureAccess(authType, permissions.PERMISSION_MANAGEMENT, root=True) + dct = self._params.copy() dct['_request'] = self._request res = authType.test(Environment.getTempEnv(), dct) diff --git a/server/src/uds/REST/methods/networks.py b/server/src/uds/REST/methods/networks.py index 9388d23e0..d1dfcc493 100644 --- a/server/src/uds/REST/methods/networks.py +++ b/server/src/uds/REST/methods/networks.py @@ -35,6 +35,7 @@ from __future__ import unicode_literals from django.utils.translation import ugettext_lazy as _, ugettext from uds.models import Network from uds.core.util import net +from uds.core.util import permissions from uds.core.ui.UserInterface import gui from uds.REST.model import ModelHandler, SaveException @@ -89,4 +90,5 @@ class Networks(ModelHandler): 'name': item.name, 'net_string': item.net_string, 'networks_count': item.transports.count(), + 'permission': permissions.getEffectivePermission(self._user, item) } diff --git a/server/src/uds/REST/methods/osmanagers.py b/server/src/uds/REST/methods/osmanagers.py index 886b7a677..41b1baa3e 100644 --- a/server/src/uds/REST/methods/osmanagers.py +++ b/server/src/uds/REST/methods/osmanagers.py @@ -33,8 +33,8 @@ from __future__ import unicode_literals from django.utils.translation import ugettext, ugettext_lazy as _ -from django.conf import settings from uds.models import OSManager +from uds.core.util import permissions from uds.REST import NotFound, RequestError from uds.core.osmanagers import factory @@ -59,8 +59,7 @@ class OsManagers(ModelHandler): {'deployed_count': {'title': _('Used by'), 'type': 'numeric', 'width': '8em'}} ] - @staticmethod - def osmToDict(osm): + def osmToDict(self, osm): type_ = osm.getType() return { 'id': osm.uuid, @@ -68,10 +67,11 @@ class OsManagers(ModelHandler): 'deployed_count': osm.deployedServices.count(), 'type': type_.type(), 'comments': osm.comments, + 'permission': permissions.getEffectivePermission(self._user, osm) } def item_as_dict(self, item): - return OsManagers.osmToDict(item) + return self.osmToDict(item) def checkDelete(self, item): if item.deployedServices.count() > 0: diff --git a/server/src/uds/REST/methods/permissions.py b/server/src/uds/REST/methods/permissions.py index 957a18e76..ba3514d76 100644 --- a/server/src/uds/REST/methods/permissions.py +++ b/server/src/uds/REST/methods/permissions.py @@ -38,7 +38,7 @@ from uds.REST import Handler from uds.REST import RequestError from uds.core.util import permissions -from uds.models import Provider, Service, Authenticator, OSManager, Transport, Network, ServicesPool +from uds.models import Provider, Service, Authenticator, OSManager, Transport, Network, ServicePool from uds.models import User, Group import six @@ -64,7 +64,7 @@ class Permissions(Handler): 'osmanagers': OSManager, 'transports': Transport, 'networks': Network, - 'servicespools': ServicesPool + 'servicespools': ServicePool }.get(arg, None) if cls is None: @@ -84,16 +84,17 @@ class Permissions(Handler): entity = perm.user res.append({ + 'id': perm.uuid, 'type': kind, 'auth': entity.manager.uuid, 'auth_name': entity.manager.name, - 'id': entity.uuid, - 'name': entity.name, + 'entity_id': entity.uuid, + 'entity_name': entity.name, 'perm': perm.permission, 'perm_name': perm.permission_as_string }) - return sorted(res, key=lambda v: v['auth_name'] + v['name']) + return sorted(res, key=lambda v: v['auth_name'] + v['entity_name']) def get(self): ''' @@ -115,28 +116,36 @@ class Permissions(Handler): ''' Processes post requests ''' - if len(self._args) != 4: - raise RequestError('Invalid request') - logger.debug('Put args: {}'.format(self._args)) - perm = { - '0': permissions.PERMISSION_NONE, - '1': permissions.PERMISSION_READ, - '2': permissions.PERMISSION_ALL - }.get(self._params.get('perm', '0'), permissions.PERMISSION_NONE) + la = len(self._args) - cls = Permissions.getClass(self._args[0]) + if la == 5 and self._args[3] == 'add': + perm = { + '0': permissions.PERMISSION_NONE, + '1': permissions.PERMISSION_READ, + '2': permissions.PERMISSION_MANAGEMENT, + '3': permissions.PERMISSION_ALL + }.get(self._params.get('perm', '0'), permissions.PERMISSION_NONE) - obj = cls.objects.get(uuid=self._args[1]) + cls = Permissions.getClass(self._args[0]) - if self._args[2] == 'users': - user = User.objects.get(uuid=self._args[3]) - permissions.addUserPermission(user, obj, perm) - elif self._args[2] == 'groups': - group = Group.objects.get(uuid=self._args[3]) - permissions.addGroupPermission(group, obj, perm) + obj = cls.objects.get(uuid=self._args[1]) + + if self._args[2] == 'users': + user = User.objects.get(uuid=self._args[4]) + permissions.addUserPermission(user, obj, perm) + elif self._args[2] == 'groups': + group = Group.objects.get(uuid=self._args[4]) + permissions.addGroupPermission(group, obj, perm) + else: + raise RequestError('Ivalid request') + + return Permissions.permsToDict(permissions.getPermissions(obj)) + elif la == 1 and self._args[0] == 'revoke': + items = self._params.get('items', []) + for permId in items: + permissions.revokePermissionById(permId) + return {} else: - raise RequestError('Ivalid request') - - return Permissions.permsToDict(permissions.getPermissions(obj)) + raise RequestError('Invalid request') diff --git a/server/src/uds/REST/methods/providers.py b/server/src/uds/REST/methods/providers.py index 764fac081..d1ca0449f 100644 --- a/server/src/uds/REST/methods/providers.py +++ b/server/src/uds/REST/methods/providers.py @@ -36,6 +36,7 @@ from django.utils.translation import ugettext, ugettext_lazy as _ from uds.models import Provider, Service, UserService from uds.REST.methods.services import Services as DetailServices from uds.core import services +from uds.core.util import permissions from uds.REST import NotFound, RequestError from uds.REST.model import ModelHandler @@ -87,6 +88,7 @@ class Providers(ModelHandler): 'offers': offers, 'type': type_.type(), 'comments': provider.comments, + 'permission': permissions.getEffectivePermission(self._user, provider) } def checkDelete(self, item): @@ -110,7 +112,9 @@ class Providers(ModelHandler): ''' for s in Service.objects.all(): try: - yield DetailServices.serviceToDict(s, True) + perm = permissions.getEffectivePermission(self._user, s) + if perm >= permissions.PERMISSION_READ: + yield DetailServices.serviceToDict(s, perm, True) except Exception: logger.exception('Passed service cause type is unknown') @@ -119,7 +123,9 @@ class Providers(ModelHandler): Custom method that returns a service by its uuid, no matter who's his daddy ''' try: - return DetailServices.serviceToDict(Service.objects.get(uuid=self._args[1]), True) + service = Service.objects.get(uuid=self._args[1]) + perm = self.ensureAccess(service, permissions.PERMISSION_READ) # Ensures that we can read this item + return DetailServices.serviceToDict(service, perm, True) except Exception: raise RequestError(ugettext('Service not found')) @@ -128,6 +134,7 @@ class Providers(ModelHandler): Custom method that swaps maintenance mode state for a provider :param item: ''' + self.ensureAccess(item, permissions.PERMISSION_MANAGEMENT) item.maintenance_mode = not item.maintenance_mode item.save() return self.item_as_dict(item) @@ -137,6 +144,9 @@ class Providers(ModelHandler): logger.debug('Type: {}'.format(type_)) spType = services.factory().lookup(type_) + + self.ensureAccess(spType, permissions.PERMISSION_MANAGEMENT, root=True) + logger.debug('spType: {}'.format(spType)) res = spType.test(Environment.getTempEnv(), self._params) if res[0]: diff --git a/server/src/uds/REST/methods/services.py b/server/src/uds/REST/methods/services.py index d68b52b50..232ddce07 100644 --- a/server/src/uds/REST/methods/services.py +++ b/server/src/uds/REST/methods/services.py @@ -39,6 +39,7 @@ from uds.models import Service, UserService from uds.core.services import Service as coreService from uds.core.util import log +from uds.core.util import permissions from uds.core.Environment import Environment from uds.REST.model import DetailHandler from uds.REST import NotFound, ResponseError, RequestError @@ -55,7 +56,22 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods ''' @staticmethod - def serviceToDict(item, full=False): + def serviceInfo(item): + info = item.getType() + return { + 'icon': info.icon().replace('\n', ''), + 'needs_publication': info.publicationType is not None, + 'max_deployed': info.maxDeployed, + 'uses_cache': info.usesCache, + 'uses_cache_l2': info.usesCache_L2, + 'cache_tooltip': _(info.cacheTooltip), + 'cache_tooltip_l2': _(info.cacheTooltip_L2), + 'needs_manager': info.needsManager, + 'must_assign_manually': info.mustAssignManually, + } + + @staticmethod + def serviceToDict(item, perm, full=False): ''' Convert a service db item to a dict for a rest response :param item: Service item (db) @@ -70,31 +86,22 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods 'deployed_services_count': item.deployedServices.count(), 'user_services_count': UserService.objects.filter(deployed_service__service=item).count(), 'maintenance_mode': item.provider.maintenance_mode, + 'permission': perm } if full: - info = item.getType() - retVal['info'] = { - 'icon': info.icon().replace('\n', ''), - 'needs_publication': info.publicationType is not None, - 'max_deployed': info.maxDeployed, - 'uses_cache': info.usesCache, - 'uses_cache_l2': info.usesCache_L2, - 'cache_tooltip': _(info.cacheTooltip), - 'cache_tooltip_l2': _(info.cacheTooltip_L2), - 'needs_manager': info.needsManager, - 'must_assign_manually': info.mustAssignManually, - } + retVal['info'] = Services.serviceInfo(item) return retVal def getItems(self, parent, item): - # Extract provider + # Check what kind of access do we have to parent provider + perm = permissions.getEffectivePermission(self._user, parent) try: if item is None: - return [Services.serviceToDict(k) for k in parent.services.all()] + return [Services.serviceToDict(k, perm) for k in parent.services.all()] else: k = parent.services.get(uuid=item) - val = Services.serviceToDict(k) + val = Services.serviceToDict(k, perm, full=True) return self.fillIntanceFields(k, val) except Exception: logger.exception('itemId {}'.format(item)) diff --git a/server/src/uds/REST/methods/services_pools.py b/server/src/uds/REST/methods/services_pools.py index 1030de2be..67e3598f0 100644 --- a/server/src/uds/REST/methods/services_pools.py +++ b/server/src/uds/REST/methods/services_pools.py @@ -37,10 +37,12 @@ from uds.models import DeployedService, OSManager, Service, Image from uds.core.ui.images import DEFAULT_THUMB_BASE64 from uds.core.util.State import State from uds.core.util import log +from uds.core.util import permissions from uds.REST.model import ModelHandler from uds.REST import RequestError, ResponseError from uds.core.ui.UserInterface import gui -from uds.REST.methods.user_services import AssignedService, CachedService, Groups, Transports, Publications +from .user_services import AssignedService, CachedService, Groups, Transports, Publications +from .services import Services import logging @@ -80,6 +82,7 @@ class ServicesPools(ModelHandler): val = { 'id': item.uuid, 'name': item.name, + 'parent': item.service.name, 'comments': item.comments, 'state': item.state if item.service.provider.maintenance_mode is False else State.MAINTENANCE, 'thumb': item.image.thumb64 if item.image is not None else DEFAULT_THUMB_BASE64, @@ -93,6 +96,8 @@ class ServicesPools(ModelHandler): 'user_services_count': item.userServices.count(), 'restrained': item.isRestrained(), 'show_transports': item.show_transports, + 'permission': permissions.getEffectivePermission(self._user, item), + 'info': Services.serviceInfo(item.service) } if item.osmanager is not None: diff --git a/server/src/uds/REST/methods/transports.py b/server/src/uds/REST/methods/transports.py index a70997b3c..7b6bdb483 100644 --- a/server/src/uds/REST/methods/transports.py +++ b/server/src/uds/REST/methods/transports.py @@ -35,6 +35,7 @@ from __future__ import unicode_literals from django.utils.translation import ugettext_lazy as _, ugettext from uds.models import Transport, Network from uds.core.transports import factory +from uds.core.util import permissions from uds.REST.model import ModelHandler @@ -93,6 +94,7 @@ class Transports(ModelHandler): 'networks': [{'id': n.id} for n in item.networks.all()], 'deployed_count': item.deployedServices.count(), 'type': type_.type(), + 'permission': permissions.getEffectivePermission(self._user, item) } def afterSave(self, item): diff --git a/server/src/uds/REST/model.py b/server/src/uds/REST/model.py index f3f5a0b39..e6622d21d 100644 --- a/server/src/uds/REST/model.py +++ b/server/src/uds/REST/model.py @@ -33,13 +33,14 @@ from __future__ import unicode_literals -from uds.REST.handlers import NotFound, RequestError, ResponseError +from uds.REST.handlers import NotFound, RequestError, ResponseError, AccessDenied, NotSupportedError from django.utils.translation import ugettext as _ from django.db import IntegrityError from uds.core.ui.UserInterface import gui as uiGui from uds.REST.handlers import Handler, HandlerError from uds.core.util import log +from uds.core.util import permissions import fnmatch import re @@ -50,7 +51,7 @@ import logging logger = logging.getLogger(__name__) -__updated__ = '2015-02-16' +__updated__ = '2015-03-05' # a few constants @@ -150,6 +151,12 @@ class BaseModelHandler(Handler): return gui + def ensureAccess(self, obj, permission, root=False): + perm = permissions.getEffectivePermission(self._user, obj, root) + if perm < permission: + self.accessDenied() + return perm + def typeInfo(self, type_): # pylint: disable=no-self-use ''' Returns info about the type @@ -233,6 +240,12 @@ class BaseModelHandler(Handler): message = _('Item not found') if message is None else None raise NotFound('{} {}: {}'.format(message, self.__class__, self._args)) + def accessDenied(self, message=None): + raise AccessDenied(message or _('Access denied')) + + def notSupported(self, message=None): + raise NotSupportedError(message or _('Operation not supported')) + # Success methods def success(self): ''' @@ -284,6 +297,7 @@ class DetailHandler(BaseModelHandler): # pylint: disable=abstract-class-not-use self._params = params self._args = args self._kwargs = kwargs + self._user = kwargs.get('user', None) def __checkCustom(self, check, parent, arg=None): ''' @@ -312,6 +326,7 @@ class DetailHandler(BaseModelHandler): # pylint: disable=abstract-class-not-use nArgs = len(self._args) parent = self._kwargs['parent'] + if nArgs == 0: return self.getItems(parent, None) @@ -578,6 +593,7 @@ class ModelHandler(BaseModelHandler): # log related def getLogs(self, item): + self.ensureAccess(item, permissions.PERMISSION_READ) logger.debug('Default getLogs invoked') return log.getLogs(item) @@ -656,14 +672,26 @@ class ModelHandler(BaseModelHandler): return data # Helper to process detail + # Details can be managed (writen) by any user that has MANAGEMENT permission over parent def processDetail(self): - logger.debug('Processing detail {0}'.format(self._path)) + logger.debug('Processing detail {} for user {}'.format(self._path, self._user)) try: item = self.model.objects.filter(uuid=self._args[0])[0] + # If we do not have access to parent to, at least, read... + + if self._operation in ('put', 'post', 'delete'): + requiredPermission = permissions.PERMISSION_MANAGEMENT + else: + requiredPermission = permissions.PERMISSION_READ + + if permissions.checkPermissions(self._user, item, requiredPermission) is False: + logger.debug('Permission for user {} does not comply with {}'.format(self._user, requiredPermission)) + self.accessDenied() + detailCls = self.detail[self._args[1]] args = list(self._args[2:]) path = self._path + '/'.join(args[:2]) - detail = detailCls(self, path, self._params, *args, parent=item) + detail = detailCls(self, path, self._params, *args, parent=item, user=self._user) method = getattr(detail, self._operation) except KeyError: self.invalidMethodException() @@ -672,10 +700,17 @@ class ModelHandler(BaseModelHandler): return method() - def getItems(self, *args, **kwargs): + def getItems(self, overview=True, *args, **kwargs): for item in self.model.objects.filter(*args, **kwargs): try: - yield self.item_as_dict_overview(item) + if permissions.checkPermissions(self._user, item, permissions.PERMISSION_READ) is False: + continue + if overview: + yield self.item_as_dict_overview(item) + else: + res = self.item_as_dict(item) + self.fillIntanceFields(item, res) + yield res except Exception: # maybe an exception is thrown to skip an item # logger.exception('Exception getting item from {0}'.format(self.model)) pass @@ -693,15 +728,7 @@ class ModelHandler(BaseModelHandler): nArgs = len(self._args) if nArgs == 0: - result = [] - for val in self.model.objects.all(): - try: - res = self.item_as_dict(val) - self.fillIntanceFields(val, res) - result.append(res) - except Exception: # maybe an exception is thrown to skip an item - pass - return result + return list(self.getItems(overview=False)) # if has custom methods, look for if this request matches any of them for cm in self.custom_methods: @@ -735,6 +762,9 @@ class ModelHandler(BaseModelHandler): # get item ID try: val = self.model.objects.get(uuid=self._args[0].lower()) + + self.ensureAccess(val, permissions.PERMISSION_READ) + res = self.item_as_dict(val) self.fillIntanceFields(val, res) return res @@ -793,6 +823,9 @@ class ModelHandler(BaseModelHandler): if len(self._args) > 1: # Detail? return self.processDetail() + + self.ensureAccess(self.model(), permissions.PERMISSION_ALL, root=True) # Must have write permissions to create, modify, etc.. + try: # Extract fields args = self.readFieldsFromParams(self.save_fields) @@ -854,6 +887,9 @@ class ModelHandler(BaseModelHandler): if len(self._args) != 1: raise RequestError('Delete need one and only one argument') + + self.ensureAccess(self.model(), permissions.PERMISSION_ALL, root=True) # Must have write permissions to delete + try: item = self.model.objects.get(uuid=self._args[0].lower()) self.checkDelete(item) diff --git a/server/src/uds/core/util/ot.py b/server/src/uds/core/util/ot.py index 3ab5827d6..771985852 100644 --- a/server/src/uds/core/util/ot.py +++ b/server/src/uds/core/util/ot.py @@ -32,7 +32,7 @@ ''' from __future__ import unicode_literals -__updated__ = '2015-03-01' +__updated__ = '2015-03-05' from uds.models import Provider, Service, OSManager, Transport, Network, ServicePool, UserService, Authenticator, User, Group, StatsCounters, StatsEvents import logging diff --git a/server/src/uds/core/util/permissions.py b/server/src/uds/core/util/permissions.py index f6be9f794..c12fb2ec0 100644 --- a/server/src/uds/core/util/permissions.py +++ b/server/src/uds/core/util/permissions.py @@ -32,7 +32,7 @@ ''' from __future__ import unicode_literals -__updated__ = '2015-03-04' +__updated__ = '2015-03-05' from uds.models import Permissions from uds.core.util import ot @@ -43,6 +43,7 @@ logger = logging.getLogger(__name__) PERMISSION_ALL = Permissions.PERMISSION_ALL PERMISSION_READ = Permissions.PERMISSION_READ +PERMISSION_MANAGEMENT = Permissions.PERMISSION_MANAGEMENT PERMISSION_NONE = Permissions.PERMISSION_NONE @@ -54,6 +55,19 @@ def getPermissions(obj): return list(Permissions.enumeratePermissions(object_type=ot.getObjectType(obj), object_id=obj.pk)) +def getEffectivePermission(user, obj, root=False): + if user.is_admin is True: + return PERMISSION_ALL + + if user.staff_member is False: + return PERMISSION_NONE + + if root is False: + return Permissions.getPermissions(user=user, groups=user.groups.all(), object_type=ot.getObjectType(obj), object_id=obj.pk) + else: + return Permissions.getPermissions(user=user, groups=user.groups.all(), object_type=ot.getObjectType(obj)) + + def addUserPermission(user, obj, permission=PERMISSION_READ): # Some permissions added to some object types needs at least READ_PERMISSION on parent Permissions.addPermission(user=user, object_type=ot.getObjectType(obj), object_id=obj.pk, permission=permission) @@ -63,15 +77,16 @@ def addGroupPermission(group, obj, permission=PERMISSION_READ): Permissions.addPermission(group=group, object_type=ot.getObjectType(obj), object_id=obj.pk, permission=permission) -def checkPermissions(user, obj, permission=PERMISSION_ALL): - if user.is_admin is True: - return True - - if user.is_staff is False: - return False - - return Permissions.getPermissions(user=user, groups=user.groups.all(), object_type=ot.getObjectType(obj), object_id=obj.pk) >= permission +def checkPermissions(user, obj, permission=PERMISSION_ALL, root=False): + return getEffectivePermission(user, obj, root) >= permission def getPermissionName(perm): return Permissions.permissionAsString(perm) + + +def revokePermissionById(permId): + try: + return Permissions.objects.get(uuid=permId).delete() + except Exception: + return None diff --git a/server/src/uds/models/Permissions.py b/server/src/uds/models/Permissions.py index 83f7d64ee..3830118ec 100644 --- a/server/src/uds/models/Permissions.py +++ b/server/src/uds/models/Permissions.py @@ -33,7 +33,7 @@ from __future__ import unicode_literals -__updated__ = '2015-03-04' +__updated__ = '2015-03-05' from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext as _ @@ -58,8 +58,9 @@ class Permissions(UUIDModel): # pylint: disable=model-missing-unicode # Allowed permissions PERMISSION_NONE = 0 - PERMISSION_READ = 16 - PERMISSION_ALL = 32 + PERMISSION_READ = 32 + PERMISSION_MANAGEMENT = 64 + PERMISSION_ALL = 96 created = models.DateTimeField(db_index=True) ends = models.DateTimeField(db_index=True, null=True, blank=True, default=None) # Future "permisions ends at this moment", not assigned right now @@ -77,6 +78,7 @@ class Permissions(UUIDModel): return { Permissions.PERMISSION_NONE: _('None'), Permissions.PERMISSION_READ: _('Read'), + Permissions.PERMISSION_MANAGEMENT: _('Management'), Permissions.PERMISSION_ALL: _('All') }.get(perm, _('None')) diff --git a/server/src/uds/static/adm/js/api.coffee b/server/src/uds/static/adm/js/api.coffee index a4912882e..81c092f3b 100644 --- a/server/src/uds/static/adm/js/api.coffee +++ b/server/src/uds/static/adm/js/api.coffee @@ -6,6 +6,13 @@ api = @api api.debug = on +api.permissions = { + NONE: 0 + READ: 32 + MANAGEMENT: 64 + ALL: 96 +} + api.doLog = (args...) -> if api.debug try @@ -267,6 +274,12 @@ class BasicModelRest return + permission: () -> + if api.config.admin is true + return api.permissions.ALL + + return api.permissions.NONE + getPermissions: (id, success_fnc, fail_fnc) -> path = "permissions/" + @path + '/' + id @_requestPath path, @@ -275,17 +288,17 @@ class BasicModelRest fail: fail_fnc addPermission: (id, type, itemId, perm, success_fnc, fail_fnc) -> - path = "permissions/" + @path + '/' + id + '/' + type + '/' + itemId + path = "permissions/" + @path + '/' + id + '/' + type + '/add/' + itemId data = perm: perm api.putJson path, data, success: success_fnc fail: fail_fnc - revokePermissions: (id, type, itemIds, success_fnc, fail_fnc)-> - path = "permissions/revoke/" + @path + '/' + id + '/' + type + revokePermissions: (itemIds, success_fnc, fail_fnc)-> + path = "permissions/revoke" data = - ids: itemIds + items: itemIds api.putJson path, data, success: success_fnc fail: fail_fnc @@ -337,13 +350,15 @@ class DetailModelRestApi extends BasicModelRest model ].join("/") @moptions = options + + permission: () -> + if @moptions.permission? then @moptions.permission else api.permissions.ALL create: (data, success_fnc, fail_fnc) -> @put data, success: success_fnc fail: fail_fnc - save: (data, success_fnc, fail_fnc) -> @put data, id: data.id diff --git a/server/src/uds/static/adm/js/gui-d-authenticators.coffee b/server/src/uds/static/adm/js/gui-d-authenticators.coffee index deb93c096..893d28701 100644 --- a/server/src/uds/static/adm/js/gui-d-authenticators.coffee +++ b/server/src/uds/static/adm/js/gui-d-authenticators.coffee @@ -125,6 +125,7 @@ gui.authenticators.link = (event) -> "edit" "delete" "xls" + "permissions" ] onRowDeselect: -> clearDetails() @@ -141,8 +142,8 @@ gui.authenticators.link = (event) -> id = selected[0].id type = gui.authenticators.types[selected[0].type] gui.doLog "Type", type - user = new GuiElement(api.authenticators.detail(id, "users"), "users") - group = new GuiElement(api.authenticators.detail(id, "groups"), "groups") + user = new GuiElement(api.authenticators.detail(id, "users", { permission: selected[0].permission }), "users") + group = new GuiElement(api.authenticators.detail(id, "groups", { permission: selected[0].permission }), "groups") grpTable = group.table( container: "groups-placeholder" rowSelect: "single" diff --git a/server/src/uds/static/adm/js/gui-d-connectivity.coffee b/server/src/uds/static/adm/js/gui-d-connectivity.coffee index a9a726770..99f68aee5 100644 --- a/server/src/uds/static/adm/js/gui-d-connectivity.coffee +++ b/server/src/uds/static/adm/js/gui-d-connectivity.coffee @@ -19,6 +19,7 @@ gui.connectivity.link = (event) -> "edit" "delete" "xls" + "permissions" ] onNew: gui.methods.typedNew(gui.connectivity.transports, gettext("New transport"), gettext("Transport creation error")) onEdit: gui.methods.typedEdit(gui.connectivity.transports, gettext("Edit transport"), gettext("Transport saving error")) @@ -32,6 +33,7 @@ gui.connectivity.link = (event) -> "edit" "delete" "xls" + "permissions" ] onNew: gui.methods.typedNew(gui.connectivity.networks, gettext("New network"), gettext("Network creation error")) onEdit: gui.methods.typedEdit(gui.connectivity.networks, gettext("Edit network"), gettext("Network saving error")) diff --git a/server/src/uds/static/adm/js/gui-d-osmanagers.coffee b/server/src/uds/static/adm/js/gui-d-osmanagers.coffee index 33a5c7973..df697d726 100644 --- a/server/src/uds/static/adm/js/gui-d-osmanagers.coffee +++ b/server/src/uds/static/adm/js/gui-d-osmanagers.coffee @@ -17,6 +17,7 @@ gui.osmanagers.link = (event) -> "edit" "delete" "xls" + "permissions" ] onNew: gui.methods.typedNew(gui.osmanagers, gettext("New OSManager"), gettext("OSManager creation error")) onEdit: gui.methods.typedEdit(gui.osmanagers, gettext("Edit OSManager"), gettext("OSManager saving error")) diff --git a/server/src/uds/static/adm/js/gui-d-services.coffee b/server/src/uds/static/adm/js/gui-d-services.coffee index 566370a6f..08ccbffee 100644 --- a/server/src/uds/static/adm/js/gui-d-services.coffee +++ b/server/src/uds/static/adm/js/gui-d-services.coffee @@ -54,6 +54,8 @@ gui.providers.link = (event) -> return tableId = gui.providers.table( + getPermission: (selected) -> + gui.doLog "Selected", selected container: "providers-placeholder" rowSelect: "single" onCheck: (check, items) -> # Check if item can be deleted @@ -84,9 +86,9 @@ gui.providers.link = (event) -> clearDetails() $("#detail-placeholder").removeClass "hidden" id = selected[0].id - + # Giving the name compossed with type, will ensure that only styles will be reattached once - services = new GuiElement(api.providers.detail(id, "services"), "services-" + selected[0].type) + services = new GuiElement(api.providers.detail(id, "services", { permission: selected[0].permission }), "services-" + selected[0].type) tmpLogTable = undefined servicesTable = services.table( container: "services-placeholder" @@ -120,7 +122,6 @@ gui.providers.link = (event) -> "edit" "delete" "xls" - "permissions" ] onEdit: gui.methods.typedEdit(services, gettext("Edit service"), gettext("Service creation error")) onNew: gui.methods.typedNew(services, gettext("New service"), gettext("Service saving error")) @@ -141,6 +142,7 @@ gui.providers.link = (event) -> "new" "edit" { + permission: api.permissions.MANAGEMENT text: gettext("Maintenance") css: "disabled" click: (val, value, btn, tbl, refreshFnc) -> diff --git a/server/src/uds/static/adm/js/gui-d-servicespools.coffee b/server/src/uds/static/adm/js/gui-d-servicespools.coffee index 0bdbd4de6..1ee705601 100644 --- a/server/src/uds/static/adm/js/gui-d-servicespools.coffee +++ b/server/src/uds/static/adm/js/gui-d-servicespools.coffee @@ -79,243 +79,114 @@ gui.servicesPools.link = (event) -> return return - # Fills up the list of available services - api.providers.allServices (services) -> - availableServices = {} - $.each services, (undefined_, service) -> - availableServices[service.id] = service + api.templates.get "services_pool", (tmpl) -> + gui.appendToWorkspace api.templates.evaluate(tmpl, + deployed_services: "deployed-services-placeholder" + assigned_services: "assigned-services-placeholder" + cache: "cache-placeholder" + groups: "groups-placeholder" + transports: "transports-placeholder" + publications: "publications-placeholder" + logs: "logs-placeholder" + ) + gui.setLinksEvents() + + # Append tabs click events + $(".bottom_tabs").on "click", (event) -> + gui.doLog event.target + setTimeout (-> + $($(event.target).attr("href") + " span.fa-refresh").click() + return + ), 10 return - gui.doLog "Available services", availableServices - api.templates.get "services_pool", (tmpl) -> - gui.appendToWorkspace api.templates.evaluate(tmpl, - deployed_services: "deployed-services-placeholder" - assigned_services: "assigned-services-placeholder" - cache: "cache-placeholder" - groups: "groups-placeholder" - transports: "transports-placeholder" - publications: "publications-placeholder" - logs: "logs-placeholder" - ) - gui.setLinksEvents() - - # Append tabs click events - $(".bottom_tabs").on "click", (event) -> - gui.doLog event.target - setTimeout (-> - $($(event.target).attr("href") + " span.fa-refresh").click() - return - ), 10 + + # + # * Services pools part + # + servicesPoolsTable = gui.servicesPools.table( + container: "deployed-services-placeholder" + rowSelect: "single" + buttons: [ + "new" + "edit" + "delete" + "xls" + "permissions" + ] + onRowDeselect: -> + clearDetails() return - - # - # * Services pools part - # - servicesPoolsTable = gui.servicesPools.table( - container: "deployed-services-placeholder" - rowSelect: "single" - buttons: [ - "new" - "edit" - "delete" - "xls" - ] - onRowDeselect: -> - clearDetails() + onRowSelect: (selected) -> + servPool = selected[0] + gui.doLog "Selected services pool", servPool + clearDetails() + service = null + try + info = servPool.info + catch e + gui.doLog "Exception on rowSelect", e + gui.notify "Service pool " + gettext("error"), "danger" return - onRowSelect: (selected) -> - servPool = selected[0] - gui.doLog "Selected services pool", servPool - clearDetails() - service = null - try - service = availableServices[servPool.service_id] - catch e - gui.doLog "Exception on rowSelect", e - gui.notify "Service pool " + gettext("error"), "danger" - return - if service? - $("#detail-placeholder").removeClass "hidden" - else - $("#detail-placeholder").addClass "hidden" - return + $("#detail-placeholder").removeClass "hidden" + + # + # * Cache Part + # + cachedItems = null + + # If service does not supports cache, do not show it + # Shows/hides cache + if info.uses_cache or info.uses_cache_l2 + $("#cache-placeholder_tab").removeClass "hidden" + cachedItems = new GuiElement(api.servicesPools.detail(servPool.id, "cache", { permission: servPool.permission }), "cache") - # - # * Cache Part - # - cachedItems = null - - # If service does not supports cache, do not show it - # Shows/hides cache - if service.info.uses_cache or service.info.uses_cache_l2 - $("#cache-placeholder_tab").removeClass "hidden" - cachedItems = new GuiElement(api.servicesPools.detail(servPool.id, "cache"), "cache") - - # Cached items table - prevCacheLogTbl = null - cachedItemsTable = cachedItems.table( - container: "cache-placeholder_tbl" - buttons: [ - "delete" - "xls" - ] - rowSelect: "single" - onData: (data) -> - fillState data - return - - onRowSelect: (selected) -> - gui.do - cached = selected[0] - if prevCacheLogTbl - $tbl = $(prevCacheLogTbl).dataTable() - $tbl.fnClearTable() - $tbl.fnDestroy() - prevCacheLogTbl = cachedItems.logTable(cached.id, - container: "cache-placeholder_log" - ) - return - - onDelete: gui.methods.del(cachedItems, gettext("Remove Cache element"), gettext("Deletion error")) - ) - prevTables.push cachedItemsTable - else - $("#cache-placeholder_tab").addClass "hidden" - - # - # * Groups part - # - groups = null - - # Shows/hides groups - if service.info.must_assign_manually is false - $("#groups-placeholder_tab").removeClass "hidden" - groups = new GuiElement(api.servicesPools.detail(servPool.id, "groups"), "groups") - - # Groups items table - groupsTable = groups.table( - container: "groups-placeholder" - rowSelect: "single" - buttons: [ - "new" - "delete" - "xls" - ] - onNew: (value, table, refreshFnc) -> - api.templates.get "pool_add_group", (tmpl) -> - api.authenticators.overview (data) -> - # Sorts groups, expression means that "if a > b returns 1, if b > a returns -1, else returns 0" - - modalId = gui.launchModal(gettext("Add group"), api.templates.evaluate(tmpl, - auths: data - )) - $(modalId + " #id_auth_select").on "change", (event) -> - auth = $(modalId + " #id_auth_select").val() - api.authenticators.detail(auth, "groups").overview (data) -> - $select = $(modalId + " #id_group_select") - $select.empty() - # Sorts groups, expression means that "if a > b returns 1, if b > a returns -1, else returns 0" - $.each data, (undefined_, value) -> - $select.append "" - return - - - # Refresh selectpicker if item is such - $select.selectpicker "refresh" if $select.hasClass("selectpicker") - return - - return - - $(modalId + " .button-accept").on "click", (event) -> - auth = $(modalId + " #id_auth_select").val() - group = $(modalId + " #id_group_select").val() - if auth is -1 or group is -1 - gui.notify gettext("You must provide authenticator and group"), "danger" - else # Save & close modal - groups.rest.create - id: group - , (data) -> - $(modalId).modal "hide" - refreshFnc() - return - - return - - - # Makes form "beautyfull" :-) - gui.tools.applyCustoms modalId - return - - return - - return - - onDelete: gui.methods.del(groups, gettext("Remove group"), gettext("Group removal error")) - onData: (data) -> - $.each data, (undefined_, value) -> - value.group_name = "" + value.auth_name + "\\" + value.name - return - - return - ) - prevTables.push groupsTable - else - $("#groups-placeholder_tab").addClass "hidden" - - # - # * Assigned services part - # - prevAssignedLogTbl = null - assignedServices = new GuiElement(api.servicesPools.detail(servPool.id, "services"), "services") - assignedServicesTable = assignedServices.table( - container: "assigned-services-placeholder_tbl" + # Cached items table + prevCacheLogTbl = null + cachedItemsTable = cachedItems.table( + container: "cache-placeholder_tbl" + buttons: [ + "delete" + "xls" + ] rowSelect: "single" - buttons: (if service.info.must_assign_manually then [ - "new" - "delete" - "xls" - ] else [ - "delete" - "xls" - ]) - onData: (data) -> fillState data - $.each data, (index, value) -> - if value.in_use is true - value.in_use = gettext('Yes') - else - value.in_use = gettext('No') - return onRowSelect: (selected) -> - service = selected[0] - if prevAssignedLogTbl - $tbl = $(prevAssignedLogTbl).dataTable() + gui.do + cached = selected[0] + if prevCacheLogTbl + $tbl = $(prevCacheLogTbl).dataTable() $tbl.fnClearTable() $tbl.fnDestroy() - prevAssignedLogTbl = assignedServices.logTable(service.id, - container: "assigned-services-placeholder_log" + prevCacheLogTbl = cachedItems.logTable(cached.id, + container: "cache-placeholder_log" ) return - onDelete: gui.methods.del(assignedServices, gettext("Remove Assigned service"), gettext("Deletion error")) + onDelete: gui.methods.del(cachedItems, gettext("Remove Cache element"), gettext("Deletion error")) ) + prevTables.push cachedItemsTable + else + $("#cache-placeholder_tab").addClass "hidden" + + # + # * Groups part + # + groups = null + + # Shows/hides groups + if info.must_assign_manually is false + $("#groups-placeholder_tab").removeClass "hidden" + groups = new GuiElement(api.servicesPools.detail(servPool.id, "groups", { permission: servPool.permission }), "groups") - # Log of assigned services (right under assigned services) - prevTables.push assignedServicesTable - - # - # * Transports part - # - transports = new GuiElement(api.servicesPools.detail(servPool.id, "transports"), "transports") - - # Transports items table - transportsTable = transports.table( - container: "transports-placeholder" + # Groups items table + groupsTable = groups.table( + container: "groups-placeholder" rowSelect: "single" buttons: [ "new" @@ -323,18 +194,38 @@ gui.servicesPools.link = (event) -> "xls" ] onNew: (value, table, refreshFnc) -> - api.templates.get "pool_add_transport", (tmpl) -> - api.transports.overview (data) -> - modalId = gui.launchModal(gettext("Add transport"), api.templates.evaluate(tmpl, - transports: data + api.templates.get "pool_add_group", (tmpl) -> + api.authenticators.overview (data) -> + # Sorts groups, expression means that "if a > b returns 1, if b > a returns -1, else returns 0" + + modalId = gui.launchModal(gettext("Add group"), api.templates.evaluate(tmpl, + auths: data )) + $(modalId + " #id_auth_select").on "change", (event) -> + auth = $(modalId + " #id_auth_select").val() + api.authenticators.detail(auth, "groups").overview (data) -> + $select = $(modalId + " #id_group_select") + $select.empty() + # Sorts groups, expression means that "if a > b returns 1, if b > a returns -1, else returns 0" + $.each data, (undefined_, value) -> + $select.append "" + return + + + # Refresh selectpicker if item is such + $select.selectpicker "refresh" if $select.hasClass("selectpicker") + return + + return + $(modalId + " .button-accept").on "click", (event) -> - transport = $(modalId + " #id_transport_select").val() - if transport is -1 - gui.notify gettext("You must provide a transport"), "danger" + auth = $(modalId + " #id_auth_select").val() + group = $(modalId + " #id_group_select").val() + if auth is -1 or group is -1 + gui.notify gettext("You must provide authenticator and group"), "danger" else # Save & close modal - transports.rest.create - id: transport + groups.rest.create + id: group , (data) -> $(modalId).modal "hide" refreshFnc() @@ -351,142 +242,232 @@ gui.servicesPools.link = (event) -> return - onDelete: gui.methods.del(transports, gettext("Remove transport"), gettext("Transport removal error")) + onDelete: gui.methods.del(groups, gettext("Remove group"), gettext("Group removal error")) onData: (data) -> $.each data, (undefined_, value) -> - style = "display:inline-block; background: url(data:image/png;base64," + value.type.icon + "); ; background-size: 16px 16px; background-repeat: no-repeat; width: 16px; height: 16px; vertical-align: middle;" - value.trans_type = value.type.name - value.name = " " + value.name + value.group_name = "" + value.auth_name + "\\" + value.name return return ) - prevTables.push transportsTable - - # - # * Publications part - # - publications = null - if service.info.needs_publication - $("#publications-placeholder_tab").removeClass "hidden" - pubApi = api.servicesPools.detail(servPool.id, "publications") - publications = new GuiElement(pubApi, "publications") - - # Publications table - publicationsTable = publications.table( - container: "publications-placeholder" - rowSelect: "single" - buttons: [ - "new" - { - text: gettext("Cancel") - css: "disabled" - click: (val, value, btn, tbl, refreshFnc) -> - gui.promptModal gettext("Publish"), gettext("Cancel publication"), - onYes: -> - pubApi.invoke val.id + "/cancel", -> - refreshFnc() - return - - return - - return - - select: (val, value, btn, tbl, refreshFnc) -> - unless val - $(btn).removeClass("btn3d-warning").addClass "disabled" - return - - if val.state == 'K' - $(btn).empty().append(gettext("Force Cancel")) - else - $(btn).empty().append(gettext("Cancel")) - - # Waiting for publication, Preparing or running - gui.doLog "State: ", val.state - $(btn).removeClass("disabled").addClass "btn3d-warning" if [ - "P" - "W" - "L" - "K" - ].indexOf(val.state) != -1 - - return - } - "xls" - ] - onNew: (action, tbl, refreshFnc) -> - gui.promptModal gettext("Publish"), gettext("Launch new publication?"), - onYes: -> - pubApi.invoke "publish", (-> - refreshFnc() - return - ), gui.failRequestModalFnc(gettext("Failed creating publication")) - return - - return - ) - prevTables.push publicationsTable - else - $("#publications-placeholder_tab").addClass "hidden" - - # - # * Log table - # - logTable = gui.servicesPools.logTable(servPool.id, - container: "logs-placeholder" - ) - prevTables.push logTable - return - + prevTables.push groupsTable + else + $("#groups-placeholder_tab").addClass "hidden" - # Pre-process data received to add "icon" to deployed service - onData: (data) -> - gui.doLog "onData", data - $.each data, (index, value) -> - gui.doLog value.thumb - try - service = availableServices[value.service_id] - if not service? - value.parent = gettext("undefined") - return - style = "display:inline-block; background: url(data:image/png;base64," + value.thumb + "); background-size: 16px 16px; background-repeat: no-repeat; width: 16px; height: 16px; vertical-align: middle;" - gui.doLog style - if value.restrained - value.name = " " + value.name - value.state = gettext("Restrained") - value.name = " " + value.name - value.parent = service.name - catch e - value.name = " " + value.name - value.parent = gettext("unknown (needs reload)") + # + # * Assigned services part + # + prevAssignedLogTbl = null + assignedServices = new GuiElement(api.servicesPools.detail(servPool.id, "services", { permission: servPool.permission }), "services") + assignedServicesTable = assignedServices.table( + container: "assigned-services-placeholder_tbl" + rowSelect: "single" + buttons: (if info.must_assign_manually then [ + "new" + "delete" + "xls" + ] else [ + "delete" + "xls" + ]) + + onData: (data) -> + fillState data + $.each data, (index, value) -> + if value.in_use is true + value.in_use = gettext('Yes') + else + value.in_use = gettext('No') + return + onRowSelect: (selected) -> + svr = selected[0] + if prevAssignedLogTbl + $tbl = $(prevAssignedLogTbl).dataTable() + $tbl.fnClearTable() + $tbl.fnDestroy() + prevAssignedLogTbl = assignedServices.logTable(svr.id, + container: "assigned-services-placeholder_log" + ) + return + + onDelete: gui.methods.del(assignedServices, gettext("Remove Assigned service"), gettext("Deletion error")) + ) + + # Log of assigned services (right under assigned services) + prevTables.push assignedServicesTable + + # + # * Transports part + # + transports = new GuiElement(api.servicesPools.detail(servPool.id, "transports", { permission: servPool.permission }), "transports") + + # Transports items table + transportsTable = transports.table( + container: "transports-placeholder" + rowSelect: "single" + buttons: [ + "new" + "delete" + "xls" + ] + onNew: (value, table, refreshFnc) -> + api.templates.get "pool_add_transport", (tmpl) -> + api.transports.overview (data) -> + modalId = gui.launchModal(gettext("Add transport"), api.templates.evaluate(tmpl, + transports: data + )) + $(modalId + " .button-accept").on "click", (event) -> + transport = $(modalId + " #id_transport_select").val() + if transport is -1 + gui.notify gettext("You must provide a transport"), "danger" + else # Save & close modal + transports.rest.create + id: transport + , (data) -> + $(modalId).modal "hide" + refreshFnc() + return + + return + + + # Makes form "beautyfull" :-) + gui.tools.applyCustoms modalId + return + + return + + return + + onDelete: gui.methods.del(transports, gettext("Remove transport"), gettext("Transport removal error")) + onData: (data) -> + $.each data, (undefined_, value) -> + style = "display:inline-block; background: url(data:image/png;base64," + value.type.icon + "); ; background-size: 16px 16px; background-repeat: no-repeat; width: 16px; height: 16px; vertical-align: middle;" + value.trans_type = value.type.name + value.name = " " + value.name + return + + return + ) + prevTables.push transportsTable + + # + # * Publications part + # + publications = null + if info.needs_publication + $("#publications-placeholder_tab").removeClass "hidden" + pubApi = api.servicesPools.detail(servPool.id, "publications") + publications = new GuiElement(pubApi, "publications", { permission: servPool.permission }) + + # Publications table + publicationsTable = publications.table( + container: "publications-placeholder" + rowSelect: "single" + buttons: [ + "new" + { + text: gettext("Cancel") + css: "disabled" + click: (val, value, btn, tbl, refreshFnc) -> + gui.promptModal gettext("Publish"), gettext("Cancel publication"), + onYes: -> + pubApi.invoke val.id + "/cancel", -> + refreshFnc() + return + + return + + return + + select: (val, value, btn, tbl, refreshFnc) -> + unless val + $(btn).removeClass("btn3d-warning").addClass "disabled" + return + + if val.state == 'K' + $(btn).empty().append(gettext("Force Cancel")) + else + $(btn).empty().append(gettext("Cancel")) + + # Waiting for publication, Preparing or running + gui.doLog "State: ", val.state + $(btn).removeClass("disabled").addClass "btn3d-warning" if [ + "P" + "W" + "L" + "K" + ].indexOf(val.state) != -1 + + return + } + "xls" + ] + onNew: (action, tbl, refreshFnc) -> + gui.promptModal gettext("Publish"), gettext("Launch new publication?"), + onYes: -> + pubApi.invoke "publish", (-> + refreshFnc() + return + ), gui.failRequestModalFnc(gettext("Failed creating publication")) + return + + return + ) + prevTables.push publicationsTable + else + $("#publications-placeholder_tab").addClass "hidden" + + # + # * Log table + # + logTable = gui.servicesPools.logTable(servPool.id, + container: "logs-placeholder" + ) + prevTables.push logTable + return + + + # Pre-process data received to add "icon" to deployed service + onData: (data) -> + gui.doLog "onData", data + $.each data, (index, value) -> + gui.doLog value.thumb + try + style = "display:inline-block; background: url(data:image/png;base64," + value.thumb + "); background-size: 16px 16px; background-repeat: no-repeat; width: 16px; height: 16px; vertical-align: middle;" + gui.doLog style + if value.restrained + value.name = " " + value.name + value.state = gettext("Restrained") + value.name = " " + value.name + catch e + value.name = " " + value.name return - onNew: gui.methods.typedNew(gui.servicesPools, gettext("New service pool"), "Service pool " + gettext("creation error"), - guiProcessor: (guiDef) -> # Create has "save on publish" field - gui.doLog guiDef - newDef = [].concat(guiDef).concat([ - name: "publish_on_save" - value: true - gui: - label: gettext("Publish on creation") - tooltip: gettext("If selected, will initiate the publication inmediatly after creation") - type: "checkbox" - order: 150 - defvalue: true - ]) - gui.doLog newDef - newDef + return - preprocessor: preFnc - ) - onEdit: gui.methods.typedEdit(gui.servicesPools, gettext("Edit") + " service pool", "Service pool " + gettext("saving error")) - onDelete: gui.methods.del(gui.servicesPools, gettext("Delete") + " service pool", "Service pool " + gettext("deletion error")) + onNew: gui.methods.typedNew(gui.servicesPools, gettext("New service pool"), "Service pool " + gettext("creation error"), + guiProcessor: (guiDef) -> # Create has "save on publish" field + gui.doLog guiDef + newDef = [].concat(guiDef).concat([ + name: "publish_on_save" + value: true + gui: + label: gettext("Publish on creation") + tooltip: gettext("If selected, will initiate the publication inmediatly after creation") + type: "checkbox" + order: 150 + defvalue: true + ]) + gui.doLog newDef + newDef + + preprocessor: preFnc ) - return - + onEdit: gui.methods.typedEdit(gui.servicesPools, gettext("Edit") + " service pool", "Service pool " + gettext("saving error")) + onDelete: gui.methods.del(gui.servicesPools, gettext("Delete") + " service pool", "Service pool " + gettext("deletion error")) + ) return - return \ No newline at end of file diff --git a/server/src/uds/static/adm/js/gui-element.coffee b/server/src/uds/static/adm/js/gui-element.coffee index dc5530884..6d8be414d 100644 --- a/server/src/uds/static/adm/js/gui-element.coffee +++ b/server/src/uds/static/adm/js/gui-element.coffee @@ -276,52 +276,56 @@ $.each tblParams.buttons, (index, value) -> # Iterate through button definition btn = null switch value + when "new" - if Object.keys(self.types).length isnt 0 - menuId = gui.genRamdonId("dd-") - ordered = [] - $.each self.types, (k, v) -> - ordered.push - type: k - css: v.css - name: v.name - description: v.description + if self.rest.permission() >= api.permissions.MANAGEMENT + if Object.keys(self.types).length isnt 0 + menuId = gui.genRamdonId("dd-") + ordered = [] + $.each self.types, (k, v) -> + ordered.push + type: k + css: v.css + name: v.name + description: v.description - return + return - ordered = ordered.sort((a, b) -> - a.name.localeCompare b.name - ) - btn = - sExtends: "div" - sButtonText: api.templates.evaluate("tmpl_comp_dropdown", - label: gui.config.dataTableButtons["new"].text - css: gui.config.dataTableButtons["new"].css - id: menuId - tableId: tableId - columns: columns - menu: ordered + ordered = ordered.sort((a, b) -> + a.name.localeCompare b.name ) - else + btn = + sExtends: "div" + sButtonText: api.templates.evaluate("tmpl_comp_dropdown", + label: gui.config.dataTableButtons["new"].text + css: gui.config.dataTableButtons["new"].css + id: menuId + tableId: tableId + columns: columns + menu: ordered + ) + else + btn = + sExtends: "text" + sButtonText: gui.config.dataTableButtons["new"].text + sButtonClass: gui.config.dataTableButtons["new"].css + fnClick: clickHandlerFor(tblParams.onNew, "new", true) + when "edit" + if self.rest.permission() >= api.permissions.MANAGEMENT btn = sExtends: "text" - sButtonText: gui.config.dataTableButtons["new"].text - sButtonClass: gui.config.dataTableButtons["new"].css - fnClick: clickHandlerFor(tblParams.onNew, "new", true) - when "edit" - btn = - sExtends: "text" - sButtonText: gui.config.dataTableButtons.edit.text - fnSelect: editSelected - fnClick: clickHandlerFor(tblParams.onEdit, "edit") - sButtonClass: gui.config.dataTableButtons.edit.css + sButtonText: gui.config.dataTableButtons.edit.text + fnSelect: editSelected + fnClick: clickHandlerFor(tblParams.onEdit, "edit") + sButtonClass: gui.config.dataTableButtons.edit.css when "delete" - btn = - sExtends: "text" - sButtonText: gui.config.dataTableButtons["delete"].text - fnSelect: deleteSelected - fnClick: clickHandlerFor(tblParams.onDelete, "delete") - sButtonClass: gui.config.dataTableButtons["delete"].css + if self.rest.permission() >= api.permissions.MANAGEMENT + btn = + sExtends: "text" + sButtonText: gui.config.dataTableButtons["delete"].text + fnSelect: deleteSelected + fnClick: clickHandlerFor(tblParams.onDelete, "delete") + sButtonClass: gui.config.dataTableButtons["delete"].css when "refresh" btn = sExtends: "text" @@ -329,7 +333,7 @@ fnClick: refreshFnc sButtonClass: gui.config.dataTableButtons.refresh.css when "permissions" - if api.config.admin + if self.rest.permission() == api.permissions.ALL btn = sExtends: "text" sButtonText: gui.config.dataTableButtons.permissions.text @@ -346,33 +350,35 @@ # End export to excell sButtonClass: gui.config.dataTableButtons.xls.css else # Custom button, this has to be - try - css = ((if value.css then value.css + " " else "")) + gui.config.dataTableButtons.custom.css - btn = - sExtends: "text" - sButtonText: value.text - sButtonClass: css + perm = if value.permission? then value.permission else api.permissions.NONE + if self.rest.permission() >= perm + try + css = ((if value.css then value.css + " " else "")) + gui.config.dataTableButtons.custom.css + btn = + sExtends: "text" + sButtonText: value.text + sButtonClass: css - if value.click - btn.fnClick = (btn) -> - tbl = $("#" + tableId).dataTable() - val = @fnGetSelectedData()[0] - setTimeout (-> - value.click val, value, btn, tbl, refreshFnc + if value.click + btn.fnClick = (btn) -> + tbl = $("#" + tableId).dataTable() + val = @fnGetSelectedData()[0] + setTimeout (-> + value.click val, value, btn, tbl, refreshFnc + return + ), 0 return - ), 0 - return - if value.select - btn.fnSelect = (btn) -> - tbl = $("#" + tableId).dataTable() - val = @fnGetSelectedData()[0] - setTimeout (-> - value.select val, value, btn, tbl, refreshFnc + if value.select + btn.fnSelect = (btn) -> + tbl = $("#" + tableId).dataTable() + val = @fnGetSelectedData()[0] + setTimeout (-> + value.select val, value, btn, tbl, refreshFnc + return + ), 0 return - ), 0 - return - catch e - gui.doLog "Button", value, e + catch e + gui.doLog "Button", value, e btns.push btn if btn return diff --git a/server/src/uds/static/adm/js/gui-permissions.coffee b/server/src/uds/static/adm/js/gui-permissions.coffee index 547224e1e..fa81a9141 100644 --- a/server/src/uds/static/adm/js/gui-permissions.coffee +++ b/server/src/uds/static/adm/js/gui-permissions.coffee @@ -1,4 +1,18 @@ gui.permissions = (val, rest, tbl, refreshFnc) -> + baseId = gui.genRamdonId('perms-') + + fillSelect = (perms, forUser) -> + $select = $('#' + baseId + (if forUser then '_user_select' else '_group_select')) + $select.empty() + + padRight = (str, len)-> + numPads = len - str.length + if (numPads > 0) then str + Array(numPads+1).join(' ') else str + + for item in perms + if (forUser is true and item.type is 'user') or (forUser is false and item.type is 'group') + $select.append('