# -*- test-case-name: mv3d.test.net.test_securable -*- # Copyright (C) 2006-2012 Mortal Coil Games # See LICENSE for details. """ Security Classes """ import logging from twisted.python.util import mergeFunctionMetadata from mv3d.util.persist import MapAttribute, Text, List, Integer Order = {"deny first":0, "allow first":1} class AccessDenied(Exception): """ Access was denied """ class SecurableError(Exception): """ Raised when you tried to do something the Securable didn't like """ def requirePermissions(*perms): """ Specify that certain permissions are required for this method assumes argument 2 can be checked for security. """ def decorator(func): def wrapper(self, *args, **kw): for perm in perms: if not self.checkPermissions(args[0], perm): raise AccessDenied("You do not have %s permissions " "to run %s." % (perm, func.__name__)) return func(self, *args, **kw) mergeFunctionMetadata(func, wrapper) return wrapper return decorator class Client: """ Used when an account info is passed in. Converts it into an object that checkPermissions can use. """ def __init__(self, ai): assert isinstance(ai, dict) self.info = ai def getName(self): """ Return the name """ return self.info["username"] def getGroups(self): """ Returns the groups this account belongs to """ return self.info["groups"] class Securable(object): """ Securable is a class that defines subclasses as things that can be locked down with extendable security settings """ allowed = MapAttribute(Text(), List(Text())) denied = MapAttribute(Text(), List(Text())) order = Integer(default=Order["deny first"]) allowedState = ["allowed", "denied", "order"] availablepermissions = ["all", "reference", "read", "modify", "delete", "read permissions"] def __init__(self): self.setupSecurable() def setupSecurable(self): """ This method just sets up the variables that securable uses """ self.allowed = {} self.denied = {} self.order = Order["deny first"] def _securableupdateAllClients(self, w, *a, **kw): """ Call updateAllClients if we have that method """ if hasattr(self, "updateAllClients"): self.updateAllClients(w, * a, **kw) def setOrder(self, o): """ Set whether we deny or allow first """ self.order = o self._securableupdateAllClients('setOrder', o) if hasattr(self, "save"): self.queueSave(selectAttributes=["order"]) @classmethod def addPermission(cls, name): """ Add a permission type """ if name not in cls.availablepermissions: # note that this is to ensure that a copy of the list is made cls.availablepermissions = cls.availablepermissions + [name] def remPermission(self, name): """ Remote a permission """ if not isinstance(self.allowed, dict): self.setupSecurable() if not name in self.availablepermissions: raise SecurableError("There is no permission named %s" % name) self.availablepermissions.remove(name) if self.allowed.has_key(name): del self.allowed[name] self._securableupdateAllClients('remPermission', name) if hasattr(self, "save"): self.queueSave(selectAttributes=["availablepermissions"]) def grantPermission(self, permission, who): """ Grant permission to this object """ if not isinstance(self.allowed, dict): self.setupSecurable() if not permission in self.availablepermissions: raise SecurableError("There is no permission named %s" % permission) if permission in self.allowed.keys(): if not who in self.allowed[permission]: self.allowed[permission].append(who) else: self.allowed[permission] = [who] self._securableupdateAllClients('grantPermission', permission, who) if hasattr(self, "save"): self.queueSave(selectAttributes=["allowed"]) observe_grantPermission = grantPermission def denyPermission(self, permission, who): """ Deny access to this object """ if not isinstance(self.allowed, dict): self.setupSecurable() if not permission in self.availablepermissions: raise SecurableError("There is no permission named %s" % permission) if permission in self.denied.keys(): if not who in self.denied[permission]: self.denied[permission].append(who) else: self.denied[permission] = [who] self._securableupdateAllClients('denyPermission', permission, who) if hasattr(self, "save"): self.queueSave(selectAttributes=["denied"]) observe_denyPermission = denyPermission def revokePermission(self, permission, who): """ Remove a previously granted permission """ if not isinstance(self.allowed, dict): self.setupSecurable() if not permission in self.availablepermissions: raise SecurableError("There is no permission named %s" % permission) self.allowed[permission].remove(who) self._securableupdateAllClients('revokePermission', permission, who) if hasattr(self, "save"): self.queueSave(selectAttributes=["allow"]) def remDeniedPermission(self, permission, who): """ Remove a previously denied permission """ if not isinstance(self.allowed, dict): self.setupSecurable() if not permission in self.availablepermissions: raise SecurableError("There is no permission named %s" % permission) self.denied[permission].remove(who) self._securableupdateAllClients('remDeniedPermission', permission, who) if hasattr(self, "save"): self.queueSave(selectAttributes=["denied"]) def checkDenyPermissions(self, con, act): """ Check if con's user is in our denied list for the given ACTion """ if not isinstance(self.allowed, dict): self.setupSecurable() if "all" in self.denied.keys(): if con.getName() in self.denied["all"]: return True for g in con.getGroups(): if g in self.denied["all"]: return True if not act in self.denied.keys(): return False pp = self.denied[act] if "all" in pp: return True if con.getName() in pp: return True gr = con.getGroups() for g in gr: if g in pp: return True return False def checkAllowPermissions(self, con, act): """ Check if con's user is in our allowed list for the given ACTion """ if not isinstance(self.allowed, dict): self.setupSecurable() if "admin" in con.getGroups(): return True if "all" in self.allowed.keys(): if con.getName() in self.allowed["all"]: return True for g in con.getGroups(): if g in self.allowed["all"]: return True if not act in self.allowed.keys(): return False pp = self.allowed[act] if "all" in pp: return True if con.getName() in pp: return True gr = con.getGroups() for g in gr: if g in pp: return True return False def checkPermissions(self, con, act, raiseException=False): """ Returns true if con is allowed to perform act on this object. If raiseException is set, then an AccessDenied exception is raised if the permission is not allowed. """ if not isinstance(self.allowed, dict): self.setupSecurable() if isinstance(con, dict): con = Client(con) if hasattr(self, "server"): svr = self.server elif hasattr(self, "log"): svr = self elif hasattr(self, "client"): svr = self.client elif hasattr(self, "parent"): svr = self.parent else: svr = None if not act in self.availablepermissions: raise SecurableError("There is no permission named %s" % act) if self.order == Order["deny first"]: if self.checkDenyPermissions(con, act): if svr is not None: svr.log("%s access denied for %s to %s" % (act, con, self.__class__.__name__), logging.WARNING) if raiseException: raise AccessDenied("%s access denied for %s to %s" % (act, con, self.__class__.__name__)) return False allowed = self.checkAllowPermissions(con, act) if raiseException and not allowed: raise AccessDenied("%s access denied for %s to %s" % (act, con, self.__class__.__name__)) return allowed if self.order == Order["allow first"]: if self.checkAllowPermissions(con, act): return True allowed = not self.checkDenyPermissions(con, act) if raiseException and not allowed: raise AccessDenied("%s access denied for %s to %s" % (act, con, self.__class__.__name__)) return allowed if svr is not None: svr.log("%s access denied for %s to %s" % (act, con, self.__class__.__name__), logging.WARNING) if raiseException: raise AccessDenied("%s access denied for %s to %s" % (act, con, self.__class__.__name__)) return False