Change a couple of things in regex-based rights manager

This commit is contained in:
Guillaume Ayoub 2013-07-23 18:05:22 +02:00
parent 5592b9e76a
commit faa331ccc3
3 changed files with 62 additions and 104 deletions

8
config
View File

@ -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

View File

@ -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(

View File

@ -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")