From dca10fa14e2a6a37681ca0569895a4bcbeb62927 Mon Sep 17 00:00:00 2001 From: Sergey Fursov Date: Wed, 25 Dec 2013 03:13:56 +0400 Subject: [PATCH 1/6] Different rights management backends Initially only one backend - regular expressions based --- config | 3 ++ radicale/__init__.py | 1 + radicale/config.py | 1 + radicale/rights/__init__.py | 45 +++++++++++++++++++++++++ radicale/{rights.py => rights/regex.py} | 2 +- setup.py | 2 +- 6 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 radicale/rights/__init__.py rename radicale/{rights.py => rights/regex.py} (99%) diff --git a/config b/config index 3f59c2a..2845169 100644 --- a/config +++ b/config @@ -100,6 +100,9 @@ committer = Firstname Lastname [rights] +# Rights backend +backend = "regex" + # Rights management method # Value: None | owner_only | owner_write | from_file type = None diff --git a/radicale/__init__.py b/radicale/__init__.py index b383638..9222e9c 100644 --- a/radicale/__init__.py +++ b/radicale/__init__.py @@ -127,6 +127,7 @@ class Application(object): super(Application, self).__init__() auth.load() storage.load() + rights.load() self.encoding = config.get("encoding", "request") if config.getboolean("logging", "full_environment"): self.headers_log = lambda environ: environ diff --git a/radicale/config.py b/radicale/config.py index 71d27a8..36dbb76 100644 --- a/radicale/config.py +++ b/radicale/config.py @@ -75,6 +75,7 @@ INITIAL_CONFIG = { "git": { "committer": "Radicale "}, "rights": { + "backend": "regex", "type": "None", "file": "~/.config/radicale/rights"}, "storage": { diff --git a/radicale/rights/__init__.py b/radicale/rights/__init__.py new file mode 100644 index 0000000..5e9770f --- /dev/null +++ b/radicale/rights/__init__.py @@ -0,0 +1,45 @@ +# -*- 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 . + +""" +Rights backends. + +This module loads the rights backend, according to the rights +configuration. + +""" +import sys + +from .. import config + + +def load(): + """Load list of available storage managers.""" + storage_type = config.get("rights", "backend") + root_module = __import__( + "rights.%s" % storage_type, globals=globals(), level=2) + module = getattr(root_module, storage_type) + sys.modules[__name__].authorized = module.authorized + return module + + +def authorized(user, collection, right): + """ Check when user has rights on collection + This method is overriden when appropriate rights backend loaded. + """ + raise NotImplementedError() diff --git a/radicale/rights.py b/radicale/rights/regex.py similarity index 99% rename from radicale/rights.py rename to radicale/rights/regex.py index c772c1a..21a828b 100644 --- a/radicale/rights.py +++ b/radicale/rights/regex.py @@ -38,7 +38,7 @@ Leading or ending slashes are trimmed from collection's path. import re import os.path -from . import config, log +from .. import config, log # Manage Python2/3 different modules # pylint: disable=F0401 diff --git a/setup.py b/setup.py index afe8e96..7318dd0 100755 --- a/setup.py +++ b/setup.py @@ -54,7 +54,7 @@ setup( "Radicale-%s.tar.gz" % radicale.VERSION), license="GNU GPL v3", platforms="Any", - packages=["radicale", "radicale.auth", "radicale.storage"], + packages=["radicale", "radicale.auth", "radicale.storage", "radicale.rights"], provides=["radicale"], scripts=["bin/radicale"], keywords=["calendar", "addressbook", "CalDAV", "CardDAV"], From a91a7790c5d17206e3838a71b9bc81258c06b2b8 Mon Sep 17 00:00:00 2001 From: Sergey Fursov Date: Sat, 28 Dec 2013 13:31:32 +0400 Subject: [PATCH 2/6] Allow attach custom auth handler --- config | 5 ++++- radicale/auth/__init__.py | 10 +++++++--- radicale/config.py | 1 + tests/__init__.py | 14 +------------- tests/custom/__init__.py | 1 + tests/custom/auth.py | 30 ++++++++++++++++++++++++++++++ tests/test_auth.py | 39 +++++++++++++++++++++++++++++++-------- 7 files changed, 75 insertions(+), 25 deletions(-) create mode 100644 tests/custom/__init__.py create mode 100644 tests/custom/auth.py diff --git a/config b/config index 2845169..621093e 100644 --- a/config +++ b/config @@ -47,9 +47,12 @@ stock = utf-8 [auth] # Authentication method -# Value: None | htpasswd | IMAP | LDAP | PAM | courier | http +# Value: None | htpasswd | IMAP | LDAP | PAM | courier | http | custom type = None +# custom auth handler +custom_handler = + # Htpasswd filename htpasswd_filename = /etc/radicale/users # Htpasswd encryption method diff --git a/radicale/auth/__init__.py b/radicale/auth/__init__.py index 5e7c01b..5dbdde4 100644 --- a/radicale/auth/__init__.py +++ b/radicale/auth/__init__.py @@ -34,13 +34,17 @@ def load(): log.LOGGER.debug("Authentication type is %s" % auth_type) if auth_type == "None": return None + elif auth_type == 'custom': + auth_module = config.get("auth", "custom_handler") + __import__(auth_module) + module = sys.modules[auth_module] else: root_module = __import__( "auth.%s" % auth_type, globals=globals(), level=2) module = getattr(root_module, auth_type) - # Override auth.is_authenticated - sys.modules[__name__].is_authenticated = module.is_authenticated - return module + # Override auth.is_authenticated + sys.modules[__name__].is_authenticated = module.is_authenticated + return module def is_authenticated(user, password): diff --git a/radicale/config.py b/radicale/config.py index 36dbb76..b905c60 100644 --- a/radicale/config.py +++ b/radicale/config.py @@ -55,6 +55,7 @@ INITIAL_CONFIG = { "stock": "utf-8"}, "auth": { "type": "None", + "custom_handler": "", "htpasswd_filename": "/etc/radicale/users", "htpasswd_encryption": "crypt", "imap_hostname": "localhost", diff --git a/tests/__init__.py b/tests/__init__.py index 0d5678c..3878cfe 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -21,8 +21,6 @@ Tests for Radicale. """ -import base64 -import hashlib import os import shutil import sys @@ -38,7 +36,6 @@ os.environ["RADICALE_CONFIG"] = os.path.join(os.path.dirname( os.path.dirname(__file__)), "config") from radicale import config -from radicale.auth import htpasswd from .helpers import get_file_content from sqlalchemy.orm import sessionmaker from sqlalchemy import create_engine @@ -126,19 +123,10 @@ class GitMultiFileSystem(GitFileSystem, MultiFileSystem): """Base class for multifilesystem tests using Git""" -class HtpasswdAuthSystem(BaseTest): +class AuthSystem(BaseTest): """Base class to test Radicale with Htpasswd authentication""" def setup(self): - self.colpath = tempfile.mkdtemp() - htpasswd_file_path = os.path.join(self.colpath, ".htpasswd") - with open(htpasswd_file_path, "wb") as fd: - fd.write(b"tmp:{SHA}" + base64.b64encode( - hashlib.sha1(b"bepo").digest())) - config.set("auth", "type", "htpasswd") self.userpass = "dG1wOmJlcG8=" - self.application = radicale.Application() - htpasswd.FILENAME = htpasswd_file_path - htpasswd.ENCRYPTION = "sha1" def teardown(self): config.set("auth", "type", "None") diff --git a/tests/custom/__init__.py b/tests/custom/__init__.py new file mode 100644 index 0000000..bf893c0 --- /dev/null +++ b/tests/custom/__init__.py @@ -0,0 +1 @@ +# coding=utf-8 \ No newline at end of file diff --git a/tests/custom/auth.py b/tests/custom/auth.py new file mode 100644 index 0000000..6fc1352 --- /dev/null +++ b/tests/custom/auth.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Radicale Server - Calendar Server +# Copyright © 2008 Nicolas Kandel +# Copyright © 2008 Pascal Halter +# Copyright © 2008-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 . + +""" +Custom authentication. + +Just check username for testing + +""" + + +def is_authenticated(user, password): + return user == 'tmp' diff --git a/tests/test_auth.py b/tests/test_auth.py index a6de517..fb31464 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -22,22 +22,45 @@ Radicale tests with simple requests and authentication. """ -from nose import with_setup -from . import HtpasswdAuthSystem +import base64 +import hashlib +import os +import radicale +import tempfile +from tests import AuthSystem +from radicale import config +from radicale.auth import htpasswd -class TestBaseAuthRequests(HtpasswdAuthSystem): +class TestBaseAuthRequests(AuthSystem): """ Tests basic requests with auth. - ..note Only htpasswd works at the moment since - it requires to spawn processes running servers for - others auth methods (ldap). + We should setup auth for each type before create Application object """ - @with_setup(HtpasswdAuthSystem.setup, HtpasswdAuthSystem.teardown) def test_root(self): - """Tests a GET request at "/".""" + self.colpath = tempfile.mkdtemp() + htpasswd_file_path = os.path.join(self.colpath, ".htpasswd") + with open(htpasswd_file_path, "wb") as fd: + fd.write(b"tmp:{SHA}" + base64.b64encode( + hashlib.sha1(b"bepo").digest())) + config.set("auth", "type", "htpasswd") + + htpasswd.FILENAME = htpasswd_file_path + htpasswd.ENCRYPTION = "sha1" + + self.application = radicale.Application() + + status, headers, answer = self.request( + "GET", "/", HTTP_AUTHORIZATION=self.userpass) + assert status == 200 + assert "Radicale works!" in answer + + def test_custom(self): + config.set("auth", "type", "custom") + config.set("auth", "custom_handler", "tests.custom.auth") + self.application = radicale.Application() status, headers, answer = self.request( "GET", "/", HTTP_AUTHORIZATION=self.userpass) assert status == 200 From 3b0328ca1ecdb0c0d35c4c4d7b1d54c1a4b1d83d Mon Sep 17 00:00:00 2001 From: Sergey Fursov Date: Sat, 28 Dec 2013 14:15:35 +0400 Subject: [PATCH 3/6] Allow attach custom storage backend --- config | 6 +++++- radicale/config.py | 1 + radicale/storage/__init__.py | 13 +++++++++---- tests/__init__.py | 19 +++++++++++++++++++ tests/custom/storage.py | 30 ++++++++++++++++++++++++++++++ tests/test_base.py | 3 ++- 6 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 tests/custom/storage.py diff --git a/config b/config index 621093e..411d49b 100644 --- a/config +++ b/config @@ -104,6 +104,7 @@ committer = Firstname Lastname [rights] # Rights backend +# Value: regex backend = "regex" # Rights management method @@ -116,9 +117,12 @@ file = ~/.config/radicale/rights [storage] # Storage backend -# Value: filesystem | multifilesystem | database +# Value: filesystem | multifilesystem | database | custom type = filesystem +# Custom storage handler +custom_handler = + # Folder for storing local collections, created if not present filesystem_folder = ~/.config/radicale/collections diff --git a/radicale/config.py b/radicale/config.py index b905c60..53a3346 100644 --- a/radicale/config.py +++ b/radicale/config.py @@ -81,6 +81,7 @@ INITIAL_CONFIG = { "file": "~/.config/radicale/rights"}, "storage": { "type": "filesystem", + "custom_handler": "", "filesystem_folder": os.path.expanduser( "~/.config/radicale/collections"), "database_url": ""}, diff --git a/radicale/storage/__init__.py b/radicale/storage/__init__.py index ca9dad3..ab698f4 100644 --- a/radicale/storage/__init__.py +++ b/radicale/storage/__init__.py @@ -23,15 +23,20 @@ This module loads the storage backend, according to the storage configuration. """ - +import sys from .. import config, ical def load(): """Load list of available storage managers.""" storage_type = config.get("storage", "type") - root_module = __import__( - "storage.%s" % storage_type, globals=globals(), level=2) - module = getattr(root_module, storage_type) + if storage_type == "custom": + storage_module = config.get("storage", "custom_handler") + __import__(storage_module) + module = sys.modules[storage_module] + else: + root_module = __import__( + "storage.%s" % storage_type, globals=globals(), level=2) + module = getattr(root_module, storage_type) ical.Collection = module.Collection return module diff --git a/tests/__init__.py b/tests/__init__.py index 3878cfe..1621bae 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -123,6 +123,25 @@ class GitMultiFileSystem(GitFileSystem, MultiFileSystem): """Base class for multifilesystem tests using Git""" +class CustomStorageSystem(BaseTest): + """Base class for custom backend tests.""" + storage_type = "custom" + + def setup(self): + """Setup function for each test.""" + self.colpath = tempfile.mkdtemp() + config.set("storage", "type", self.storage_type) + config.set("storage", "custom_handler", "tests.custom.storage") + from tests.custom import storage + storage.FOLDER = self.colpath + storage.GIT_REPOSITORY = None + self.application = radicale.Application() + + def teardown(self): + """Teardown function for each test.""" + shutil.rmtree(self.colpath) + + class AuthSystem(BaseTest): """Base class to test Radicale with Htpasswd authentication""" def setup(self): diff --git a/tests/custom/storage.py b/tests/custom/storage.py new file mode 100644 index 0000000..bc12356 --- /dev/null +++ b/tests/custom/storage.py @@ -0,0 +1,30 @@ +# -*- 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 . + +""" +Custom storage backend. + +Copy of filesystem storage backend for testing + +""" + +from radicale.storage import filesystem + + +class Collection(filesystem.Collection): + """Collection stored in a flat ical file.""" diff --git a/tests/test_base.py b/tests/test_base.py index 071c44d..d2c030c 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -25,6 +25,7 @@ from . import (FileSystem, MultiFileSystem, DataBaseSystem, GitFileSystem, GitMultiFileSystem) from .helpers import get_file_content import sys +from tests import CustomStorageSystem class BaseRequests(object): @@ -83,7 +84,7 @@ class BaseRequests(object): # Generate classes with different configs cl_list = [FileSystem, MultiFileSystem, DataBaseSystem, - GitFileSystem, GitMultiFileSystem] + GitFileSystem, GitMultiFileSystem, CustomStorageSystem] for cl in cl_list: classname = "Test%s" % cl.__name__ setattr(sys.modules[__name__], From 017df0ddcf2298568d0945fa1b2ece4117e77bea Mon Sep 17 00:00:00 2001 From: Sergey Fursov Date: Sat, 28 Dec 2013 14:40:29 +0400 Subject: [PATCH 4/6] Simplified tests structure --- tests/__init__.py | 88 ---------------------------------------------- tests/test_auth.py | 11 ++++-- tests/test_base.py | 88 ++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 86 insertions(+), 101 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index 1621bae..6c4dd9b 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -22,23 +22,15 @@ Tests for Radicale. """ import os -import shutil import sys -import tempfile -from dulwich.repo import Repo from io import BytesIO sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) -import radicale - os.environ["RADICALE_CONFIG"] = os.path.join(os.path.dirname( os.path.dirname(__file__)), "config") -from radicale import config from .helpers import get_file_content -from sqlalchemy.orm import sessionmaker -from sqlalchemy import create_engine class BaseTest(object): @@ -70,83 +62,3 @@ class BaseTest(object): """Put the response values into the current application.""" self.application._status = status self.application._headers = headers - - -class FileSystem(BaseTest): - """Base class for filesystem tests.""" - storage_type = "filesystem" - - def setup(self): - """Setup function for each test.""" - self.colpath = tempfile.mkdtemp() - config.set("storage", "type", self.storage_type) - from radicale.storage import filesystem - filesystem.FOLDER = self.colpath - filesystem.GIT_REPOSITORY = None - self.application = radicale.Application() - - def teardown(self): - """Teardown function for each test.""" - shutil.rmtree(self.colpath) - - -class MultiFileSystem(FileSystem): - """Base class for multifilesystem tests.""" - storage_type = "multifilesystem" - - -class DataBaseSystem(BaseTest): - """Base class for database tests""" - def setup(self): - config.set("storage", "type", "database") - config.set("storage", "database_url", "sqlite://") - from radicale.storage import database - database.Session = sessionmaker() - database.Session.configure(bind=create_engine("sqlite://")) - session = database.Session() - for st in get_file_content("schema.sql").split(";"): - session.execute(st) - session.commit() - self.application = radicale.Application() - - -class GitFileSystem(FileSystem): - """Base class for filesystem tests using Git""" - def setup(self): - super(GitFileSystem, self).setup() - Repo.init(self.colpath) - from radicale.storage import filesystem - filesystem.GIT_REPOSITORY = Repo(self.colpath) - - -class GitMultiFileSystem(GitFileSystem, MultiFileSystem): - """Base class for multifilesystem tests using Git""" - - -class CustomStorageSystem(BaseTest): - """Base class for custom backend tests.""" - storage_type = "custom" - - def setup(self): - """Setup function for each test.""" - self.colpath = tempfile.mkdtemp() - config.set("storage", "type", self.storage_type) - config.set("storage", "custom_handler", "tests.custom.storage") - from tests.custom import storage - storage.FOLDER = self.colpath - storage.GIT_REPOSITORY = None - self.application = radicale.Application() - - def teardown(self): - """Teardown function for each test.""" - shutil.rmtree(self.colpath) - - -class AuthSystem(BaseTest): - """Base class to test Radicale with Htpasswd authentication""" - def setup(self): - self.userpass = "dG1wOmJlcG8=" - - def teardown(self): - config.set("auth", "type", "None") - radicale.auth.is_authenticated = lambda *_: True diff --git a/tests/test_auth.py b/tests/test_auth.py index fb31464..0a1f61e 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -27,18 +27,25 @@ import hashlib import os import radicale import tempfile -from tests import AuthSystem from radicale import config from radicale.auth import htpasswd +from tests import BaseTest -class TestBaseAuthRequests(AuthSystem): +class TestBaseAuthRequests(BaseTest): """ Tests basic requests with auth. We should setup auth for each type before create Application object """ + def setup(self): + self.userpass = "dG1wOmJlcG8=" + + def teardown(self): + config.set("auth", "type", "None") + radicale.auth.is_authenticated = lambda *_: True + def test_root(self): self.colpath = tempfile.mkdtemp() htpasswd_file_path = os.path.join(self.colpath, ".htpasswd") diff --git a/tests/test_base.py b/tests/test_base.py index d2c030c..cc1fc3d 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -21,11 +21,15 @@ Radicale tests with simple requests. """ -from . import (FileSystem, MultiFileSystem, DataBaseSystem, - GitFileSystem, GitMultiFileSystem) from .helpers import get_file_content -import sys -from tests import CustomStorageSystem +import radicale +import shutil +import tempfile +from dulwich.repo import Repo +from radicale import config +from sqlalchemy.orm import sessionmaker +from sqlalchemy import create_engine +from tests import BaseTest class BaseRequests(object): @@ -82,10 +86,72 @@ class BaseRequests(object): status, headers, answer = self.request("GET", "/calendar.ics/") assert "VEVENT" not in answer -# Generate classes with different configs -cl_list = [FileSystem, MultiFileSystem, DataBaseSystem, - GitFileSystem, GitMultiFileSystem, CustomStorageSystem] -for cl in cl_list: - classname = "Test%s" % cl.__name__ - setattr(sys.modules[__name__], - classname, type(classname, (BaseRequests, cl), {})) + +class TestFileSystem(BaseRequests, BaseTest): + """Base class for filesystem tests.""" + storage_type = "filesystem" + + def setup(self): + """Setup function for each test.""" + self.colpath = tempfile.mkdtemp() + config.set("storage", "type", self.storage_type) + from radicale.storage import filesystem + filesystem.FOLDER = self.colpath + filesystem.GIT_REPOSITORY = None + self.application = radicale.Application() + + def teardown(self): + """Teardown function for each test.""" + shutil.rmtree(self.colpath) + + +class TestMultiFileSystem(TestFileSystem): + """Base class for multifilesystem tests.""" + storage_type = "multifilesystem" + + +class TestDataBaseSystem(BaseRequests, BaseTest): + """Base class for database tests""" + def setup(self): + config.set("storage", "type", "database") + config.set("storage", "database_url", "sqlite://") + from radicale.storage import database + database.Session = sessionmaker() + database.Session.configure(bind=create_engine("sqlite://")) + session = database.Session() + for st in get_file_content("schema.sql").split(";"): + session.execute(st) + session.commit() + self.application = radicale.Application() + + +class TestGitFileSystem(TestFileSystem): + """Base class for filesystem tests using Git""" + def setup(self): + super(TestGitFileSystem, self).setup() + Repo.init(self.colpath) + from radicale.storage import filesystem + filesystem.GIT_REPOSITORY = Repo(self.colpath) + + +class TestGitMultiFileSystem(TestGitFileSystem, TestMultiFileSystem): + """Base class for multifilesystem tests using Git""" + + +class TestCustomStorageSystem(BaseRequests, BaseTest): + """Base class for custom backend tests.""" + storage_type = "custom" + + def setup(self): + """Setup function for each test.""" + self.colpath = tempfile.mkdtemp() + config.set("storage", "type", self.storage_type) + config.set("storage", "custom_handler", "tests.custom.storage") + from tests.custom import storage + storage.FOLDER = self.colpath + storage.GIT_REPOSITORY = None + self.application = radicale.Application() + + def teardown(self): + """Teardown function for each test.""" + shutil.rmtree(self.colpath) \ No newline at end of file From 1d0418594d36ca3c45e5326bae448ee65873cbe9 Mon Sep 17 00:00:00 2001 From: Sergey Fursov Date: Sun, 29 Dec 2013 15:13:35 +0400 Subject: [PATCH 5/6] Allow attach custom rights backend, small fix in default config file --- config | 7 +++++-- radicale/config.py | 1 + radicale/rights/__init__.py | 11 ++++++++--- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/config b/config index 411d49b..93f38b5 100644 --- a/config +++ b/config @@ -105,12 +105,15 @@ committer = Firstname Lastname [rights] # Rights backend # Value: regex -backend = "regex" +backend = regex # Rights management method -# Value: None | owner_only | owner_write | from_file +# Value: None | owner_only | owner_write | from_file | custom type = None +# Rights custom handler +custom_handler = + # File for rights management from_file file = ~/.config/radicale/rights diff --git a/radicale/config.py b/radicale/config.py index 53a3346..eecd42c 100644 --- a/radicale/config.py +++ b/radicale/config.py @@ -78,6 +78,7 @@ INITIAL_CONFIG = { "rights": { "backend": "regex", "type": "None", + "custom_handler": "", "file": "~/.config/radicale/rights"}, "storage": { "type": "filesystem", diff --git a/radicale/rights/__init__.py b/radicale/rights/__init__.py index 5e9770f..789953b 100644 --- a/radicale/rights/__init__.py +++ b/radicale/rights/__init__.py @@ -31,9 +31,14 @@ from .. import config def load(): """Load list of available storage managers.""" storage_type = config.get("rights", "backend") - root_module = __import__( - "rights.%s" % storage_type, globals=globals(), level=2) - module = getattr(root_module, storage_type) + if storage_type == 'custom': + rights_module = config.get("rights", "custom_handler") + __import__(rights_module) + module = sys.modules[rights_module] + else: + root_module = __import__( + "rights.%s" % storage_type, globals=globals(), level=2) + module = getattr(root_module, storage_type) sys.modules[__name__].authorized = module.authorized return module From 13629764005ce2fb4fd9a6f78f074ec27e1d123b Mon Sep 17 00:00:00 2001 From: Sergey Fursov Date: Sun, 29 Dec 2013 18:30:53 +0400 Subject: [PATCH 6/6] fixed default config --- config | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config b/config index 93f38b5..2d93905 100644 --- a/config +++ b/config @@ -104,11 +104,11 @@ committer = Firstname Lastname [rights] # Rights backend -# Value: regex +# Value: regex | custom backend = regex # Rights management method -# Value: None | owner_only | owner_write | from_file | custom +# Value: None | owner_only | owner_write | from_file type = None # Rights custom handler