Needs more testing, but permission delegation seems to work right now.

This commit is contained in:
Adolfo Gómez García 2015-03-05 15:20:46 +01:00
parent 6387629e7e
commit 2435f589b9
24 changed files with 663 additions and 539 deletions

View File

@ -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):

View File

@ -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??

View File

@ -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)

View File

@ -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)
}

View File

@ -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:

View File

@ -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,15 +116,16 @@ class Permissions(Handler):
'''
Processes post requests
'''
if len(self._args) != 4:
raise RequestError('Invalid request')
logger.debug('Put args: {}'.format(self._args))
la = len(self._args)
if la == 5 and self._args[3] == 'add':
perm = {
'0': permissions.PERMISSION_NONE,
'1': permissions.PERMISSION_READ,
'2': permissions.PERMISSION_ALL
'2': permissions.PERMISSION_MANAGEMENT,
'3': permissions.PERMISSION_ALL
}.get(self._params.get('perm', '0'), permissions.PERMISSION_NONE)
cls = Permissions.getClass(self._args[0])
@ -131,12 +133,19 @@ class Permissions(Handler):
obj = cls.objects.get(uuid=self._args[1])
if self._args[2] == 'users':
user = User.objects.get(uuid=self._args[3])
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[3])
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('Invalid request')

View File

@ -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]:

View File

@ -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))

View File

@ -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:

View File

@ -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):

View File

@ -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:
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)

View File

@ -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

View File

@ -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

View File

@ -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'))

View File

@ -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
@ -338,12 +351,14 @@ class DetailModelRestApi extends BasicModelRest
].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

View File

@ -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"

View File

@ -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"))

View File

@ -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"))

View File

@ -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
@ -86,7 +88,7 @@ gui.providers.link = (event) ->
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) ->

View File

@ -79,14 +79,6 @@ 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
return
gui.doLog "Available services", availableServices
api.templates.get "services_pool", (tmpl) ->
gui.appendToWorkspace api.templates.evaluate(tmpl,
deployed_services: "deployed-services-placeholder"
@ -120,6 +112,7 @@ gui.servicesPools.link = (event) ->
"edit"
"delete"
"xls"
"permissions"
]
onRowDeselect: ->
clearDetails()
@ -131,16 +124,13 @@ gui.servicesPools.link = (event) ->
clearDetails()
service = null
try
service = availableServices[servPool.service_id]
info = servPool.info
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
#
# * Cache Part
@ -149,9 +139,9 @@ gui.servicesPools.link = (event) ->
# If service does not supports cache, do not show it
# Shows/hides cache
if service.info.uses_cache or service.info.uses_cache_l2
if info.uses_cache or info.uses_cache_l2
$("#cache-placeholder_tab").removeClass "hidden"
cachedItems = new GuiElement(api.servicesPools.detail(servPool.id, "cache"), "cache")
cachedItems = new GuiElement(api.servicesPools.detail(servPool.id, "cache", { permission: servPool.permission }), "cache")
# Cached items table
prevCacheLogTbl = null
@ -190,9 +180,9 @@ gui.servicesPools.link = (event) ->
groups = null
# Shows/hides groups
if service.info.must_assign_manually is false
if info.must_assign_manually is false
$("#groups-placeholder_tab").removeClass "hidden"
groups = new GuiElement(api.servicesPools.detail(servPool.id, "groups"), "groups")
groups = new GuiElement(api.servicesPools.detail(servPool.id, "groups", { permission: servPool.permission }), "groups")
# Groups items table
groupsTable = groups.table(
@ -268,11 +258,11 @@ gui.servicesPools.link = (event) ->
# * Assigned services part
#
prevAssignedLogTbl = null
assignedServices = new GuiElement(api.servicesPools.detail(servPool.id, "services"), "services")
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 service.info.must_assign_manually then [
buttons: (if info.must_assign_manually then [
"new"
"delete"
"xls"
@ -292,12 +282,12 @@ gui.servicesPools.link = (event) ->
return
onRowSelect: (selected) ->
service = selected[0]
svr = selected[0]
if prevAssignedLogTbl
$tbl = $(prevAssignedLogTbl).dataTable()
$tbl.fnClearTable()
$tbl.fnDestroy()
prevAssignedLogTbl = assignedServices.logTable(service.id,
prevAssignedLogTbl = assignedServices.logTable(svr.id,
container: "assigned-services-placeholder_log"
)
return
@ -311,7 +301,7 @@ gui.servicesPools.link = (event) ->
#
# * Transports part
#
transports = new GuiElement(api.servicesPools.detail(servPool.id, "transports"), "transports")
transports = new GuiElement(api.servicesPools.detail(servPool.id, "transports", { permission: servPool.permission }), "transports")
# Transports items table
transportsTable = transports.table(
@ -367,10 +357,10 @@ gui.servicesPools.link = (event) ->
# * Publications part
#
publications = null
if service.info.needs_publication
if info.needs_publication
$("#publications-placeholder_tab").removeClass "hidden"
pubApi = api.servicesPools.detail(servPool.id, "publications")
publications = new GuiElement(pubApi, "publications")
publications = new GuiElement(pubApi, "publications", { permission: servPool.permission })
# Publications table
publicationsTable = publications.table(
@ -446,20 +436,14 @@ gui.servicesPools.link = (event) ->
$.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 = "<span class=\"fa fa-exclamation text-danger\"></span> " + value.name
value.state = gettext("Restrained")
value.name = "<span style=\"" + style + "\"></span> " + value.name
value.parent = service.name
catch e
value.name = "<span class=\"fa fa-asterisk text-alert\"></span> " + value.name
value.parent = gettext("unknown (needs reload)")
return
return
@ -486,7 +470,4 @@ gui.servicesPools.link = (event) ->
onDelete: gui.methods.del(gui.servicesPools, gettext("Delete") + " service pool", "Service pool " + gettext("deletion error"))
)
return
return
return

View File

@ -276,7 +276,9 @@
$.each tblParams.buttons, (index, value) -> # Iterate through button definition
btn = null
switch value
when "new"
if self.rest.permission() >= api.permissions.MANAGEMENT
if Object.keys(self.types).length isnt 0
menuId = gui.genRamdonId("dd-")
ordered = []
@ -309,6 +311,7 @@
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.edit.text
@ -316,6 +319,7 @@
fnClick: clickHandlerFor(tblParams.onEdit, "edit")
sButtonClass: gui.config.dataTableButtons.edit.css
when "delete"
if self.rest.permission() >= api.permissions.MANAGEMENT
btn =
sExtends: "text"
sButtonText: gui.config.dataTableButtons["delete"].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,6 +350,8 @@
# End export to excell
sButtonClass: gui.config.dataTableButtons.xls.css
else # Custom button, this has to be
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 =

View File

@ -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('&nbsp;') 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('<option value="' + item.id + '">' + padRight(item.auth_name + '\\' + item.entity_name, 28) + '&nbsp;| ' + item.perm_name)
addModal = (forUser) ->
if forUser
@ -39,14 +53,18 @@ gui.permissions = (val, rest, tbl, refreshFnc) ->
if auth is -1 or item is -1
gui.notify gettext("You must provide authenticator and") + " " + label, "danger"
else # Save & close modal
rest.addPermission val.id, items, item, perm
rest.addPermission val.id, items, item, perm, (
(perms) ->
$(modalId).modal "hide"
fillSelect perms, forUser
)
return
# Makes form "beautyfull" :-)
gui.tools.applyCustoms modalId
return
delModal = (forUser, selectedItems) ->
if forUser
label = gettext('User')
@ -64,30 +82,19 @@ gui.permissions = (val, rest, tbl, refreshFnc) ->
gui.doLog modalId
$(modalId + ' .button-revoke').on('click', () ->
rest.revokePermissions val.id, items, toDel
rest.revokePermissions toDel, (
(perms) ->
$(modalId).modal "hide"
for v in selectedItems
$(v).remove()
)
)
fillSelect = (baseId, 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('&nbsp;') 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('<option value="' + item.id + '">' + padRight(item.auth_name + '\\' + item.name, 28) + '&nbsp;| ' + item.perm_name)
api.templates.get "permissions", (tmpl) ->
rest.getPermissions val.id, (perms) ->
id = gui.genRamdonId('perms-')
content = api.templates.evaluate(tmpl,
id: id
id: baseId
perms: perms
)
modalId = gui.launchModal gettext("Permissions for") + " " + val.name, content,
@ -95,31 +102,31 @@ gui.permissions = (val, rest, tbl, refreshFnc) ->
closeButton: '<button type="button" class="btn btn-default" data-dismiss="modal">Ok</button>'
# Fills user select
fillSelect id, perms, true
fillSelect id, perms, false
fillSelect perms, true
fillSelect perms, false
$('#' + id + '_user_del').on('click', () ->
$select = $('#' + id + '_user_select')
$('#' + baseId + '_user_del').on('click', () ->
$select = $('#' + baseId + '_user_select')
selected = $select.find(":selected")
return if selected.length is 0
delModal true, selected
)
$('#' + id + '_user_add').on('click', () ->
$('#' + baseId + '_user_add').on('click', () ->
addModal yes
)
$('#' + id + '_group_del').on('click', () ->
$select = $('#' + id + '_group_select')
$('#' + baseId + '_group_del').on('click', () ->
$select = $('#' + baseId + '_group_select')
selected = $select.find(":selected")
return if selected.length is 0
delModal false, selected
)
$('#' + id + '_group_add').on('click', () ->
$('#' + baseId + '_group_add').on('click', () ->
addModal no
)

View File

@ -5,7 +5,7 @@
<div class="col-md-6 column">
<div class="form-group">
<label for="{{ id }}_select">{% endverbatim %}{% trans 'Users' %}{% verbatim %}</label>
<select class="form-control" multiple size="8" id="{{ id }}_user_select" style='font-family: "Courier New"'>
<select class="form-control" multiple size="12" id="{{ id }}_user_select" style='font-family: "Courier New"'>
</select>
</div>
<div class="form-group">
@ -20,7 +20,7 @@
<div class="col-md-6 column">
<div class="form-group">
<label for="{{ id }}_select">{% endverbatim %}{% trans 'Groups' %}{% verbatim %}</label>
<select class="form-control" multiple size="8" id="{{ id }}_group_select" style='font-family: "Courier New"'>
<select class="form-control" multiple size="12" id="{{ id }}_group_select" style='font-family: "Courier New"'>
</select>
</div>
<div class="form-group">

View File

@ -25,8 +25,9 @@
<label for="id_perm_select" class="col-sm-2 control-label">{% endverbatim %}{% trans 'Permission' %}{% verbatim %}</label>
<div class="col-sm-10">
<select id="id_perm_select" name="group" class="selectpicker show-menu-arrow show-tick modal_field_data" data-style="btn-default" data-width="100%">
<option value="1">Read only</option>
<option value="2">All Access</option>
<option value="1">{% endverbatim %}{% trans 'Read only' %}{% verbatim %}</option>
<option value="2">{% endverbatim %}{% trans 'Management Access' %}{% verbatim %}</option>
<option value="2">{% endverbatim %}{% trans 'Full Access' %}{% verbatim %}</option>
</select>
</div>
</div>