Allow finer control in rights plugin
New permissions: R: read collections without tag r: read collections with tag and included objects W: write and delete collections without tag w: write and delete collection with tag and included objects
This commit is contained in:
		| @@ -144,32 +144,33 @@ class Application: | |||||||
|  |  | ||||||
|     def collect_allowed_items(self, items, user): |     def collect_allowed_items(self, items, user): | ||||||
|         """Get items from request that user is allowed to access.""" |         """Get items from request that user is allowed to access.""" | ||||||
|         read_allowed_items = [] |  | ||||||
|         write_allowed_items = [] |  | ||||||
|         for item in items: |         for item in items: | ||||||
|             if isinstance(item, storage.BaseCollection): |             if isinstance(item, storage.BaseCollection): | ||||||
|                 path = storage.sanitize_path("/%s/" % item.path) |                 path = storage.sanitize_path("/%s/" % item.path) | ||||||
|                 can_read = self.Rights.authorized(user, path, "r") |                 if item.get_meta("tag"): | ||||||
|                 can_write = self.Rights.authorized(user, path, "w") |                     permissions = self.Rights.authorized(user, path, "rw") | ||||||
|                 target = "collection %r" % item.path |                     target = "collection with tag %r" % item.path | ||||||
|  |                 else: | ||||||
|  |                     permissions = self.Rights.authorized(user, path, "RW") | ||||||
|  |                     target = "collection %r" % item.path | ||||||
|             else: |             else: | ||||||
|                 path = storage.sanitize_path("/%s/%s" % (item.collection.path, |                 path = storage.sanitize_path("/%s/" % item.collection.path) | ||||||
|                                                          item.href)) |                 permissions = self.Rights.authorized(user, path, "rw") | ||||||
|                 can_read = self.Rights.authorized_item(user, path, "r") |  | ||||||
|                 can_write = self.Rights.authorized_item(user, path, "w") |  | ||||||
|                 target = "item %r from %r" % (item.href, item.collection.path) |                 target = "item %r from %r" % (item.href, item.collection.path) | ||||||
|             text_status = [] |             if rights.intersect_permissions(permissions, "Ww"): | ||||||
|             if can_read: |                 permission = "w" | ||||||
|                 text_status.append("read") |                 status = "write" | ||||||
|                 read_allowed_items.append(item) |             elif rights.intersect_permissions(permissions, "Rr"): | ||||||
|             if can_write: |                 permission = "r" | ||||||
|                 text_status.append("write") |                 status = "read" | ||||||
|                 write_allowed_items.append(item) |             else: | ||||||
|  |                 permission = "" | ||||||
|  |                 status = "NO" | ||||||
|             logger.debug( |             logger.debug( | ||||||
|                 "%s has %s access to %s", |                 "%s has %s access to %s", | ||||||
|                 repr(user) if user else "anonymous user", |                 repr(user) if user else "anonymous user", status, target) | ||||||
|                 " and ".join(text_status) if text_status else "NO", target) |             if permission: | ||||||
|         return read_allowed_items, write_allowed_items |                 yield item, permission | ||||||
|  |  | ||||||
|     def __call__(self, environ, start_response): |     def __call__(self, environ, start_response): | ||||||
|         with log.register_stream(environ["wsgi.errors"]): |         with log.register_stream(environ["wsgi.errors"]): | ||||||
| @@ -315,7 +316,7 @@ class Application: | |||||||
|         # Create principal collection |         # Create principal collection | ||||||
|         if user: |         if user: | ||||||
|             principal_path = "/%s/" % user |             principal_path = "/%s/" % user | ||||||
|             if self.Rights.authorized(user, principal_path, "w"): |             if self.Rights.authorized(user, principal_path, "W"): | ||||||
|                 with self.Collection.acquire_lock("r", user): |                 with self.Collection.acquire_lock("r", user): | ||||||
|                     principal = next( |                     principal = next( | ||||||
|                         self.Collection.discover(principal_path, depth="1"), |                         self.Collection.discover(principal_path, depth="1"), | ||||||
| @@ -365,19 +366,28 @@ class Application: | |||||||
|         return response(status, headers, answer) |         return response(status, headers, answer) | ||||||
|  |  | ||||||
|     def _access(self, user, path, permission, item=None): |     def _access(self, user, path, permission, item=None): | ||||||
|         """Check if ``user`` can access ``path`` or the parent collection. |         if permission not in "rw": | ||||||
|  |             raise ValueError("Invalid permission argument: %r" % permission) | ||||||
|         ``permission`` must either be "r" or "w". |         if not item: | ||||||
|  |             permissions = permission + permission.upper() | ||||||
|         If ``item`` is given, only access to that class of item is checked. |             parent_permissions = permission | ||||||
|  |         elif isinstance(item, storage.BaseCollection): | ||||||
|         """ |             if item.get_meta("tag"): | ||||||
|         allowed = False |                 permissions = permission | ||||||
|         if not item or isinstance(item, storage.BaseCollection): |             else: | ||||||
|             allowed |= self.Rights.authorized(user, path, permission) |                 permissions = permission.upper() | ||||||
|         if not item or not isinstance(item, storage.BaseCollection): |             parent_permissions = "" | ||||||
|             allowed |= self.Rights.authorized_item(user, path, permission) |         else: | ||||||
|         return allowed |             permissions = "" | ||||||
|  |             parent_permissions = permission | ||||||
|  |         if permissions and self.Rights.authorized(user, path, permissions): | ||||||
|  |             return True | ||||||
|  |         if parent_permissions: | ||||||
|  |             parent_path = storage.sanitize_path( | ||||||
|  |                 "/%s/" % posixpath.dirname(path.strip("/"))) | ||||||
|  |             if self.Rights.authorized(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) | ||||||
| @@ -459,10 +469,10 @@ class Application: | |||||||
|             return NOT_ALLOWED |             return NOT_ALLOWED | ||||||
|         with self.Collection.acquire_lock("w", user): |         with self.Collection.acquire_lock("w", user): | ||||||
|             item = next(self.Collection.discover(path), None) |             item = next(self.Collection.discover(path), None) | ||||||
|             if not self._access(user, path, "w", item): |  | ||||||
|                 return NOT_ALLOWED |  | ||||||
|             if not item: |             if not item: | ||||||
|                 return NOT_FOUND |                 return NOT_FOUND | ||||||
|  |             if not self._access(user, path, "w", item): | ||||||
|  |                 return 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): | ||||||
|                 # ETag precondition not verified, do not delete item |                 # ETag precondition not verified, do not delete item | ||||||
| @@ -493,10 +503,10 @@ class Application: | |||||||
|             return NOT_ALLOWED |             return NOT_ALLOWED | ||||||
|         with self.Collection.acquire_lock("r", user): |         with self.Collection.acquire_lock("r", user): | ||||||
|             item = next(self.Collection.discover(path), None) |             item = next(self.Collection.discover(path), None) | ||||||
|             if not self._access(user, path, "r", item): |  | ||||||
|                 return NOT_ALLOWED |  | ||||||
|             if not item: |             if not item: | ||||||
|                 return NOT_FOUND |                 return NOT_FOUND | ||||||
|  |             if not self._access(user, path, "r", item): | ||||||
|  |                 return NOT_ALLOWED | ||||||
|             if isinstance(item, storage.BaseCollection): |             if isinstance(item, storage.BaseCollection): | ||||||
|                 tag = item.get_meta("tag") |                 tag = item.get_meta("tag") | ||||||
|                 if not tag: |                 if not tag: | ||||||
| @@ -563,7 +573,8 @@ class Application: | |||||||
|  |  | ||||||
|     def do_MKCOL(self, environ, base_prefix, path, user): |     def do_MKCOL(self, environ, base_prefix, path, user): | ||||||
|         """Manage MKCOL request.""" |         """Manage MKCOL request.""" | ||||||
|         if not self.Rights.authorized(user, path, "w"): |         permissions = self.Rights.authorized(user, path, "Ww") | ||||||
|  |         if not permissions: | ||||||
|             return NOT_ALLOWED |             return NOT_ALLOWED | ||||||
|         try: |         try: | ||||||
|             xml_content = self._read_xml_content(environ) |             xml_content = self._read_xml_content(environ) | ||||||
| @@ -587,6 +598,9 @@ class Application: | |||||||
|                     parent_item.get_meta("tag")): |                     parent_item.get_meta("tag")): | ||||||
|                 return FORBIDDEN |                 return FORBIDDEN | ||||||
|             props = xmlutils.props_from_request(xml_content) |             props = xmlutils.props_from_request(xml_content) | ||||||
|  |             if (props.get("tag") and "w" not in permissions or | ||||||
|  |                     not props.get("tag") and "W" not in permissions): | ||||||
|  |                 return NOT_ALLOWED | ||||||
|             try: |             try: | ||||||
|                 storage.check_and_sanitize_props(props) |                 storage.check_and_sanitize_props(props) | ||||||
|                 self.Collection.create_collection(path, props=props) |                 self.Collection.create_collection(path, props=props) | ||||||
| @@ -617,12 +631,11 @@ class Application: | |||||||
|  |  | ||||||
|         with self.Collection.acquire_lock("w", user): |         with self.Collection.acquire_lock("w", user): | ||||||
|             item = next(self.Collection.discover(path), None) |             item = next(self.Collection.discover(path), None) | ||||||
|             if not self._access(user, path, "w", item): |  | ||||||
|                 return NOT_ALLOWED |  | ||||||
|             if not self._access(user, to_path, "w", item): |  | ||||||
|                 return NOT_ALLOWED |  | ||||||
|             if not item: |             if not item: | ||||||
|                 return NOT_FOUND |                 return NOT_FOUND | ||||||
|  |             if (not self._access(user, path, "w", item) or | ||||||
|  |                     not self._access(user, to_path, "w", item)): | ||||||
|  |                 return NOT_ALLOWED | ||||||
|             if isinstance(item, storage.BaseCollection): |             if isinstance(item, storage.BaseCollection): | ||||||
|                 # TODO: support moving collections |                 # TODO: support moving collections | ||||||
|                 return METHOD_NOT_ALLOWED |                 return METHOD_NOT_ALLOWED | ||||||
| @@ -682,24 +695,24 @@ class Application: | |||||||
|                 path, environ.get("HTTP_DEPTH", "0")) |                 path, environ.get("HTTP_DEPTH", "0")) | ||||||
|             # take root item for rights checking |             # take root item for rights checking | ||||||
|             item = next(items, None) |             item = next(items, None) | ||||||
|             if not self._access(user, path, "r", item): |  | ||||||
|                 return NOT_ALLOWED |  | ||||||
|             if not item: |             if not item: | ||||||
|                 return NOT_FOUND |                 return NOT_FOUND | ||||||
|  |             if not self._access(user, path, "r", item): | ||||||
|  |                 return NOT_ALLOWED | ||||||
|             # put item back |             # put item back | ||||||
|             items = itertools.chain([item], items) |             items = itertools.chain([item], items) | ||||||
|             read_items, write_items = self.collect_allowed_items(items, user) |             allowed_items = self.collect_allowed_items(items, user) | ||||||
|             headers = {"DAV": DAV_HEADERS, |             headers = {"DAV": DAV_HEADERS, | ||||||
|                        "Content-Type": "text/xml; charset=%s" % self.encoding} |                        "Content-Type": "text/xml; charset=%s" % self.encoding} | ||||||
|             status, xml_answer = xmlutils.propfind( |             status, xml_answer = xmlutils.propfind( | ||||||
|                 base_prefix, path, xml_content, read_items, write_items, user) |                 base_prefix, path, xml_content, allowed_items, user) | ||||||
|             if status == client.FORBIDDEN: |             if status == client.FORBIDDEN: | ||||||
|                 return NOT_ALLOWED |                 return NOT_ALLOWED | ||||||
|             return status, headers, self._write_xml_content(xml_answer) |             return status, headers, self._write_xml_content(xml_answer) | ||||||
|  |  | ||||||
|     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.Rights.authorized(user, path, "w"): |         if not self._access(user, path, "w"): | ||||||
|             return NOT_ALLOWED |             return NOT_ALLOWED | ||||||
|         try: |         try: | ||||||
|             xml_content = self._read_xml_content(environ) |             xml_content = self._read_xml_content(environ) | ||||||
| @@ -714,6 +727,8 @@ class Application: | |||||||
|             item = next(self.Collection.discover(path), None) |             item = next(self.Collection.discover(path), None) | ||||||
|             if not item: |             if not item: | ||||||
|                 return NOT_FOUND |                 return NOT_FOUND | ||||||
|  |             if not self._access(user, path, "w", item): | ||||||
|  |                 return NOT_ALLOWED | ||||||
|             if not isinstance(item, storage.BaseCollection): |             if not isinstance(item, storage.BaseCollection): | ||||||
|                 return FORBIDDEN |                 return FORBIDDEN | ||||||
|             headers = {"DAV": DAV_HEADERS, |             headers = {"DAV": DAV_HEADERS, | ||||||
| @@ -754,7 +769,7 @@ class Application: | |||||||
|             if write_whole_collection: |             if write_whole_collection: | ||||||
|                 if not self.Rights.authorized(user, path, "w"): |                 if not self.Rights.authorized(user, path, "w"): | ||||||
|                     return NOT_ALLOWED |                     return NOT_ALLOWED | ||||||
|             elif not self.Rights.authorized_item(user, path, "w"): |             elif not self.Rights.authorized(user, parent_path, "w"): | ||||||
|                 return NOT_ALLOWED |                 return NOT_ALLOWED | ||||||
|  |  | ||||||
|             etag = environ.get("HTTP_IF_MATCH", "") |             etag = environ.get("HTTP_IF_MATCH", "") | ||||||
| @@ -850,10 +865,10 @@ class Application: | |||||||
|             return REQUEST_TIMEOUT |             return REQUEST_TIMEOUT | ||||||
|         with self.Collection.acquire_lock("r", user): |         with self.Collection.acquire_lock("r", user): | ||||||
|             item = next(self.Collection.discover(path), None) |             item = next(self.Collection.discover(path), None) | ||||||
|             if not self._access(user, path, "r", item): |  | ||||||
|                 return NOT_ALLOWED |  | ||||||
|             if not item: |             if not item: | ||||||
|                 return NOT_FOUND |                 return NOT_FOUND | ||||||
|  |             if not self._access(user, path, "r", item): | ||||||
|  |                 return NOT_ALLOWED | ||||||
|             if isinstance(item, storage.BaseCollection): |             if isinstance(item, storage.BaseCollection): | ||||||
|                 collection = item |                 collection = item | ||||||
|             else: |             else: | ||||||
|   | |||||||
| @@ -39,7 +39,6 @@ Leading or ending slashes are trimmed from collection's path. | |||||||
|  |  | ||||||
| import configparser | import configparser | ||||||
| import os.path | import os.path | ||||||
| import posixpath |  | ||||||
| import re | import re | ||||||
| from importlib import import_module | from importlib import import_module | ||||||
|  |  | ||||||
| @@ -75,59 +74,61 @@ def load(configuration): | |||||||
|     return rights_class(configuration) |     return rights_class(configuration) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def intersect_permissions(a, b="RrWw"): | ||||||
|  |     return "".join(set(a).intersection(set(b))) | ||||||
|  |  | ||||||
|  |  | ||||||
| class BaseRights: | class BaseRights: | ||||||
|     def __init__(self, configuration): |     def __init__(self, configuration): | ||||||
|         self.configuration = configuration |         self.configuration = configuration | ||||||
|  |  | ||||||
|     def authorized(self, user, path, permission): |     def authorized(self, user, path, permissions): | ||||||
|         """Check if the user is allowed to read or write the collection. |         """Check if the user is allowed to read or write the collection. | ||||||
|  |  | ||||||
|         If ``user`` is empty, check for anonymous rights. |         If ``user`` is empty, check for anonymous rights. | ||||||
|  |  | ||||||
|         ``path`` is sanitized. |         ``path`` is sanitized. | ||||||
|  |  | ||||||
|         ``permission`` is "r" or "w". |         ``permissions`` can include "R", "r", "W", "w" | ||||||
|  |  | ||||||
|  |         Returns granted rights. | ||||||
|  |  | ||||||
|         """ |         """ | ||||||
|         raise NotImplementedError |         raise NotImplementedError | ||||||
|  |  | ||||||
|     def authorized_item(self, user, path, permission): |  | ||||||
|         """Check if the user is allowed to read or write the item.""" |  | ||||||
|         path = storage.sanitize_path(path) |  | ||||||
|         parent_path = storage.sanitize_path( |  | ||||||
|             "/%s/" % posixpath.dirname(path.strip("/"))) |  | ||||||
|         return self.authorized(user, parent_path, permission) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class NoneRights(BaseRights): | class NoneRights(BaseRights): | ||||||
|     def authorized(self, user, path, permission): |     def authorized(self, user, path, permissions): | ||||||
|         return True |         return intersect_permissions(permissions) | ||||||
|  |  | ||||||
|  |  | ||||||
| class AuthenticatedRights(BaseRights): | class AuthenticatedRights(BaseRights): | ||||||
|     def authorized(self, user, path, permission): |     def authorized(self, user, path, permissions): | ||||||
|         return bool(user) |         if not user: | ||||||
|  |             return "" | ||||||
|  |         return intersect_permissions(permissions) | ||||||
|  |  | ||||||
|  |  | ||||||
| class OwnerWriteRights(BaseRights): | class OwnerWriteRights(BaseRights): | ||||||
|     def authorized(self, user, path, permission): |     def authorized(self, user, path, permissions): | ||||||
|  |         if not user: | ||||||
|  |             return "" | ||||||
|         sane_path = storage.sanitize_path(path).strip("/") |         sane_path = storage.sanitize_path(path).strip("/") | ||||||
|         return bool(user) and (permission == "r" or |         if user != sane_path.split("/", maxsplit=1)[0]: | ||||||
|                                user == sane_path.split("/", maxsplit=1)[0]) |             return intersect_permissions(permissions, "Rr") | ||||||
|  |         return intersect_permissions(permissions) | ||||||
|  |  | ||||||
|  |  | ||||||
| class OwnerOnlyRights(BaseRights): | class OwnerOnlyRights(BaseRights): | ||||||
|     def authorized(self, user, path, permission): |     def authorized(self, user, path, permissions): | ||||||
|  |         if not user: | ||||||
|  |             return "" | ||||||
|         sane_path = storage.sanitize_path(path).strip("/") |         sane_path = storage.sanitize_path(path).strip("/") | ||||||
|         return bool(user) and ( |         if not sane_path: | ||||||
|             permission == "r" and not sane_path or |             return intersect_permissions(permissions, "R") | ||||||
|             user == sane_path.split("/", maxsplit=1)[0]) |         if user != sane_path.split("/", maxsplit=1)[0]: | ||||||
|  |             return "" | ||||||
|     def authorized_item(self, user, path, permission): |         return intersect_permissions(permissions) | ||||||
|         sane_path = storage.sanitize_path(path).strip("/") |  | ||||||
|         if "/" not in sane_path: |  | ||||||
|             return False |  | ||||||
|         return super().authorized_item(user, path, permission) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Rights(BaseRights): | class Rights(BaseRights): | ||||||
| @@ -135,41 +136,41 @@ class Rights(BaseRights): | |||||||
|         super().__init__(configuration) |         super().__init__(configuration) | ||||||
|         self.filename = os.path.expanduser(configuration.get("rights", "file")) |         self.filename = os.path.expanduser(configuration.get("rights", "file")) | ||||||
|  |  | ||||||
|     def authorized(self, user, path, permission): |     def authorized(self, user, path, permissions): | ||||||
|         user = user or "" |         user = user or "" | ||||||
|         sane_path = storage.sanitize_path(path).strip("/") |         sane_path = storage.sanitize_path(path).strip("/") | ||||||
|         # Prevent "regex injection" |         # Prevent "regex injection" | ||||||
|         user_escaped = re.escape(user) |         user_escaped = re.escape(user) | ||||||
|         sane_path_escaped = re.escape(sane_path) |         sane_path_escaped = re.escape(sane_path) | ||||||
|         regex = configparser.ConfigParser( |         rights_config = configparser.ConfigParser( | ||||||
|             {"login": user_escaped, "path": sane_path_escaped}) |             {"login": user_escaped, "path": sane_path_escaped}) | ||||||
|         try: |         try: | ||||||
|             if not regex.read(self.filename): |             if not rights_config.read(self.filename): | ||||||
|                 raise RuntimeError("No such file: %r" % |                 raise RuntimeError("No such file: %r" % | ||||||
|                                    self.filename) |                                    self.filename) | ||||||
|         except Exception as e: |         except Exception as e: | ||||||
|             raise RuntimeError("Failed to load rights file %r: %s" % |             raise RuntimeError("Failed to load rights file %r: %s" % | ||||||
|                                (self.filename, e)) from e |                                (self.filename, e)) from e | ||||||
|         for section in regex.sections(): |         for section in rights_config.sections(): | ||||||
|             try: |             try: | ||||||
|                 re_user_pattern = regex.get(section, "user") |                 user_pattern = rights_config.get(section, "user") | ||||||
|                 re_collection_pattern = regex.get(section, "collection") |                 collection_pattern = rights_config.get(section, "collection") | ||||||
|                 # Emulate fullmatch |                 user_match = re.fullmatch(user_pattern, user) | ||||||
|                 user_match = re.match(r"(?:%s)\Z" % re_user_pattern, user) |                 collection_match = user_match and re.fullmatch( | ||||||
|                 collection_match = user_match and re.match( |                     collection_pattern.format( | ||||||
|                     r"(?:%s)\Z" % re_collection_pattern.format( |  | ||||||
|                         *map(re.escape, user_match.groups())), sane_path) |                         *map(re.escape, user_match.groups())), sane_path) | ||||||
|             except Exception as e: |             except Exception as e: | ||||||
|                 raise RuntimeError("Error in section %r of rights file %r: " |                 raise RuntimeError("Error in section %r of rights file %r: " | ||||||
|                                    "%s" % (section, self.filename, e)) from e |                                    "%s" % (section, self.filename, e)) from e | ||||||
|             if user_match and collection_match: |             if user_match and collection_match: | ||||||
|                 logger.debug("Rule %r:%r matches %r:%r from section %r", |                 logger.debug("Rule %r:%r matches %r:%r from section %r", | ||||||
|                              user, sane_path, re_user_pattern, |                              user, sane_path, user_pattern, | ||||||
|                              re_collection_pattern, section) |                              collection_pattern, section) | ||||||
|                 return permission in regex.get(section, "permission") |                 return intersect_permissions( | ||||||
|  |                     permissions, rights_config.get(section, "permissions")) | ||||||
|             else: |             else: | ||||||
|                 logger.debug("Rule %r:%r doesn't match %r:%r from section %r", |                 logger.debug("Rule %r:%r doesn't match %r:%r from section %r", | ||||||
|                              user, sane_path, re_user_pattern, |                              user, sane_path, user_pattern, | ||||||
|                              re_collection_pattern, section) |                              collection_pattern, section) | ||||||
|         logger.info("Rights: %r:%r doesn't match any section", user, sane_path) |         logger.info("Rights: %r:%r doesn't match any section", user, sane_path) | ||||||
|         return False |         return "" | ||||||
|   | |||||||
| @@ -23,5 +23,7 @@ from radicale import rights | |||||||
|  |  | ||||||
|  |  | ||||||
| class Rights(rights.BaseRights): | class Rights(rights.BaseRights): | ||||||
|     def authorized(self, user, path, permission): |     def authorized(self, user, path, permissions): | ||||||
|         return path.strip("/") in ("tmp", "other") |         if path.strip("/") not in ("tmp", "other"): | ||||||
|  |             return "" | ||||||
|  |         return rights.intersect_permissions(permissions) | ||||||
|   | |||||||
| @@ -118,11 +118,11 @@ class TestBaseAuthRequests(BaseTest): | |||||||
| [owner] | [owner] | ||||||
| user: .+ | user: .+ | ||||||
| collection: %(login)s(/.*)? | collection: %(login)s(/.*)? | ||||||
| permission: rw | permissions: RrWw | ||||||
| [custom] | [custom] | ||||||
| user: .* | user: .* | ||||||
| collection: custom(/.*)? | collection: custom(/.*)? | ||||||
| permission: r""") | permissions: Rr""") | ||||||
|         self.configuration["rights"]["file"] = rights_file_path |         self.configuration["rights"]["file"] = rights_file_path | ||||||
|         self._test_rights("from_file", "", "/other", "r", 401) |         self._test_rights("from_file", "", "/other", "r", 401) | ||||||
|         self._test_rights("from_file", "tmp", "/other", "r", 403) |         self._test_rights("from_file", "tmp", "/other", "r", 403) | ||||||
|   | |||||||
| @@ -771,8 +771,7 @@ def delete(base_prefix, path, collection, href=None): | |||||||
|     return multistatus |     return multistatus | ||||||
|  |  | ||||||
|  |  | ||||||
| def propfind(base_prefix, path, xml_request, read_collections, | def propfind(base_prefix, path, xml_request, allowed_items, user): | ||||||
|              write_collections, user): |  | ||||||
|     """Read and answer PROPFIND requests. |     """Read and answer PROPFIND requests. | ||||||
|  |  | ||||||
|     Read rfc4918-9.1 for info. |     Read rfc4918-9.1 for info. | ||||||
| @@ -805,19 +804,10 @@ def propfind(base_prefix, path, xml_request, read_collections, | |||||||
|     # Writing answer |     # Writing answer | ||||||
|     multistatus = ET.Element(_tag("D", "multistatus")) |     multistatus = ET.Element(_tag("D", "multistatus")) | ||||||
|  |  | ||||||
|     collections = [] |     for item, permission in allowed_items: | ||||||
|     for collection in write_collections: |         write = permission == "w" | ||||||
|         collections.append(collection) |  | ||||||
|         response = _propfind_response( |         response = _propfind_response( | ||||||
|             base_prefix, path, collection, props, user, write=True, |             base_prefix, path, item, props, user, write=write, | ||||||
|             allprop=allprop, propname=propname) |  | ||||||
|         if response: |  | ||||||
|             multistatus.append(response) |  | ||||||
|     for collection in read_collections: |  | ||||||
|         if collection in collections: |  | ||||||
|             continue |  | ||||||
|         response = _propfind_response( |  | ||||||
|             base_prefix, path, collection, props, user, write=False, |  | ||||||
|             allprop=allprop, propname=propname) |             allprop=allprop, propname=propname) | ||||||
|         if response: |         if response: | ||||||
|             multistatus.append(response) |             multistatus.append(response) | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								rights
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								rights
									
									
									
									
									
								
							| @@ -19,30 +19,30 @@ | |||||||
| [admin] | [admin] | ||||||
| user: admin.* | user: admin.* | ||||||
| collection: .* | collection: .* | ||||||
| permission: r | permissions: Rr | ||||||
|  |  | ||||||
| # This means all users may read and write any collection starting with public. | # This means all users may read and write any collection starting with public. | ||||||
| # We do so by just not testing against the user string. | # We do so by just not testing against the user string. | ||||||
| [public] | [public] | ||||||
| user: .* | user: .* | ||||||
| collection: public(/.+)? | collection: public(/.+)? | ||||||
| permission: rw | permissions: RrWw | ||||||
|  |  | ||||||
| # A little more complex: give read access to users from a domain for all | # A little more complex: give read access to users from a domain for all | ||||||
| # collections of all the users (ie. user@domain.tld can read domain/*). | # collections of all the users (ie. user@domain.tld can read domain/*). | ||||||
| [domain-wide-access] | [domain-wide-access] | ||||||
| user: .+@(.+)\..+ | user: .+@(.+)\..+ | ||||||
| collection: {0}/.+ | collection: {0}/.+ | ||||||
| permission: r | permissions: Rr | ||||||
|  |  | ||||||
| # Allow authenticated user to read all collections | # Allow authenticated user to read all collections | ||||||
| [allow-everyone-read] | [allow-everyone-read] | ||||||
| user: .+ | user: .+ | ||||||
| collection: .* | collection: .* | ||||||
| permission: r | permissions: Rr | ||||||
|  |  | ||||||
| # Give write access to owners | # Give write access to owners | ||||||
| [owner-write] | [owner-write] | ||||||
| user: .+ | user: .+ | ||||||
| collection: %(login)s/.* | collection: %(login)s/.* | ||||||
| permission: w | permissions: Ww | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Unrud
					Unrud