Minimize accesses to rights backend
This commit is contained in:
parent
99adeb19c1
commit
aef58bd55c
@ -264,12 +264,11 @@ class Application(
|
|||||||
# Create principal collection
|
# Create principal collection
|
||||||
if user:
|
if user:
|
||||||
principal_path = "/%s/" % user
|
principal_path = "/%s/" % user
|
||||||
if "W" in self._rights.authorization(user, principal_path):
|
with self._storage.acquire_lock("r", user):
|
||||||
with self._storage.acquire_lock("r", user):
|
principal = next(self._storage.discover(
|
||||||
principal = next(
|
principal_path, depth="1"), None)
|
||||||
self._storage.discover(principal_path, depth="1"),
|
if not principal:
|
||||||
None)
|
if "W" in self._rights.authorization(user, principal_path):
|
||||||
if not principal:
|
|
||||||
with self._storage.acquire_lock("w", user):
|
with self._storage.acquire_lock("w", user):
|
||||||
try:
|
try:
|
||||||
self._storage.create_collection(principal_path)
|
self._storage.create_collection(principal_path)
|
||||||
@ -277,9 +276,9 @@ class Application(
|
|||||||
logger.warning("Failed to create principal "
|
logger.warning("Failed to create principal "
|
||||||
"collection %r: %s", user, e)
|
"collection %r: %s", user, e)
|
||||||
user = ""
|
user = ""
|
||||||
else:
|
else:
|
||||||
logger.warning("Access to principal path %r denied by "
|
logger.warning("Access to principal path %r denied by "
|
||||||
"rights backend", principal_path)
|
"rights backend", principal_path)
|
||||||
|
|
||||||
if self.configuration.get("server", "_internal_server"):
|
if self.configuration.get("server", "_internal_server"):
|
||||||
# Verify content length
|
# Verify content length
|
||||||
@ -313,32 +312,6 @@ class Application(
|
|||||||
|
|
||||||
return response(status, headers, answer)
|
return response(status, headers, answer)
|
||||||
|
|
||||||
def _access(self, user, path, permission, item=None):
|
|
||||||
if permission not in "rw":
|
|
||||||
raise ValueError("Invalid permission argument: %r" % permission)
|
|
||||||
if not item:
|
|
||||||
permissions = permission + permission.upper()
|
|
||||||
parent_permissions = permission
|
|
||||||
elif isinstance(item, storage.BaseCollection):
|
|
||||||
if item.get_meta("tag"):
|
|
||||||
permissions = permission
|
|
||||||
else:
|
|
||||||
permissions = permission.upper()
|
|
||||||
parent_permissions = ""
|
|
||||||
else:
|
|
||||||
permissions = ""
|
|
||||||
parent_permissions = permission
|
|
||||||
if permissions and rights.intersect(
|
|
||||||
self._rights.authorization(user, path), permissions):
|
|
||||||
return True
|
|
||||||
if parent_permissions:
|
|
||||||
parent_path = pathutils.unstrip_path(
|
|
||||||
posixpath.dirname(pathutils.strip_path(path)), True)
|
|
||||||
if rights.intersect(self._rights.authorization(user, parent_path),
|
|
||||||
parent_permissions):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _read_raw_content(self, environ):
|
def _read_raw_content(self, environ):
|
||||||
content_length = int(environ.get("CONTENT_LENGTH") or 0)
|
content_length = int(environ.get("CONTENT_LENGTH") or 0)
|
||||||
if not content_length:
|
if not content_length:
|
||||||
@ -382,3 +355,44 @@ class Application(
|
|||||||
headers = {"Content-Type": "text/xml; charset=%s" % self._encoding}
|
headers = {"Content-Type": "text/xml; charset=%s" % self._encoding}
|
||||||
content = self._write_xml_content(xmlutils.webdav_error(human_tag))
|
content = self._write_xml_content(xmlutils.webdav_error(human_tag))
|
||||||
return status, headers, content
|
return status, headers, content
|
||||||
|
|
||||||
|
|
||||||
|
class Access:
|
||||||
|
"""Helper class to check access rights of an item"""
|
||||||
|
|
||||||
|
def __init__(self, rights, user, path):
|
||||||
|
self._rights = rights
|
||||||
|
self.user = user
|
||||||
|
self.path = path
|
||||||
|
self.parent_path = pathutils.unstrip_path(
|
||||||
|
posixpath.dirname(pathutils.strip_path(path)), True)
|
||||||
|
self.permissions = self._rights.authorization(self.user, self.path)
|
||||||
|
self._parent_permissions = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parent_permissions(self):
|
||||||
|
if self.path == self.parent_path:
|
||||||
|
return self.permissions
|
||||||
|
if self._parent_permissions is None:
|
||||||
|
self._parent_permissions = self._rights.authorization(
|
||||||
|
self.user, self.parent_path)
|
||||||
|
return self._parent_permissions
|
||||||
|
|
||||||
|
def check(self, permission, item=None):
|
||||||
|
if permission not in "rw":
|
||||||
|
raise ValueError("Invalid permission argument: %r" % permission)
|
||||||
|
if not item:
|
||||||
|
permissions = permission + permission.upper()
|
||||||
|
parent_permissions = permission
|
||||||
|
elif isinstance(item, storage.BaseCollection):
|
||||||
|
if item.get_meta("tag"):
|
||||||
|
permissions = permission
|
||||||
|
else:
|
||||||
|
permissions = permission.upper()
|
||||||
|
parent_permissions = ""
|
||||||
|
else:
|
||||||
|
permissions = ""
|
||||||
|
parent_permissions = permission
|
||||||
|
return bool(rights.intersect(self.permissions, permissions) or (
|
||||||
|
self.path != self.parent_path and
|
||||||
|
rights.intersect(self.parent_permissions, parent_permissions)))
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
from http import client
|
from http import client
|
||||||
from xml.etree import ElementTree as ET
|
from xml.etree import ElementTree as ET
|
||||||
|
|
||||||
from radicale import httputils, storage, xmlutils
|
from radicale import app, httputils, storage, xmlutils
|
||||||
|
|
||||||
|
|
||||||
def xml_delete(base_prefix, path, collection, href=None):
|
def xml_delete(base_prefix, path, collection, href=None):
|
||||||
@ -49,13 +49,14 @@ def xml_delete(base_prefix, path, collection, href=None):
|
|||||||
class ApplicationDeleteMixin:
|
class ApplicationDeleteMixin:
|
||||||
def do_DELETE(self, environ, base_prefix, path, user):
|
def do_DELETE(self, environ, base_prefix, path, user):
|
||||||
"""Manage DELETE request."""
|
"""Manage DELETE request."""
|
||||||
if not self._access(user, path, "w"):
|
access = app.Access(self._rights, user, path)
|
||||||
|
if not access.check("w"):
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
with self._storage.acquire_lock("w", user):
|
with self._storage.acquire_lock("w", user):
|
||||||
item = next(self._storage.discover(path), None)
|
item = next(self._storage.discover(path), None)
|
||||||
if not item:
|
if not item:
|
||||||
return httputils.NOT_FOUND
|
return httputils.NOT_FOUND
|
||||||
if not self._access(user, path, "w", item):
|
if not access.check("w", item):
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
if_match = environ.get("HTTP_IF_MATCH", "*")
|
if_match = environ.get("HTTP_IF_MATCH", "*")
|
||||||
if if_match not in ("*", item.etag):
|
if if_match not in ("*", item.etag):
|
||||||
|
@ -21,7 +21,7 @@ import posixpath
|
|||||||
from http import client
|
from http import client
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
||||||
from radicale import httputils, pathutils, storage, xmlutils
|
from radicale import app, httputils, pathutils, storage, xmlutils
|
||||||
from radicale.log import logger
|
from radicale.log import logger
|
||||||
|
|
||||||
|
|
||||||
@ -70,13 +70,14 @@ class ApplicationGetMixin:
|
|||||||
# Dispatch .web URL to web module
|
# Dispatch .web URL to web module
|
||||||
if path == "/.web" or path.startswith("/.web/"):
|
if path == "/.web" or path.startswith("/.web/"):
|
||||||
return self._web.get(environ, base_prefix, path, user)
|
return self._web.get(environ, base_prefix, path, user)
|
||||||
if not self._access(user, path, "r"):
|
access = app.Access(self._rights, user, path)
|
||||||
|
if not access.check("r"):
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
with self._storage.acquire_lock("r", user):
|
with self._storage.acquire_lock("r", user):
|
||||||
item = next(self._storage.discover(path), None)
|
item = next(self._storage.discover(path), None)
|
||||||
if not item:
|
if not item:
|
||||||
return httputils.NOT_FOUND
|
return httputils.NOT_FOUND
|
||||||
if not self._access(user, path, "r", item):
|
if not access.check("r", item):
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
if isinstance(item, storage.BaseCollection):
|
if isinstance(item, storage.BaseCollection):
|
||||||
tag = item.get_meta("tag")
|
tag = item.get_meta("tag")
|
||||||
|
@ -30,9 +30,8 @@ from radicale.log import logger
|
|||||||
class ApplicationMkcolMixin:
|
class ApplicationMkcolMixin:
|
||||||
def do_MKCOL(self, environ, base_prefix, path, user):
|
def do_MKCOL(self, environ, base_prefix, path, user):
|
||||||
"""Manage MKCOL request."""
|
"""Manage MKCOL request."""
|
||||||
permissions = rights.intersect(
|
permissions = self._rights.authorization(user, path)
|
||||||
self._rights.authorization(user, path), "Ww")
|
if not rights.intersect(permissions, "Ww"):
|
||||||
if not permissions:
|
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
try:
|
try:
|
||||||
xml_content = self._read_xml_content(environ)
|
xml_content = self._read_xml_content(environ)
|
||||||
|
@ -21,7 +21,7 @@ import posixpath
|
|||||||
from http import client
|
from http import client
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from radicale import httputils, pathutils, storage
|
from radicale import app, httputils, pathutils, storage
|
||||||
from radicale.log import logger
|
from radicale.log import logger
|
||||||
|
|
||||||
|
|
||||||
@ -34,7 +34,8 @@ class ApplicationMoveMixin:
|
|||||||
logger.info("Unsupported destination address: %r", raw_dest)
|
logger.info("Unsupported destination address: %r", raw_dest)
|
||||||
# Remote destination server, not supported
|
# Remote destination server, not supported
|
||||||
return httputils.REMOTE_DESTINATION
|
return httputils.REMOTE_DESTINATION
|
||||||
if not self._access(user, path, "w"):
|
access = app.Access(self._rights, user, path)
|
||||||
|
if not access.check("w"):
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
to_path = pathutils.sanitize_path(to_url.path)
|
to_path = pathutils.sanitize_path(to_url.path)
|
||||||
if not (to_path + "/").startswith(base_prefix + "/"):
|
if not (to_path + "/").startswith(base_prefix + "/"):
|
||||||
@ -42,15 +43,16 @@ class ApplicationMoveMixin:
|
|||||||
"start with base prefix", to_path, path)
|
"start with base prefix", to_path, path)
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
to_path = to_path[len(base_prefix):]
|
to_path = to_path[len(base_prefix):]
|
||||||
if not self._access(user, to_path, "w"):
|
to_access = app.Access(self._rights, user, to_path)
|
||||||
|
if not to_access.check("w"):
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
|
|
||||||
with self._storage.acquire_lock("w", user):
|
with self._storage.acquire_lock("w", user):
|
||||||
item = next(self._storage.discover(path), None)
|
item = next(self._storage.discover(path), None)
|
||||||
if not item:
|
if not item:
|
||||||
return httputils.NOT_FOUND
|
return httputils.NOT_FOUND
|
||||||
if (not self._access(user, path, "w", item) or
|
if (not access.check("w", item) or
|
||||||
not self._access(user, to_path, "w", item)):
|
not to_access.check("w", item)):
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
if isinstance(item, storage.BaseCollection):
|
if isinstance(item, storage.BaseCollection):
|
||||||
# TODO: support moving collections
|
# TODO: support moving collections
|
||||||
|
@ -24,7 +24,7 @@ import socket
|
|||||||
from http import client
|
from http import client
|
||||||
from xml.etree import ElementTree as ET
|
from xml.etree import ElementTree as ET
|
||||||
|
|
||||||
from radicale import httputils, pathutils, rights, storage, xmlutils
|
from radicale import app, httputils, pathutils, rights, storage, xmlutils
|
||||||
from radicale.log import logger
|
from radicale.log import logger
|
||||||
|
|
||||||
|
|
||||||
@ -343,7 +343,8 @@ class ApplicationPropfindMixin:
|
|||||||
|
|
||||||
def do_PROPFIND(self, environ, base_prefix, path, user):
|
def do_PROPFIND(self, environ, base_prefix, path, user):
|
||||||
"""Manage PROPFIND request."""
|
"""Manage PROPFIND request."""
|
||||||
if not self._access(user, path, "r"):
|
access = app.Access(self._rights, user, path)
|
||||||
|
if not access.check("r"):
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
try:
|
try:
|
||||||
xml_content = self._read_xml_content(environ)
|
xml_content = self._read_xml_content(environ)
|
||||||
@ -361,7 +362,7 @@ class ApplicationPropfindMixin:
|
|||||||
item = next(items, None)
|
item = next(items, None)
|
||||||
if not item:
|
if not item:
|
||||||
return httputils.NOT_FOUND
|
return httputils.NOT_FOUND
|
||||||
if not self._access(user, path, "r", item):
|
if not access.check("r", item):
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
# put item back
|
# put item back
|
||||||
items = itertools.chain([item], items)
|
items = itertools.chain([item], items)
|
||||||
|
@ -21,7 +21,7 @@ import socket
|
|||||||
from http import client
|
from http import client
|
||||||
from xml.etree import ElementTree as ET
|
from xml.etree import ElementTree as ET
|
||||||
|
|
||||||
from radicale import httputils
|
from radicale import app, httputils
|
||||||
from radicale import item as radicale_item
|
from radicale import item as radicale_item
|
||||||
from radicale import storage, xmlutils
|
from radicale import storage, xmlutils
|
||||||
from radicale.log import logger
|
from radicale.log import logger
|
||||||
@ -87,7 +87,8 @@ def xml_proppatch(base_prefix, path, xml_request, collection):
|
|||||||
class ApplicationProppatchMixin:
|
class ApplicationProppatchMixin:
|
||||||
def do_PROPPATCH(self, environ, base_prefix, path, user):
|
def do_PROPPATCH(self, environ, base_prefix, path, user):
|
||||||
"""Manage PROPPATCH request."""
|
"""Manage PROPPATCH request."""
|
||||||
if not self._access(user, path, "w"):
|
access = app.Access(self._rights, user, path)
|
||||||
|
if not access.check("w"):
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
try:
|
try:
|
||||||
xml_content = self._read_xml_content(environ)
|
xml_content = self._read_xml_content(environ)
|
||||||
@ -102,7 +103,7 @@ class ApplicationProppatchMixin:
|
|||||||
item = next(self._storage.discover(path), None)
|
item = next(self._storage.discover(path), None)
|
||||||
if not item:
|
if not item:
|
||||||
return httputils.NOT_FOUND
|
return httputils.NOT_FOUND
|
||||||
if not self._access(user, path, "w", item):
|
if not access.check("w", item):
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
if not isinstance(item, storage.BaseCollection):
|
if not isinstance(item, storage.BaseCollection):
|
||||||
return httputils.FORBIDDEN
|
return httputils.FORBIDDEN
|
||||||
|
@ -25,7 +25,7 @@ from http import client
|
|||||||
|
|
||||||
import vobject
|
import vobject
|
||||||
|
|
||||||
from radicale import httputils
|
from radicale import app, httputils
|
||||||
from radicale import item as radicale_item
|
from radicale import item as radicale_item
|
||||||
from radicale import pathutils, rights, storage, xmlutils
|
from radicale import pathutils, rights, storage, xmlutils
|
||||||
from radicale.log import logger
|
from radicale.log import logger
|
||||||
@ -114,7 +114,8 @@ def prepare(vobject_items, path, content_type, permissions, parent_permissions,
|
|||||||
class ApplicationPutMixin:
|
class ApplicationPutMixin:
|
||||||
def do_PUT(self, environ, base_prefix, path, user):
|
def do_PUT(self, environ, base_prefix, path, user):
|
||||||
"""Manage PUT request."""
|
"""Manage PUT request."""
|
||||||
if not self._access(user, path, "w"):
|
access = app.Access(self._rights, user, path)
|
||||||
|
if not access.check("w"):
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
try:
|
try:
|
||||||
content = self._read_content(environ)
|
content = self._read_content(environ)
|
||||||
@ -126,12 +127,6 @@ class ApplicationPutMixin:
|
|||||||
return httputils.REQUEST_TIMEOUT
|
return httputils.REQUEST_TIMEOUT
|
||||||
# Prepare before locking
|
# Prepare before locking
|
||||||
content_type = environ.get("CONTENT_TYPE", "").split(";")[0]
|
content_type = environ.get("CONTENT_TYPE", "").split(";")[0]
|
||||||
parent_path = pathutils.unstrip_path(
|
|
||||||
posixpath.dirname(pathutils.strip_path(path)), True)
|
|
||||||
permissions = rights.intersect(
|
|
||||||
self._rights.authorization(user, path), "Ww")
|
|
||||||
parent_permissions = rights.intersect(
|
|
||||||
self._rights.authorization(user, parent_path), "w")
|
|
||||||
try:
|
try:
|
||||||
vobject_items = tuple(vobject.readComponents(content or ""))
|
vobject_items = tuple(vobject.readComponents(content or ""))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -140,12 +135,14 @@ class ApplicationPutMixin:
|
|||||||
return httputils.BAD_REQUEST
|
return httputils.BAD_REQUEST
|
||||||
(prepared_items, prepared_tag, prepared_write_whole_collection,
|
(prepared_items, prepared_tag, prepared_write_whole_collection,
|
||||||
prepared_props, prepared_exc_info) = prepare(
|
prepared_props, prepared_exc_info) = prepare(
|
||||||
vobject_items, path, content_type, permissions,
|
vobject_items, path, content_type,
|
||||||
parent_permissions)
|
bool(rights.intersect(access.permissions, "Ww")),
|
||||||
|
bool(rights.intersect(access.parent_permissions, "w")))
|
||||||
|
|
||||||
with self._storage.acquire_lock("w", user):
|
with self._storage.acquire_lock("w", user):
|
||||||
item = next(self._storage.discover(path), None)
|
item = next(self._storage.discover(path), None)
|
||||||
parent_item = next(self._storage.discover(parent_path), None)
|
parent_item = next(
|
||||||
|
self._storage.discover(access.parent_path), None)
|
||||||
if not parent_item:
|
if not parent_item:
|
||||||
return httputils.CONFLICT
|
return httputils.CONFLICT
|
||||||
|
|
||||||
@ -159,10 +156,9 @@ class ApplicationPutMixin:
|
|||||||
tag = parent_item.get_meta("tag")
|
tag = parent_item.get_meta("tag")
|
||||||
|
|
||||||
if write_whole_collection:
|
if write_whole_collection:
|
||||||
if ("w" if tag else "W") not in self._rights.authorization(
|
if ("w" if tag else "W") not in access.permissions:
|
||||||
user, path):
|
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
elif "w" not in self._rights.authorization(user, parent_path):
|
elif "w" not in access.parent_permissions:
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
|
|
||||||
etag = environ.get("HTTP_IF_MATCH", "")
|
etag = environ.get("HTTP_IF_MATCH", "")
|
||||||
@ -182,8 +178,10 @@ class ApplicationPutMixin:
|
|||||||
prepared_write_whole_collection != write_whole_collection):
|
prepared_write_whole_collection != write_whole_collection):
|
||||||
(prepared_items, prepared_tag, prepared_write_whole_collection,
|
(prepared_items, prepared_tag, prepared_write_whole_collection,
|
||||||
prepared_props, prepared_exc_info) = prepare(
|
prepared_props, prepared_exc_info) = prepare(
|
||||||
vobject_items, path, content_type, permissions,
|
vobject_items, path, content_type,
|
||||||
parent_permissions, tag, write_whole_collection)
|
bool(rights.intersect(access.permissions, "Ww")),
|
||||||
|
bool(rights.intersect(access.parent_permissions, "w")),
|
||||||
|
tag, write_whole_collection)
|
||||||
props = prepared_props
|
props = prepared_props
|
||||||
if prepared_exc_info:
|
if prepared_exc_info:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
|
@ -24,7 +24,7 @@ from http import client
|
|||||||
from urllib.parse import unquote, urlparse
|
from urllib.parse import unquote, urlparse
|
||||||
from xml.etree import ElementTree as ET
|
from xml.etree import ElementTree as ET
|
||||||
|
|
||||||
from radicale import httputils, pathutils, storage, xmlutils
|
from radicale import app, httputils, pathutils, storage, xmlutils
|
||||||
from radicale.item import filter as radicale_filter
|
from radicale.item import filter as radicale_filter
|
||||||
from radicale.log import logger
|
from radicale.log import logger
|
||||||
|
|
||||||
@ -257,7 +257,8 @@ def xml_item_response(base_prefix, href, found_props=(), not_found_props=(),
|
|||||||
class ApplicationReportMixin:
|
class ApplicationReportMixin:
|
||||||
def do_REPORT(self, environ, base_prefix, path, user):
|
def do_REPORT(self, environ, base_prefix, path, user):
|
||||||
"""Manage REPORT request."""
|
"""Manage REPORT request."""
|
||||||
if not self._access(user, path, "r"):
|
access = app.Access(self._rights, user, path)
|
||||||
|
if not access.check("r"):
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
try:
|
try:
|
||||||
xml_content = self._read_xml_content(environ)
|
xml_content = self._read_xml_content(environ)
|
||||||
@ -273,7 +274,7 @@ class ApplicationReportMixin:
|
|||||||
item = next(self._storage.discover(path), None)
|
item = next(self._storage.discover(path), None)
|
||||||
if not item:
|
if not item:
|
||||||
return httputils.NOT_FOUND
|
return httputils.NOT_FOUND
|
||||||
if not self._access(user, path, "r", item):
|
if not access.check("r", item):
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
if isinstance(item, storage.BaseCollection):
|
if isinstance(item, storage.BaseCollection):
|
||||||
collection = item
|
collection = item
|
||||||
|
Loading…
Reference in New Issue
Block a user