diff --git a/config b/config index 086110cd..b1548cca 100644 --- a/config +++ b/config @@ -99,16 +99,10 @@ http_password_parameter = # Value: None | owner_only | owner_write | from_file | regex type = None -# File for rights management from_file +# File for rights management from_file or regex file = ~/.config/radicale/rights -# File for rights management regex -regex_file =~/.config/radicale/regex -# use this as alternative method -regex_secondary = owner_only - - [storage] # Storage backend # Value: filesystem | database diff --git a/radicale/config.py b/radicale/config.py index 81db5461..45b758fd 100644 --- a/radicale/config.py +++ b/radicale/config.py @@ -74,9 +74,7 @@ INITIAL_CONFIG = { "http_password_parameter": ""}, "rights": { "type": "None", - "file": "", - "regex_file": "", - "regex_secondary": "owner_only"}, + "file": ""}, "storage": { "type": "filesystem", "filesystem_folder": os.path.expanduser( diff --git a/radicale/rights/regex.py b/radicale/rights/regex.py index a886fb68..2b4f823e 100644 --- a/radicale/rights/regex.py +++ b/radicale/rights/regex.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # # This file is part of Radicale Server - Calendar Server -# Copyright © 2012-2013 Guillaume Ayoub +# Copyright © 2013 Guillaume Ayoub # # This library is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -19,142 +19,108 @@ """ Regex-based rights. -You can define an secondary rights management method. If not, it will use -"owner_only", which implies the owners have all rights on their collections. -Secondary rights management method is specified in the config (section -"right", key "regex_secondary"). - Regexes are read from a file whose name is specified in the config (section -"right", key "regex_file"). - -Test string for regex is "user|collection" per default, because "|" is -not allowed in an URL. You may set this string per rule, using Python's -ConfigParser interpolation: %(user)s and %(collection)s -In fact you may also set user/collection to a fixed value per rule. But you -should consider using a secondary rights management method (e.g. "from_file"). +"right", key "file"). +Authentication login is matched against the "user" key, and collection's path +is matched against the "collection" key. You can use Python's ConfigParser +interpolation values %(login)s and %(path)s. You can also get groups from the +user regex in the collection with {0}, {1}, etc. Section names are only used for naming the rule. -Leading or Ending "/"s are trimmed from collection names. -Example: +Leading or ending slashes are trimmed from collection's path. + +Examples: # This means all users starting with "admin" may read any collection [admin] -regex: ^admin.*\|.+?$ +user: ^admin.*\|.+?$ +collection: .* permission: r # This means all users may read and write any collection starting with public. # We do so by just not testing against the user string. [public] -glue: %(collection)s -regex: ^public(/.+)?$ +user: .* +collection: ^public(/.+)?$ permission: rw -# A little more complex: -# Give read access to users from a domain for all collections of all the users: +# 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/*). [domain-wide-access] -regex: ^.+@(.+)\|.+@\1(/.+)?$ +user: ^.+@(.+)\..+$ +collection: ^{0}/.+$ permission: r +# Allow authenticated user to read all collections +[allow-everyone-read] +user: .* +collection: .* +permission: r + +# Give write access to owners +[owner-write] +user: .* +collection: ^%(login)s/.+$ +permission: w """ import os.path import re -from radicale import config, log, rights +from radicale import config, log + # Manage Python2/3 different modules # pylint: disable=F0401 try: - from configparser import ( - ConfigParser as ConfigParser, NoSectionError, NoOptionError) + from configparser import ConfigParser except ImportError: - from ConfigParser import ( - ConfigParser as ConfigParser, NoSectionError, NoOptionError) + from ConfigParser import ConfigParser # pylint: enable=F0401 FILENAME = ( - os.path.expanduser(config.get("rights", "regex_file")) or + os.path.expanduser(config.get("rights", "file")) or log.LOGGER.error("No file name configured for rights type 'regex'")) - - -def _read_regex(user, collection): - """Load regex from file.""" - regex = ConfigParser({'user': user, 'collection': collection}) + + +def _read_from_sections(user, collection, permission): + """Get regex sections.""" + log.LOGGER.debug("Reading regex from file %s" % FILENAME) + regex = ConfigParser({"login": user, "path": collection}) if not regex.read(FILENAME): log.LOGGER.error( - "File '%s' not found for rights management type 'regex'" % FILENAME) - return regex - -def _read_from_sections(user, collection, perm): - """Get regex sections.""" - regex = _read_regex(user, collection) - try: - for section in regex.sections(): - if _matches_section(user, collection, section): - if perm in regex.get(section, "permission"): + "File '%s' not found for rights management type 'regex'" % + FILENAME) + return False + + for section in regex.sections(): + re_user = regex.get(section, "user") + re_collection = regex.get(section, "collection") + log.LOGGER.debug( + "Test if '%s:%s' matches against '%s:%s' from section '%s'" % ( + user, collection, re_user, re_collection, section)) + user_match = re.match(re_user, user) + if user_match: + re_collection = re_collection.format(*user_match.groups()) + if re.match(re_collection, collection): + log.LOGGER.debug("Section '%s' matches" % section) + if permission in regex.get(section, "permission"): return True - except (NoSectionError, NoOptionError): - return False - return False - -def _matches_section(user, collection, section): - """Regex section against user and collection""" - log.LOGGER.debug("Reading regex from file %s" % FILENAME) - regex = _read_regex(user, collection) - log.LOGGER.debug("Match against section '%s'" % section) + log.LOGGER.debug("Section '%s' does not match" % section) - try: - test = regex.get(section, 'glue') - except (NoOptionError): - test = user+'|'+collection; - - try: - match = re.match(regex.get(section, 'regex'), test) - if match: - return True; - log.LOGGER.debug("Test-String '%s' does not match against '%s' from section '%s'" % \ - (test, regex.get(section, 'regex'), section)) - except (NoSectionError, NoOptionError): - return False return False - - -def _get_secondary(): - """Get secondary rights management method""" - try: - secondary = config.get("rights", "regex_secondary") - if not secondary or secondary == None: - secondary = 'owner_only' - - root_module = __import__( - "rights.%s" % secondary, globals=globals(), level=2) - module = getattr(root_module, secondary) - return module - except (ImportError, NoSectionError, NoOptionError): - return None - def read_authorized(user, collection): """Check if the user is allowed to read the collection.""" - if user is None: - return False - elif _get_secondary() != None and _get_secondary().read_authorized(user, collection): - return True - else: - return _read_from_sections( - user, collection.url.rstrip("/") or "/", "r") + return user and _read_from_sections( + user, collection.url.rstrip("/") or "/", "r") def write_authorized(user, collection): """Check if the user is allowed to write the collection.""" - if user is None: - return False - elif _get_secondary() != None and _get_secondary().write_authorized(user, collection): - return True - else: - return _read_from_sections( - user, collection.url.rstrip("/") or "/", "w") + return user and _read_from_sections( + user, collection.url.rstrip("/") or "/", "w")