Merge pull request #43 from Sweil/regex-rights-management
New rights management method: regex
This commit is contained in:
		
							
								
								
									
										8
									
								
								config
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								config
									
									
									
									
									
								
							@@ -96,13 +96,19 @@ http_password_parameter =
 | 
			
		||||
 | 
			
		||||
[rights]
 | 
			
		||||
# Rights management method
 | 
			
		||||
# Value: None | owner_only | owner_write | from_file
 | 
			
		||||
# Value: None | owner_only | owner_write | from_file | regex
 | 
			
		||||
type = None
 | 
			
		||||
 | 
			
		||||
# File for rights management from_file
 | 
			
		||||
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
 | 
			
		||||
 
 | 
			
		||||
@@ -74,7 +74,9 @@ INITIAL_CONFIG = {
 | 
			
		||||
        "http_password_parameter": ""},
 | 
			
		||||
    "rights": {
 | 
			
		||||
        "type": "None",
 | 
			
		||||
        "file": ""},
 | 
			
		||||
        "file": "",
 | 
			
		||||
        "regex_file": "",
 | 
			
		||||
        "regex_secondary": "owner_only"},
 | 
			
		||||
    "storage": {
 | 
			
		||||
        "type": "filesystem",
 | 
			
		||||
        "filesystem_folder": os.path.expanduser(
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										160
									
								
								radicale/rights/regex.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								radicale/rights/regex.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,160 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# This file is part of Radicale Server - Calendar Server
 | 
			
		||||
# Copyright © 2012-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
 | 
			
		||||
# the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# This library is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with Radicale.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
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").
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Section names are only used for naming the rule.
 | 
			
		||||
Leading or Ending "/"s are trimmed from collection names.
 | 
			
		||||
 | 
			
		||||
Example:
 | 
			
		||||
 | 
			
		||||
# This means all users starting with "admin" may read any collection
 | 
			
		||||
[admin]
 | 
			
		||||
regex: ^admin.*\|.+?$
 | 
			
		||||
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(/.+)?$
 | 
			
		||||
permission: rw
 | 
			
		||||
 | 
			
		||||
# A little more complex:
 | 
			
		||||
# Give read access to users from a domain for all collections of all the users:
 | 
			
		||||
[domain-wide-access]
 | 
			
		||||
regex:  ^.+@(.+)\|.+@\1(/.+)?$
 | 
			
		||||
permission: r
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import os.path
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
from radicale import config, log, rights
 | 
			
		||||
# Manage Python2/3 different modules
 | 
			
		||||
# pylint: disable=F0401
 | 
			
		||||
try:
 | 
			
		||||
    from configparser import (
 | 
			
		||||
        ConfigParser as ConfigParser, NoSectionError, NoOptionError)
 | 
			
		||||
except ImportError:
 | 
			
		||||
    from ConfigParser import (
 | 
			
		||||
        ConfigParser as ConfigParser, NoSectionError, NoOptionError)
 | 
			
		||||
# pylint: enable=F0401
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FILENAME = (
 | 
			
		||||
    os.path.expanduser(config.get("rights", "regex_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})
 | 
			
		||||
    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"):
 | 
			
		||||
                    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)
 | 
			
		||||
 | 
			
		||||
    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")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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")
 | 
			
		||||
		Reference in New Issue
	
	Block a user