diff --git a/evennia/commands/cmdhandler.py b/evennia/commands/cmdhandler.py index aaa4bcdd85..02a31720e4 100644 --- a/evennia/commands/cmdhandler.py +++ b/evennia/commands/cmdhandler.py @@ -250,7 +250,9 @@ def _progressive_cmd_run(cmd, generator, response=None): if isinstance(value, (int, float)): utils.delay(value, _progressive_cmd_run, cmd, generator) elif isinstance(value, str): - _GET_INPUT(cmd.caller, value, _process_input, cmd=cmd, generator=generator) + _GET_INPUT( + cmd.caller, value, _process_input, session=cmd.session, cmd=cmd, generator=generator + ) else: raise ValueError("unknown type for a yielded value in command: {}".format(type(value))) @@ -698,7 +700,10 @@ def cmdhandler( # Parse the input string and match to available cmdset. # This also checks for permissions, so all commands in match # are commands the caller is allowed to call. - matches = yield _COMMAND_PARSER(raw_string, cmdset, caller) + try: + matches = yield _COMMAND_PARSER(raw_string, cmdset, caller, session=session) + except TypeError: + matches = yield _COMMAND_PARSER(raw_string, cmdset, caller) # Deal with matches diff --git a/evennia/commands/cmdparser.py b/evennia/commands/cmdparser.py index 11d8e067be..0c321a7b12 100644 --- a/evennia/commands/cmdparser.py +++ b/evennia/commands/cmdparser.py @@ -111,7 +111,7 @@ def try_num_differentiators(raw_string): return None, None -def cmdparser(raw_string, cmdset, caller, match_index=None): +def cmdparser(raw_string, cmdset, caller, match_index=None, session=None, **kwargs): """ This function is called by the cmdhandler once it has gathered and merged all valid cmdsets valid for this particular parsing. @@ -166,7 +166,7 @@ def cmdparser(raw_string, cmdset, caller, match_index=None): matches = build_matches(raw_string, cmdset, include_prefixes=False) # only select command matches we are actually allowed to call. - matches = [match for match in matches if match[2].access(caller, "cmd")] + matches = [match for match in matches if match[2].access(caller, "cmd", session=session)] # try to bring the number of matches down to 1 if len(matches) > 1: diff --git a/evennia/commands/command.py b/evennia/commands/command.py index 8931442bb4..c3bbe08b0a 100644 --- a/evennia/commands/command.py +++ b/evennia/commands/command.py @@ -16,7 +16,7 @@ from django.utils.text import slugify from evennia.locks.lockhandler import LockHandler from evennia.utils.ansi import ANSIString from evennia.utils.evtable import EvTable -from evennia.utils.utils import fill, is_iter, lazy_property, make_iter +from evennia.utils.utils import is_iter, lazy_property, make_iter CMD_IGNORE_PREFIXES = settings.CMD_IGNORE_PREFIXES _RE_CMD_LOCKFUNC_IN_LOCKSTRING = re.compile(r"(^|;|\s)cmd\:\w+", re.DOTALL) @@ -383,7 +383,7 @@ class Command(metaclass=CommandMeta): return k, v return None, None - def access(self, srcobj, access_type="cmd", default=False): + def access(self, srcobj, access_type="cmd", default=False, session=None): """ This hook is called by the cmdhandler to determine if srcobj is allowed to execute this command. It should return a boolean @@ -395,9 +395,10 @@ class Command(metaclass=CommandMeta): access_type (str, optional): The lock type to check. default (bool, optional): The fallback result if no lock of matching `access_type` is found on this Command. + session (Session, optional): The session to pass to lock functions. """ - return self.lockhandler.check(srcobj, access_type, default=default) + return self.lockhandler.check(srcobj, access_type, default=default, session=session) def msg(self, text=None, to_obj=None, from_obj=None, session=None, **kwargs): """ @@ -595,7 +596,7 @@ Command \"{cmdname}\" has no defined `func()` method. Available properties on th "help-entry-detail", kwargs={"category": slugify(self.help_category), "topic": slugify(self.key)}, ) - except Exception as e: + except Exception: return "#" def web_get_admin_url(self): diff --git a/evennia/commands/default/help.py b/evennia/commands/default/help.py index dbdfe65374..95ec0f3dc3 100644 --- a/evennia/commands/default/help.py +++ b/evennia/commands/default/help.py @@ -421,7 +421,9 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): cmdset.make_unique(caller) # retrieve all available commands and database / file-help topics. # also check the 'cmd:' lock here - cmd_help_topics = [cmd for cmd in cmdset if cmd and cmd.access(caller, "cmd")] + cmd_help_topics = [ + cmd for cmd in cmdset if cmd and cmd.access(caller, "cmd", session=self.session) + ] # get all file-based help entries, checking perms file_help_topics = {topic.key.lower().strip(): topic for topic in FILE_HELP_ENTRIES.all()} # get db-based help entries, checking perms diff --git a/evennia/game_template/server/conf/cmdparser.py b/evennia/game_template/server/conf/cmdparser.py index 831990a89d..cd3a727344 100644 --- a/evennia/game_template/server/conf/cmdparser.py +++ b/evennia/game_template/server/conf/cmdparser.py @@ -32,7 +32,7 @@ your settings file: """ -def cmdparser(raw_string, cmdset, caller, match_index=None): +def cmdparser(raw_string, cmdset, caller, match_index=None, **kwargs): """ This function is called by the cmdhandler once it has gathered and merged all valid cmdsets valid for this particular parsing. diff --git a/evennia/locks/lockfuncs.py b/evennia/locks/lockfuncs.py index 4121107b22..8f77bcb6f0 100644 --- a/evennia/locks/lockfuncs.py +++ b/evennia/locks/lockfuncs.py @@ -518,14 +518,13 @@ def is_ooc(accessing_obj, accessed_obj, *args, **kwargs): account = obj.account if utils.inherits_from(obj, evennia.DefaultObject) else obj if not account: return True - try: - session = accessed_obj.session - except AttributeError: - # note-this doesn't work well - # for high multisession mode. We may need - # to change to sessiondb to resolve this - sessions = session = account.sessions.get() - session = sessions[0] if sessions else None + session = kwargs.get("session", None) + if session is None: + try: + session = accessed_obj.session + except AttributeError: + sessions = account.sessions.get() + session = sessions[0] if sessions else None if not session: # this suggests we are not even logged in; treat as ooc. return True diff --git a/evennia/locks/lockhandler.py b/evennia/locks/lockhandler.py index eba0e64509..97da785294 100644 --- a/evennia/locks/lockhandler.py +++ b/evennia/locks/lockhandler.py @@ -363,7 +363,7 @@ class LockHandler: access_type, rhs = [part.strip() for part in lockdef.split(":", 1)] if not access_type: err = _( - "Lock: '{lockdef}' has no access_type " "(left-side of colon is empty)." + "Lock: '{lockdef}' has no access_type (left-side of colon is empty)." ).format(lockdef=lockdef) if validate_only: return False, err @@ -514,13 +514,15 @@ class LockHandler: """ old_lockstring = self.get(access_type) - if not lockstring.strip().lower() in old_lockstring.lower(): + if lockstring.strip().lower() not in old_lockstring.lower(): lockstring = "{old} {op} {new}".format( old=old_lockstring, op=op, new=lockstring.strip() ) self.add(lockstring) - def check(self, accessing_obj, access_type, default=False, no_superuser_bypass=False): + def check( + self, accessing_obj, access_type, default=False, no_superuser_bypass=False, session=None + ): """ Checks a lock of the correct type by passing execution off to the lock function(s). @@ -580,7 +582,16 @@ class LockHandler: evalstring, func_tup, raw_string = self.locks[access_type] # execute all lock funcs in the correct order, producing a tuple of True/False results. true_false = tuple( - bool(tup[0](accessing_obj, self.obj, *tup[1], access_type=access_type, **tup[2])) + bool( + tup[0]( + accessing_obj, + self.obj, + *tup[1], + access_type=access_type, + session=session, + **tup[2], + ) + ) for tup in func_tup ) # the True/False tuple goes into evalstring, which combines them diff --git a/evennia/locks/tests.py b/evennia/locks/tests.py index ba3a509f6a..978ffcb903 100644 --- a/evennia/locks/tests.py +++ b/evennia/locks/tests.py @@ -7,13 +7,15 @@ the stability and integrity of the codebase during updates. This module tests the lock functionality of Evennia. """ + +from evennia.server.serversession import ServerSession from evennia.utils.test_resources import BaseEvenniaTest try: # this is a special optimized Django version, only available in current Django devel - from django.utils.unittest import TestCase, override_settings + from django.utils.unittest import override_settings except ImportError: - from django.test import TestCase, override_settings + from django.test import override_settings from evennia import settings_default from evennia.locks import lockfuncs @@ -220,6 +222,19 @@ class TestLockfuncs(BaseEvenniaTest): self.account.unpuppet_all() self.assertEqual(True, lockfuncs.is_ooc(self.account, self.char1)) + def test_is_ooc__multi_session_mixed(self): + """Test that two sessions on the same account can have different IC/OOC states.""" + ic_session = self.session + + # Create a bare unpuppeted session. Full login flow steals the puppet from ic_session in + # MULTISESSION_MODE 0. + ooc_session = ServerSession() + ooc_session.sessid = 2 + ooc_session.puppet = None + + self.assertFalse(lockfuncs.is_ooc(self.account, self.char1, session=ic_session)) + self.assertTrue(lockfuncs.is_ooc(self.account, self.char1, session=ooc_session)) + class TestPermissionCheck(BaseEvenniaTest): """