Add locks to filehelp entities. Test view/read locks

This commit is contained in:
Griatch 2021-05-30 10:59:36 +02:00
parent 905cc78069
commit 0fb29073b2
3 changed files with 99 additions and 59 deletions

View file

@ -259,46 +259,46 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
return help_index return help_index
def check_show_help(self, cmd, caller): def check_show_help(self, cmd_or_topic, caller):
""" """
Helper method. If this return True, the given cmd Helper method. If this return True, the given help topic
auto-help will be viewable in the help listing. be viewable in the help listing. Note that even if this returns False,
Override this to easily select what is shown to the entry will still be visible in the help index unless `should_list_topic`
the account. Note that only commands available is also returning False.
in the caller's merged cmdset are available.
Args: Args:
cmd (Command): Command class from the merged cmdset cmd_or_topic (Command, HelpEntry or FileHelpEntry): The topic/command to test.
caller (Character, Account or Session): The current caller caller: the caller checking for access.
executing the help command.
Returns:
bool: If command can be viewed or not.
""" """
# return only those with auto_help set and passing the cmd: lock if inherits_from(cmd_or_topic, "evennia.commands.command.Command"):
return cmd.auto_help and cmd.access(caller) return cmd_or_topic.auto_help and cmd_or_topic.access(caller)
else:
return cmd_or_topic.access(caller, 'read', default=True)
def should_list_cmd(self, cmd, caller): def should_list_topic(self, cmd_or_topic, caller):
""" """
Should the specified command appear in the help table? Should the specified command appear in the help table?
This method only checks whether a specified command should This method only checks whether a specified command should appear in the table of
appear in the table of topics/commands. The command can be topics/commands. The command can be used by the caller (see the 'check_show_help' method)
used by the caller (see the 'check_show_help' method) and and the command will still be available, for instance, if a character type 'help name of the
the command will still be available, for instance, if a command'. However, if you return False, the specified command will not appear in the table.
character type 'help name of the command'. However, if This is sometimes useful to "hide" commands in the table, but still access them through the
you return False, the specified command will not appear in help system.
the table. This is sometimes useful to "hide" commands in
the table, but still access them through the help system.
Args: Args:
cmd: the command to be tested. cmd_or_topic (Command, HelpEntry or FileHelpEntry): The topic/command to test.
caller: the caller of the help system. caller: the caller checking for access.
Return: Returns:
True: the command should appear in the table. bool: If command should be listed or not.
False: the command shouldn't appear in the table.
""" """
return True return cmd_or_topic.access(caller, 'view', default=True)
def parse(self): def parse(self):
""" """
@ -336,39 +336,49 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
cmdset.make_unique(caller) cmdset.make_unique(caller)
# retrieve all available commands and database / file-help topics # retrieve all available commands and database / file-help topics
all_cmds = [cmd for cmd in cmdset if self.check_show_help(cmd, caller)] all_cmd_topics = [cmd for cmd in cmdset if self.check_show_help(cmd, caller) if cmd]
# we group the file-help topics with the db ones, giving the db ones priority # get all file-based help entries, checking perms
file_help_topics = FILE_HELP_ENTRIES.all(return_dict=True) file_help_topics = {
db_topics = { topic.key.lower().strip(): topic
topic.key.lower().strip(): topic for topic in HelpEntry.objects.all() for topic in FILE_HELP_ENTRIES.all()
if topic.access(caller, "view", default=True) if topic.access(caller)
} }
all_db_topics = list({**file_help_topics, **db_topics}.values()) # get db-based help entries, checking perms
db_topics = {
topic.key.lower().strip(): topic
for topic in HelpEntry.objects.all()
if topic.access(caller)
}
# merge so db topics override file topics with same key
all_file_db_topics = list({**file_help_topics, **db_topics}.values())
# get all categories
all_categories = list(set( all_categories = list(set(
[HelpCategory(cmd.help_category) for cmd in all_cmds] [HelpCategory(cmd.help_category) for cmd in all_cmd_topics]
+ [HelpCategory(topic.help_category) for topic in all_db_topics] + [HelpCategory(topic.help_category) for topic in all_file_db_topics]
)) ))
if not query: if not query:
# list all available help entries, grouped by category. We want to # list all available help entries, grouped by category. We want to
# build dictionaries {category: [topic, topic, ...], ...} # build dictionaries {category: [topic, topic, ...], ...}
cmd_help_dict = defaultdict(list) cmd_help_dict = defaultdict(list)
db_help_dict = defaultdict(list) file_db_help_dict = defaultdict(list)
# Filter commands that should be reached by the help # Filter commands/topics that should be reached by the help
# system, but not be displayed in the table, or be displayed differently. # system, but not be displayed in the table, or be displayed differently.
for cmd in all_cmds: for cmd in all_cmd_topics:
if self.should_list_cmd(cmd, caller): if self.should_list_topic(cmd, caller):
key = (cmd.auto_help_display_key key = (
if hasattr(cmd, "auto_help_display_key") else cmd.key) cmd.auto_help_display_key
if hasattr(cmd, "auto_help_display_key") else cmd.key
)
cmd_help_dict[cmd.help_category].append(key) cmd_help_dict[cmd.help_category].append(key)
for topic in all_file_db_topics:
if self.should_list_topic(topic, caller):
file_db_help_dict[topic.help_category].append(topic.key)
for db_topic in all_db_topics: output = self.format_help_index(cmd_help_dict, file_db_help_dict)
db_help_dict[db_topic.help_category].append(db_topic.key)
output = self.format_help_index(cmd_help_dict, db_help_dict)
self.msg_help(output) self.msg_help(output)
return return
@ -376,8 +386,8 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
# We have a query - try to find a specific topic/category using the # We have a query - try to find a specific topic/category using the
# Lunr search engine # Lunr search engine
# all available options # all available help options - will be searched in order
entries = [cmd for cmd in all_cmds if cmd] + all_db_topics + all_categories entries = all_cmd_topics + all_file_db_topics + all_categories
# lunr search fields/boosts # lunr search fields/boosts
search_fields = [ search_fields = [
@ -443,14 +453,14 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
{ {
match.key: [ match.key: [
cmd.key cmd.key
for cmd in all_cmds for cmd in all_cmd_topics
if match.key.lower() == cmd.help_category if match.key.lower() == cmd.help_category
] ]
}, },
{ {
match.key: [ match.key: [
topic.key topic.key
for topic in all_db_topics for topic in all_file_db_topics
if match.key.lower() == topic.help_category if match.key.lower() == topic.help_category
] ]
}, },

View file

@ -13,12 +13,13 @@ Each help-entry dict is on the form
:: ::
{'key': <str>, {'key': <str>,
'text': <str>,
'category': <str>, # optional, otherwise settings.DEFAULT_HELP_CATEGORY 'category': <str>, # optional, otherwise settings.DEFAULT_HELP_CATEGORY
'aliases': <list>, # optional 'aliases': <list>, # optional
'text': <str>} 'locks': <str>} # optional, use access-type 'view'. Default is view:all()
where the `category` is optional and the `text`` should be formatted on the The `text`` should be formatted on the same form as other help entry-texts and
same form as other help entry-texts and contain ``# subtopics`` as normal. can contain ``# subtopics`` as normal.
New help-entry modules are added to the system by providing the python-path to New help-entry modules are added to the system by providing the python-path to
the module to `settings.FILE_HELP_ENTRY_MODULES`. Note that if same-key entries are the module to `settings.FILE_HELP_ENTRY_MODULES`. Note that if same-key entries are
@ -33,6 +34,7 @@ An example of the contents of a module:
"key": "The Gods", # case-insensitive, also partial-matching ('gods') works "key": "The Gods", # case-insensitive, also partial-matching ('gods') works
"aliases": ['pantheon', 'religion'], "aliases": ['pantheon', 'religion'],
"category": "Lore", "category": "Lore",
"locks": "view:all()", # this is optional unless restricting access
"text": ''' "text": '''
The gods formed the world ... The gods formed the world ...
@ -68,6 +70,8 @@ from django.conf import settings
from evennia.utils.utils import ( from evennia.utils.utils import (
variable_from_module, make_iter, all_from_module) variable_from_module, make_iter, all_from_module)
from evennia.utils import logger from evennia.utils import logger
from evennia.utils.utils import lazy_property
from evennia.locks.lockhandler import LockHandler
_DEFAULT_HELP_CATEGORY = settings.DEFAULT_HELP_CATEGORY _DEFAULT_HELP_CATEGORY = settings.DEFAULT_HELP_CATEGORY
@ -84,6 +88,7 @@ class FileHelpEntry:
aliases: list aliases: list
help_category: str help_category: str
entrytext: str entrytext: str
lock_storage: str
@property @property
def search_index_entry(self): def search_index_entry(self):
@ -96,6 +101,7 @@ class FileHelpEntry:
"aliases": " ".join(self.aliases), "aliases": " ".join(self.aliases),
"category": self.help_category, "category": self.help_category,
"tags": "", "tags": "",
"locks": "",
"text": self.entrytext, "text": self.entrytext,
} }
@ -105,6 +111,22 @@ class FileHelpEntry:
def __repr__(self): def __repr__(self):
return f"<FileHelpEntry {self.key}>" return f"<FileHelpEntry {self.key}>"
@lazy_property
def locks(self):
return LockHandler(self)
def access(self, accessing_obj, access_type="view", default=True):
"""
Determines if another object has permission to access this help entry.
Args:
accessing_obj (Object or Account): Entity trying to access this one.
access_type (str): type of access sought.
default (bool): What to return if no lock of `access_type` was found.
"""
return self.locks.check(accessing_obj, access_type=access_type, default=default)
class FileHelpStorageHandler: class FileHelpStorageHandler:
""" """
@ -154,14 +176,15 @@ class FileHelpStorageHandler:
key = dct.get('key').lower().strip() key = dct.get('key').lower().strip()
category = dct.get('category', _DEFAULT_HELP_CATEGORY).strip() category = dct.get('category', _DEFAULT_HELP_CATEGORY).strip()
aliases = list(dct.get('aliases', [])) aliases = list(dct.get('aliases', []))
entrytext = dct.get('text') entrytext = dct.get('text', '')
locks = dct.get('locks', '')
if not key and entrytext: if not key and entrytext:
logger.error(f"Cannot load file-help-entry (missing key or text): {dct}") logger.error(f"Cannot load file-help-entry (missing key or text): {dct}")
continue continue
unique_help_entries[key] = FileHelpEntry( unique_help_entries[key] = FileHelpEntry(
key=key, help_category=category, aliases=aliases, key=key, help_category=category, aliases=aliases, lock_storage=locks,
entrytext=entrytext) entrytext=entrytext)
self.help_entries_dict = unique_help_entries self.help_entries_dict = unique_help_entries

View file

@ -114,12 +114,19 @@ class HelpEntry(SharedMemoryModel):
def __repr__(self): def __repr__(self):
return f"<HelpEntry {self.key}>" return f"<HelpEntry {self.key}>"
def access(self, accessing_obj, access_type="read", default=False): def access(self, accessing_obj, access_type="read", default=True):
""" """
Determines if another object has permission to access. Determines if another object has permission to access this help entry.
accessing_obj - object trying to access this one
access_type - type of access sought Accesses used by default:
default - what to return if no lock of access_type was found 'read' - read the help entry itself.
'view' - see help entry in help index.
Args:
accessing_obj (Object or Account): Entity trying to access this one.
access_type (str): type of access sought.
default (bool): What to return if no lock of `access_type` was found.
""" """
return self.locks.check(accessing_obj, access_type=access_type, default=default) return self.locks.check(accessing_obj, access_type=access_type, default=default)