diff --git a/contrib/misc_commands.py b/contrib/misc_commands.py index d2d3898a3e..c1cf4830af 100644 --- a/contrib/misc_commands.py +++ b/contrib/misc_commands.py @@ -21,19 +21,32 @@ from src.commands.default.muxcommand import MuxCommand PERMISSION_HIERARCHY = settings.PERMISSION_HIERARCHY PERMISSION_HIERARCHY_LOWER = [perm.lower() for perm in PERMISSION_HIERARCHY] -class CmdQuell(MuxCommand): +class CmdQuell(MuxPlayerCommand): """ Quelling permissions Usage: - quell [=permission level] + quell + unquell + quell/permlevel + + Normally the permission level of the Player is used when puppeting a + Character/Object to determine access. Giving this command without + arguments will instead switch the lock system to make use of the + puppeted Object's permissions instead. Note that this only works DOWNWARDS - + a Player cannot use a higher-permission Character to escalate their Player + permissions for example. Use the unquell command to revert this state. + + Note that the superuser character is unaffected by full quelling. Use a separate + admin account for testing. + + When given an argument, the argument is considered a command to execute with + a different (lower) permission level than they currently have. This is useful + for quick testing. If no permlevel switch is given, the command will be + executed using the lowest permission level available in settings.PERMISSION_HIERARCHY. + + Quelling singular commands will work also for the superuser. - This is an admin command that allows to execute another command as - another (lower) permission level than what you currently - have. This is useful for testing. Also superuser flag will be - deactivated by this command. If no permission level is given, - the command will be executed as the lowest level available in - settings.PERMISSION_HIERARCHY. """ key = "quell" @@ -43,12 +56,30 @@ class CmdQuell(MuxCommand): def func(self): "Perform the command" + player = self.caller + if not self.args: - self.caller.msg("Usage: quell [=permission level]") - return + # try to fully quell player permissions + if self.cmdstring == 'unquell': + if player.get_attribute('_quell'): + self.msg("You are not currently quelling you Player permissions.") + else: + player.del_attribute('_quell') + self.msg("You are now using your Player permissions normally.") + return + else: + if player.is_superuser: + self.msg("Superusers cannot be quelled.") + return + if player.get_attribute('_quell'): + self.msg("You are already quelling your Player permissions.") + return + player.set_attribute('_quell', True) + self.msg("You quell your Player permissions.") + return cmd = self.lhs - perm = self.rhs + perm = self.switches and self.switches[0] or None if not PERMISSION_HIERARCHY: self.caller.msg("settings.PERMISSION_HIERARCHY is not defined. Add a hierarchy to use this command.") diff --git a/src/commands/default/building.py b/src/commands/default/building.py index dbb73a692b..9320935818 100644 --- a/src/commands/default/building.py +++ b/src/commands/default/building.py @@ -1116,6 +1116,7 @@ class CmdSetAttribute(ObjManipCommand): @set / = @set / = @set / + @set */attr = Sets attributes on objects. The second form clears a previously set attribute while the last form @@ -1214,7 +1215,10 @@ class CmdSetAttribute(ObjManipCommand): objname = self.lhs_objattr[0]['name'] attrs = self.lhs_objattr[0]['attrs'] - obj = caller.search(objname) + if objname.startswith('*'): + obj = caller.search_player(objname.lstrip('*')) + else: + obj = caller.search(objname) if not obj: return @@ -1682,7 +1686,7 @@ class CmdExamine(ObjManipCommand): self.player_mode = "player" in self.switches or obj_name.startswith('*') if self.player_mode: - obj = self.search_player(obj_name) + obj = caller.search_player(obj_name.lstrip('*')) else: obj = caller.search(obj_name) if not obj: diff --git a/src/commands/default/cmdset_player.py b/src/commands/default/cmdset_player.py index 29e520721d..e448c2fca7 100644 --- a/src/commands/default/cmdset_player.py +++ b/src/commands/default/cmdset_player.py @@ -35,6 +35,7 @@ class PlayerCmdSet(CmdSet): self.add(player.CmdQuit()) self.add(player.CmdPassword()) self.add(player.CmdColorTest()) + self.add(player.CmdQuell()) # testing self.add(building.CmdExamine()) diff --git a/src/commands/default/player.py b/src/commands/default/player.py index c458d94325..feb88a9b95 100644 --- a/src/commands/default/player.py +++ b/src/commands/default/player.py @@ -31,6 +31,9 @@ __all__ = ("CmdOOCLook", "CmdIC", "CmdOOC", "CmdPassword", "CmdQuit", MAX_NR_CHARACTERS = MULTISESSION_MODE < 2 and 1 or MAX_NR_CHARACTERS BASE_PLAYER_TYPECLASS = settings.BASE_PLAYER_TYPECLASS +PERMISSION_HIERARCHY = settings.PERMISSION_HIERARCHY +PERMISSION_HIERARCHY_LOWER = [perm.lower() for perm in PERMISSION_HIERARCHY] + class CmdOOCLook(MuxPlayerCommand): """ ooc look @@ -601,3 +604,51 @@ class CmdColorTest(MuxPlayerCommand): # malformed input self.msg("Usage: @color ansi|xterm256") +class CmdQuell(MuxPlayerCommand): + """ + Quelling permissions + + Usage: + quell + unquell + + Normally the permission level of the Player is used when puppeting a + Character/Object to determine access. This command will switch the lock + system to make use of the puppeted Object's permissions instead. This is + useful mainly for testing. + Hierarchical permission quelling only work downwards, thus a Player cannot + use a higher-permission Character to escalate their permission level. + Use the unquell command to revert back to normal operation. + + Note that the superuser character cannot be quelled. Use a separate + admin account for testing. + """ + + key = "@quell" + aliases =["@unquell"] + locks = "cmd:all()" + help_category = "General" + + def func(self): + "Perform the command" + player = self.caller + permstr = " (%s)" % (", ".join(player.permissions)) + if player.is_superuser: + self.msg("Superusers cannot be quelled.") + return + if self.cmdstring == '@unquell': + if not player.get_attribute('_quell'): + self.msg("Already using normal Player permissions%s." % permstr) + else: + player.del_attribute('_quell') + self.msg("Player permissions restored%s." % permstr) + return + else: + if player.get_attribute('_quell'): + self.msg("Already quelling Player permissions") + return + player.set_attribute('_quell', True) + self.msg("Quelling Player permissions%s." % permstr) + return + + return diff --git a/src/locks/lockfuncs.py b/src/locks/lockfuncs.py index 693b372aec..d5c7f6ceb8 100644 --- a/src/locks/lockfuncs.py +++ b/src/locks/lockfuncs.py @@ -128,24 +128,61 @@ def perm(accessing_obj, accessed_obj, *args, **kwargs): perm() where is the permission accessing_obj must - have in order to pass the lock. If the given permission - is part of _PERMISSION_HIERARCHY, permission is also granted - to all ranks higher up in the hierarchy. + have in order to pass the lock. + + If the given permission is part of settings.PERMISSION_HIERARCHY, + permission is also granted to all ranks higher up in the hierarchy. + + If accessing_object is an Object controlled by a Player, the + permissions of the Player is used unless the PlayerAttribute _quell + is set to True on the Object. In this case however, the + LOWEST hieararcy-permission of the Player/Object-pair will be used + (this is order to avoid Players potentially escalating their own permissions + by use of a higher-level Object) + """ + # this allows the perm_above lockfunc to make use of this function too + gtmode = kwargs.pop("_greater_than", False) + try: perm = args[0].lower() - permissions = [p.lower() for p in accessing_obj.permissions] + perms_object = [p.lower() for p in accessing_obj.permissions] except (AttributeError, IndexError): return False - if perm in permissions: - # simplest case - we have a direct match + if utils.inherits_from(accessing_obj, "src.objects.objects.Object") and accessing_obj.player: + player = accessing_obj.player + perms_player = [p.lower() for p in player.permissions] + is_quell = player.get_attribute("_quell") + + if perm in _PERMISSION_HIERARCHY: + # check hierarchy without allowing escalation obj->player + hpos_target = _PERMISSION_HIERARCHY.index(perm) + hpos_player = [hpos for hpos, hperm in enumerate(_PERMISSION_HIERARCHY) if hperm in perms_player] + hpos_player = hpos_player and hpos_player[-1] or -1 + if is_quell: + hpos_object = [hpos for hpos, hperm in enumerate(_PERMISSION_HIERARCHY) if hperm in perms_object] + hpos_object = hpos_object and hpos_object[-1] or -1 + if gtmode: + return hpos_target < min(hpos_player, hpos_object) + else: + return hpos_target <= min(hpos_player, hpos_object) + elif gtmode: + return gtmode and hpos_target < hpos_player + else: + return hpos_target <= hpos_player + elif not is_quell and perm in perms_player: + # if we get here, check player perms first, otherwise continue as normal + return True + + if perm in perms_object: + # simplest case - we have direct match return True if perm in _PERMISSION_HIERARCHY: # check if we have a higher hierarchy position - ppos = _PERMISSION_HIERARCHY.index(perm) + hpos_target = _PERMISSION_HIERARCHY.index(perm) return any(1 for hpos, hperm in enumerate(_PERMISSION_HIERARCHY) - if hperm in permissions and hpos > ppos) + if hperm in perms_object and hpos_target < hpos) return False def perm_above(accessing_obj, accessed_obj, *args, **kwargs): @@ -155,20 +192,12 @@ def perm_above(accessing_obj, accessed_obj, *args, **kwargs): it's assumed we refer to superuser. If no hierarchy is defined, this function has no meaning and returns False. """ - try: - perm = args[0].lower() - except (AttributeError, IndexError): - return False - - if perm in _PERMISSION_HIERARCHY: - ppos = _PERMISSION_HIERARCHY.index(perm) - return any(1 for hpos, hperm in enumerate(_PERMISSION_HIERARCHY) - if hperm in [p.lower() for p in accessing_obj.permissions] and hpos > ppos) - return False + kwargs["_greater_than"] = True + return perm(accessing_obj,accessed_obj, *args, **kwargs) def pperm(accessing_obj, accessed_obj, *args, **kwargs): """ - The basic permission-checker for Player objects. Ignores case. + The basic permission-checker only for Player objects. Ignores case. Usage: pperm()