Skip to content

Commit

Permalink
Merge pull request #1576 from petervarkoly/master
Browse files Browse the repository at this point in the history
Implementing group calendars and increase perfomance
  • Loading branch information
pbiering authored Sep 22, 2024
2 parents 7fbc0e7 + ccb5944 commit fdb014d
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 26 deletions.
6 changes: 3 additions & 3 deletions config
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
# URI to the LDAP server
#ldap_uri = ldap://localhost

# The base DN of the LDAP server
# The base DN where the user accounts have to be searched
#ldap_base = ##BASE_DN##

# The reader DN of the LDAP server
Expand All @@ -71,8 +71,8 @@
# If the ldap groups of the user need to be loaded
#ldap_load_groups = True

# Value: none | htpasswd | remote_user | http_x_remote_user | denyall
#type = none
# The filter to find the DN of the user. This filter must contain a python-style placeholder for the login
#ldap_filter = (&(objectClass=person)(cn={0}))

# Htpasswd filename
#htpasswd_filename = /etc/radicale/users
Expand Down
3 changes: 2 additions & 1 deletion radicale/app/propfind.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,8 @@ def do_PROPFIND(self, environ: types.WSGIEnviron, base_prefix: str,
return httputils.REQUEST_TIMEOUT
with self._storage.acquire_lock("r", user):
items_iter = iter(self._storage.discover(
path, environ.get("HTTP_DEPTH", "0")))
path, environ.get("HTTP_DEPTH", "0"),
None, self._rights._user_groups))
# take root item for rights checking
item = next(items_iter, None)
if not item:
Expand Down
33 changes: 18 additions & 15 deletions radicale/rights/from_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,33 +49,36 @@ def __init__(self, configuration: config.Configuration) -> None:
super().__init__(configuration)
self._filename = configuration.get("rights", "file")
self._log_rights_rule_doesnt_match_on_debug = configuration.get("logging", "rights_rule_doesnt_match_on_debug")
self._rights_config = configparser.ConfigParser()
try:
with open(self._filename, "r") as f:
self._rights_config.read_file(f)
logger.debug("Read rights file")
except Exception as e:
raise RuntimeError("Failed to load rights file %r: %s" %
(self._filename, e)) from e

def authorization(self, user: str, path: str) -> str:
user = user or ""
sane_path = pathutils.strip_path(path)
# Prevent "regex injection"
escaped_user = re.escape(user)
rights_config = configparser.ConfigParser()
try:
with open(self._filename, "r") as f:
rights_config.read_file(f)
except Exception as e:
raise RuntimeError("Failed to load rights file %r: %s" %
(self._filename, e)) from e
if not self._log_rights_rule_doesnt_match_on_debug:
logger.debug("logging of rules which doesn't match suppressed by config/option [logging] rights_rule_doesnt_match_on_debug")
for section in rights_config.sections():
group_match = False
for section in self._rights_config.sections():
group_match = None
user_match = None
try:
user_pattern = rights_config.get(section, "user")
collection_pattern = rights_config.get(section, "collection")
allowed_groups = rights_config.get(section, "groups", fallback="").split(",")
user_pattern = self._rights_config.get(section, "user", fallback="")
collection_pattern = self._rights_config.get(section, "collection")
allowed_groups = self._rights_config.get(section, "groups", fallback="").split(",")
try:
group_match = len(self._user_groups.intersection(allowed_groups)) > 0
except Exception:
pass
# Use empty format() for harmonized handling of curly braces
user_match = re.fullmatch(user_pattern.format(), user)
if user_pattern != "":
user_match = re.fullmatch(user_pattern.format(), user)
user_collection_match = user_match and re.fullmatch(
collection_pattern.format(
*(re.escape(s) for s in user_match.groups()),
Expand All @@ -85,13 +88,13 @@ def authorization(self, user: str, path: str) -> str:
raise RuntimeError("Error in section %r of rights file %r: "
"%s" % (section, self._filename, e)) from e
if user_match and user_collection_match:
permission = rights_config.get(section, "permissions")
permission = self._rights_config.get(section, "permissions")
logger.debug("Rule %r:%r matches %r:%r from section %r permission %r",
user, sane_path, user_pattern,
collection_pattern, section, permission)
return permission
if group_match and group_collection_match:
permission = rights_config.get(section, "permissions")
permission = self._rights_config.get(section, "permissions")
logger.debug("Rule %r:%r matches %r:%r from section %r permission %r by group membership",
user, sane_path, user_pattern,
collection_pattern, section, permission)
Expand Down
11 changes: 7 additions & 4 deletions radicale/storage/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
import json
import xml.etree.ElementTree as ET
from hashlib import sha256
from typing import (Iterable, Iterator, Mapping, Optional, Sequence, Set,
Tuple, Union, overload)
from typing import (Callable, ContextManager, Iterable, Iterator, Mapping,
Optional, Sequence, Set, Tuple, Union, overload)

import vobject

Expand Down Expand Up @@ -282,8 +282,11 @@ def __init__(self, configuration: "config.Configuration") -> None:
"""
self.configuration = configuration

def discover(self, path: str, depth: str = "0") -> Iterable[
"types.CollectionOrItem"]:
def discover(
self, path: str, depth: str = "0",
child_context_manager: Optional[
Callable[[str, Optional[str]], ContextManager[None]]] = None,
user_groups: Set[str] = set([])) -> Iterable["types.CollectionOrItem"]:
"""Discover a list of collections under the given ``path``.
``path`` is sanitized.
Expand Down
19 changes: 16 additions & 3 deletions radicale/storage/multifilesystem/discover.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@
# You should have received a copy of the GNU General Public License
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.

import base64
import os
import posixpath
from typing import Callable, ContextManager, Iterator, Optional, cast
from typing import Callable, ContextManager, Iterator, Optional, Set, cast

from radicale import pathutils, types
from radicale.log import logger
Expand All @@ -35,8 +36,10 @@ def _null_child_context_manager(path: str,
class StoragePartDiscover(StorageBase):

def discover(
self, path: str, depth: str = "0", child_context_manager: Optional[
Callable[[str, Optional[str]], ContextManager[None]]] = None
self, path: str, depth: str = "0",
child_context_manager: Optional[
Callable[[str, Optional[str]], ContextManager[None]]] = None,
user_groups: Set[str] = set([])
) -> Iterator[types.CollectionOrItem]:
# assert isinstance(self, multifilesystem.Storage)
if child_context_manager is None:
Expand Down Expand Up @@ -102,3 +105,13 @@ def discover(
with child_context_manager(sane_child_path, None):
yield self._collection_class(
cast(multifilesystem.Storage, self), child_path)
for group in user_groups:
href = base64.b64encode(group.encode('utf-8')).decode('ascii')
logger.debug(f"searching for group calendar {group} {href}")
sane_child_path = f"GROUPS/{href}"
if not os.path.isdir(pathutils.path_to_filesystem(folder, sane_child_path)):
continue
child_path = f"/GROUPS/{href}/"
with child_context_manager(sane_child_path, None):
yield self._collection_class(
cast(multifilesystem.Storage, self), child_path)

0 comments on commit fdb014d

Please sign in to comment.