diff --git a/config b/config index d0469c4..32a72ac 100644 --- a/config +++ b/config @@ -36,8 +36,8 @@ request = utf-8 stock = utf-8 -[acl] -# Access method +[auth] +# Authentication method # Value: None | htpasswd | LDAP | PAM | courier type = None @@ -78,6 +78,12 @@ pam_group_membership = courier_socket = +[rights] +# Rights management method +# Value: None | owner_only +type = None + + [storage] # Storage backend type = filesystem diff --git a/radicale/__init__.py b/radicale/__init__.py index d88af77..b24a84c 100644 --- a/radicale/__init__.py +++ b/radicale/__init__.py @@ -200,7 +200,7 @@ class Application(object): # Check rights if not items or not access or function == self.options: - # No collection, or no acl, or OPTIONS request: don't check rights + # No collection, or no auth, or OPTIONS request: don't check rights status, headers, answer = function(environ, items, content, None) else: # Ask authentication backend to check rights @@ -213,23 +213,22 @@ class Application(object): else: user = password = None - if access.is_authenticated(user, password): - last_collection_allowed = None allowed_items = [] for item in items: log.LOGGER.debug("Testing %s" % (item.name)) if not isinstance(item, ical.Collection): # item is not a colleciton, it's the child of the last - # collection we've met in the loop. Only add this item if - # this last collection was allowed. log.LOGGER.info("not a collection: " + collection.name) - # collections.append(collection) + # collection we've met in the loop. Only add this item + # if this last collection was allowed. if last_collection_allowed: allowed_items.append(item) else: - if access.may_read(user, item) or access.may_write(user, item): - log.LOGGER.info(user + "has access to " + item.name) + if access.read_authorized(user, item) or \ + access.write_authorized(user, item): + log.LOGGER.info("%s has access to %s" % ( + user, item.name)) last_collection_allowed = True allowed_items.append(item) else: @@ -242,18 +241,17 @@ class Application(object): else: # Good user and no collections found, redirect user to home location = "/%s/" % str(quote(user)) - if path != location: + if path == location: + # Send answer anyway since else we're getting into a + # redirect loop + status, headers, answer = function( + environ, allowed_items, content, user) + else: log.LOGGER.info("redirecting to %s" % location) status = client.FOUND headers = {"Location": location} answer = "Redirecting to %s" % location - else: - # Send answer anyway since else we're getting into a redirect loop - status, headers, answer = function( - environ, allowed_items, content, user) - else: - # Unknown or unauthorized user log.LOGGER.info( "%s refused" % (user or "Anonymous user")) @@ -262,9 +260,6 @@ class Application(object): "WWW-Authenticate": "Basic realm=\"Radicale Server - Password Required\""} answer = None - - - # Set content length if answer: @@ -279,18 +274,14 @@ class Application(object): # Return response content return [answer] if answer else [] - - - + def response_not_allowed(self): + """Return a standard "not allowed" response.""" headers = { "WWW-Authenticate": "Basic realm=\"Radicale Server - Password Required\""} return client.FORBIDDEN, headers, None - - - # All these functions must have the same parameters, some are useless # pylint: disable=W0612,W0613,R0201 @@ -311,20 +302,15 @@ class Application(object): etag = environ.get("HTTP_IF_MATCH", item.etag).replace("\\", "") if etag == item.etag: # No ETag precondition or precondition verified, delete item - if access.may_write(user, collection): + if access.write_authorized(user, collection): answer = xmlutils.delete(environ["PATH_INFO"], collection) return client.OK, {}, answer else: return self.response_not_allowed() - # No item or ETag precondition not verified, do not delete item return client.PRECONDITION_FAILED, {}, None - - - - def get(self, environ, collections, content, user): """Manage GET request. @@ -346,23 +332,23 @@ class Application(object): # Get collection item item = collection.get_item(item_name) if item: - if access.may_read(user, collection): + if access.read_authorized(user, collection): items = collection.timezones items.append(item) answer_text = ical.serialize( collection.tag, collection.headers, items) etag = item.etag else: - return self.response_not_allowed() + return self.response_not_allowed() else: return client.GONE, {}, None else: # Create the collection if it does not exist - if not collection.exists and access.may_write(user, collection): + if not collection.exists and access.write_authorized(user, collection): log.LOGGER.debug("creating collection " + collection.name) collection.write() - if access.may_read(user, collection): + if access.read_authorized(user, collection): # Get whole collection answer_text = collection.text etag = collection.etag @@ -376,18 +362,11 @@ class Application(object): answer = answer_text.encode(self.encoding) return client.OK, headers, answer - - - def head(self, environ, collections, content, user): """Manage HEAD request.""" status, headers, answer = self.get(environ, collections, content, user) return status, headers, None - - - - def mkcalendar(self, environ, collections, content, user): """Manage MKCALENDAR request.""" collection = collections[0] @@ -399,15 +378,12 @@ class Application(object): with collection.props as collection_props: for key, value in props.items(): collection_props[key] = value - if access.may_write(user, collection): + if access.write_authorized(user, collection): collection.write() else: return self.response_not_allowed() return client.CREATED, {}, None - - - def mkcol(self, environ, collections, content, user): """Manage MKCOL request.""" collection = collections[0] @@ -415,15 +391,12 @@ class Application(object): with collection.props as collection_props: for key, value in props.items(): collection_props[key] = value - if access.may_write(user, collection): + if access.write_authorized(user, collection): collection.write() else: return self.response_not_allowed() return client.CREATED, {}, None - - - def move(self, environ, collections, content, user): """Manage MOVE request.""" from_collection = collections[0] @@ -439,7 +412,8 @@ class Application(object): to_path, to_name = to_url.rstrip("/").rsplit("/", 1) to_collection = ical.Collection.from_path( to_path, depth="0")[0] - if access.may_write(user, to_collection) and access.may_write(user.from_collection): + if access.write_authorized(user, to_collection) and \ + access.write_authorized(user.from_collection): to_collection.append(to_name, item.text) from_collection.remove(from_name) return client.CREATED, {}, None @@ -455,22 +429,14 @@ class Application(object): # Moving collections, not supported return client.FORBIDDEN, {}, None - - - - def options(self, environ, collections, content, user): """Manage OPTIONS request.""" headers = { - "Allow": "DELETE, HEAD, GET, MKCALENDAR, MKCOL, MOVE, " \ - "OPTIONS, PROPFIND, PROPPATCH, PUT, REPORT", + "Allow": ("DELETE, HEAD, GET, MKCALENDAR, MKCOL, MOVE, " + "OPTIONS, PROPFIND, PROPPATCH, PUT, REPORT"), "DAV": "1, 2, 3, calendar-access, addressbook, extended-mkcol"} return client.OK, headers, None - - - - def propfind(self, environ, collections, content, user): """Manage PROPFIND request.""" headers = { @@ -480,10 +446,6 @@ class Application(object): environ["PATH_INFO"], content, collections, user) return client.MULTI_STATUS, headers, answer - - - - def proppatch(self, environ, collections, content, user): """Manage PROPPATCH request.""" collection = collections[0] @@ -493,10 +455,6 @@ class Application(object): "Content-Type": "text/xml"} return client.MULTI_STATUS, headers, answer - - - - def put(self, environ, collections, content, user): """Manage PUT request.""" collection = collections[0] @@ -513,13 +471,13 @@ class Application(object): # Case 1: No item and no ETag precondition: Add new item # Case 2: Item and ETag precondition verified: Modify item # Case 3: Item and no Etag precondition: Force modifying item - if access.may_write(user, collection): + if access.write_authorized(user, collection): xmlutils.put(environ["PATH_INFO"], content, collection) status = client.CREATED - # Try to return the etag in the header - # If the added item does't have the same name as the one given by - # the client, then there's no obvious way to generate an etag, we - # can safely ignore it. + # Try to return the etag in the header. + # If the added item does't have the same name as the one given + # by the client, then there's no obvious way to generate an + # etag, we can safely ignore it. new_item = collection.get_item(item_name) if new_item: headers["ETag"] = new_item.etag @@ -530,16 +488,11 @@ class Application(object): status = client.PRECONDITION_FAILED return status, headers, None - - - - - def report(self, environ, collections, content, user): """Manage REPORT request.""" collection = collections[0] headers = {"Content-Type": "text/xml"} - if access.may_read(user, collection): + if access.read_authorized(user, collection): answer = xmlutils.report(environ["PATH_INFO"], content, collection) return client.MULTI_STATUS, headers, answer else: diff --git a/radicale/access.py b/radicale/access.py index bde7706..28af2ae 100644 --- a/radicale/access.py +++ b/radicale/access.py @@ -19,49 +19,43 @@ """ Radicale access module. -Manages access to collections. +Manage access to collections. """ -import os -import sys - -from radicale import acl, authorization, log +from radicale import auth, rights, log +AUTH = None +RIGHTS = None def load(): - log.LOGGER.debug("access.load()") - global aacl ; aacl = acl.load() - global aauthorization ; aauthorization = authorization.load() - + """Load authentication and rights modules.""" + global AUTH, RIGHTS + AUTH = auth.load() + RIGHTS = rights.load() def is_authenticated(user, password): - if (not user): - # No user given - return False - - return aacl.is_authenticated(user, password) - + """Check if the user is authenticated.""" + return AUTH.is_authenticated(user, password) if user else False - -def may_read(user, collection): - """Check if the user is allowed to read the collection""" - - user_authorized = aauthorization.read_authorized(user, collection) - - log.LOGGER.debug("read %s %s -- %i" % (user, collection.owner, user_authorized)) +def read_authorized(user, collection): + """Check if the user is allowed to read the collection.""" + if RIGHTS is None: + return True + user_authorized = RIGHTS.read_authorized(user, collection) + log.LOGGER.debug( + "Read %s %s -- %i" % (user, collection.owner, user_authorized)) return user_authorized - - -def may_write(user, collection): - """Check if the user is allowed to write the collection""" - - user_authorized = aauthorization.write_authorized(user, collection) - - log.LOGGER.debug("write %s %s -- %i" % (user, collection.owner, user_authorized)) +def write_authorized(user, collection): + """Check if the user is allowed to write the collection.""" + if RIGHTS is None: + return True + user_authorized = RIGHTS.write_authorized(user, collection) + log.LOGGER.debug( + "Write %s %s -- %i" % (user, collection.owner, user_authorized)) return user_authorized diff --git a/radicale/acl/IMAP.py b/radicale/auth/IMAP.py similarity index 77% rename from radicale/acl/IMAP.py rename to radicale/auth/IMAP.py index f9d9718..61203a2 100644 --- a/radicale/acl/IMAP.py +++ b/radicale/auth/IMAP.py @@ -17,7 +17,7 @@ # along with Radicale. If not, see . """ -IMAP ACL. +IMAP authentication. Secure authentication based on the ``imaplib`` module. @@ -32,17 +32,17 @@ Python 3.2 or newer is required for TLS. import imaplib -from radicale import acl, config, log +from radicale import config, log -IMAP_SERVER = config.get("acl", "imap_auth_host_name") -IMAP_SERVER_PORT = config.get("acl", "imap_auth_host_port") +IMAP_SERVER = config.get("auth", "imap_auth_host_name") +IMAP_SERVER_PORT = config.get("auth", "imap_auth_host_port") def is_authenticated(user, password): """Check if ``user``/``password`` couple is valid.""" log.LOGGER.debug( - "[IMAP ACL] Connecting to %s:%s." % (IMAP_SERVER, IMAP_SERVER_PORT,)) + "[IMAP AUTH] Connecting to %s:%s." % (IMAP_SERVER, IMAP_SERVER_PORT,)) connection = imaplib.IMAP4(host=IMAP_SERVER, port=IMAP_SERVER_PORT) server_is_local = (IMAP_SERVER == "localhost") @@ -50,20 +50,20 @@ def is_authenticated(user, password): connection_is_secure = False try: connection.starttls() - log.LOGGER.debug("[IMAP ACL] Server connection changed to TLS.") + log.LOGGER.debug("IMAP server connection changed to TLS.") connection_is_secure = True except AttributeError: if not server_is_local: log.LOGGER.error( - "[IMAP ACL] Python 3.2 or newer is required for TLS.") + "Python 3.2 or newer is required for IMAP + TLS.") except (imaplib.IMAP4.error, imaplib.IMAP4.abort) as exception: log.LOGGER.warning( - "[IMAP ACL] Server at %s failed to accept TLS connection " + "IMAP server at %s failed to accept TLS connection " "because of: %s" % (IMAP_SERVER, exception)) if server_is_local and not connection_is_secure: log.LOGGER.warning( - "[IMAP ACL] Server is local. " + "IMAP server is local. " "Will allow transmitting unencrypted credentials.") if connection_is_secure or server_is_local: @@ -71,16 +71,16 @@ def is_authenticated(user, password): connection.login(user, password) connection.logout() log.LOGGER.debug( - "[IMAP ACL] Authenticated user %s " + "Authenticated IMAP user %s " "via %s." % (user, IMAP_SERVER)) return True except (imaplib.IMAP4.error, imaplib.IMAP4.abort) as exception: log.LOGGER.error( - "[IMAP ACL] Server could not authenticate user %s " + "IMAP server could not authenticate user %s " "because of: %s" % (user, exception)) else: log.LOGGER.critical( - "[IMAP ACL] Server did not support TLS and is not ``localhost``. " + "IMAP server did not support TLS and is not ``localhost``. " "Refusing to transmit passwords under these conditions. " "Authentication attempt aborted.") return False # authentication failed diff --git a/radicale/acl/LDAP.py b/radicale/auth/LDAP.py similarity index 81% rename from radicale/acl/LDAP.py rename to radicale/auth/LDAP.py index 9e77d17..538deb5 100644 --- a/radicale/acl/LDAP.py +++ b/radicale/auth/LDAP.py @@ -18,7 +18,7 @@ # along with Radicale. If not, see . """ -LDAP ACL. +LDAP authentication. Authentication based on the ``python-ldap`` module (http://www.python-ldap.org/). @@ -26,16 +26,16 @@ Authentication based on the ``python-ldap`` module """ import ldap -from radicale import acl, config, log +from radicale import config, log -BASE = config.get("acl", "ldap_base") -ATTRIBUTE = config.get("acl", "ldap_attribute") -FILTER = config.get("acl", "ldap_filter") -CONNEXION = ldap.initialize(config.get("acl", "ldap_url")) -BINDDN = config.get("acl", "ldap_binddn") -PASSWORD = config.get("acl", "ldap_password") -SCOPE = getattr(ldap, "SCOPE_%s" % config.get("acl", "ldap_scope").upper()) +BASE = config.get("auth", "ldap_base") +ATTRIBUTE = config.get("auth", "ldap_attribute") +FILTER = config.get("auth", "ldap_filter") +CONNEXION = ldap.initialize(config.get("auth", "ldap_url")) +BINDDN = config.get("auth", "ldap_binddn") +PASSWORD = config.get("auth", "ldap_password") +SCOPE = getattr(ldap, "SCOPE_%s" % config.get("auth", "ldap_scope").upper()) def is_authenticated(user, password): @@ -46,7 +46,7 @@ def is_authenticated(user, password): CONNEXION.whoami_s() except: log.LOGGER.debug("Reconnecting the LDAP server") - CONNEXION = ldap.initialize(config.get("acl", "ldap_url")) + CONNEXION = ldap.initialize(config.get("auth", "ldap_url")) if BINDDN and PASSWORD: log.LOGGER.debug("Initial LDAP bind as %s" % BINDDN) diff --git a/radicale/acl/PAM.py b/radicale/auth/PAM.py similarity index 94% rename from radicale/acl/PAM.py rename to radicale/auth/PAM.py index 68ea857..6aad96a 100644 --- a/radicale/acl/PAM.py +++ b/radicale/auth/PAM.py @@ -17,7 +17,7 @@ # along with Radicale. If not, see . """ -PAM ACL. +PAM authentication. Authentication based on the ``pam-python`` module. @@ -27,10 +27,10 @@ import grp import pam import pwd -from radicale import acl, config, log +from radicale import config, log -GROUP_MEMBERSHIP = config.get("acl", "pam_group_membership") +GROUP_MEMBERSHIP = config.get("auth", "pam_group_membership") def is_authenticated(user, password): diff --git a/radicale/authorization/allauthenticated.py b/radicale/auth/__init__.py similarity index 50% rename from radicale/authorization/allauthenticated.py rename to radicale/auth/__init__.py index cf991de..f21d122 100644 --- a/radicale/authorization/allauthenticated.py +++ b/radicale/auth/__init__.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- # # This file is part of Radicale Server - Calendar Server -# Copyright © 2011-2012 Guillaume Ayoub +# Copyright © 2008-2012 Guillaume Ayoub +# Copyright © 2008 Nicolas Kandel +# Copyright © 2008 Pascal Halter # # 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 @@ -17,31 +19,20 @@ # along with Radicale. If not, see . """ -Radicale authorization module. - -Manages who is authorized to access a collection. - -The policy here is that all authenticated users -have read and write access to all collections. +Authentication management. """ -import os -import sys - -from radicale import authorization, config, log +from radicale import config, log - - -def read_authorized(user, collection): - """Check if the user is allowed to read the collection""" - log.LOGGER.debug("read_authorized '" + user + "' in '" + collection.name + "'"); - return True - - -def write_authorized(user, collection): - """Check if the user is allowed to write the collection""" - log.LOGGER.debug("write_authorized '" + user + "' in '" + collection.name + "'"); - return True - +def load(): + """Load list of available authentication managers.""" + auth_type = config.get("auth", "type") + log.LOGGER.debug("Authentication type is %s" % auth_type) + if auth_type == "None": + return None + else: + module = __import__( + "auth.%s" % auth_type, globals=globals(), level=2) + return getattr(module, auth_type) diff --git a/radicale/acl/courier.py b/radicale/auth/courier.py similarity index 93% rename from radicale/acl/courier.py rename to radicale/auth/courier.py index 462cfbe..3dd72dd 100644 --- a/radicale/acl/courier.py +++ b/radicale/auth/courier.py @@ -17,16 +17,17 @@ # along with Radicale. If not, see . """ -Courier-Authdaemon ACL. +Courier-Authdaemon authentication. """ import sys import socket -from radicale import acl, config, log + +from radicale import config, log -COURIER_SOCKET = config.get("acl", "courier_socket") +COURIER_SOCKET = config.get("auth", "courier_socket") def is_authenticated(user, password): diff --git a/radicale/acl/htpasswd.py b/radicale/auth/htpasswd.py similarity index 93% rename from radicale/acl/htpasswd.py rename to radicale/auth/htpasswd.py index 4ccccd3..02313c5 100644 --- a/radicale/acl/htpasswd.py +++ b/radicale/auth/htpasswd.py @@ -19,7 +19,7 @@ # along with Radicale. If not, see . """ -Htpasswd ACL. +Htpasswd authentication. Load the list of login/password couples according a the configuration file created by Apache ``htpasswd`` command. Plain-text, crypt and sha1 are @@ -30,11 +30,11 @@ supported, but md5 is not (see ``htpasswd`` man page to understand why). import base64 import hashlib -from radicale import acl, config +from radicale import config -FILENAME = config.get("acl", "htpasswd_filename") -ENCRYPTION = config.get("acl", "htpasswd_encryption") +FILENAME = config.get("auth", "htpasswd_filename") +ENCRYPTION = config.get("auth", "htpasswd_encryption") def _plain(hash_value, password): diff --git a/radicale/authorization/__init__.py b/radicale/authorization/__init__.py deleted file mode 100644 index 8f05533..0000000 --- a/radicale/authorization/__init__.py +++ /dev/null @@ -1,76 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Radicale Server - Calendar Server -# Copyright © 2008-2012 Guillaume Ayoub -# Copyright © 2008 Nicolas Kandel -# Copyright © 2008 Pascal Halter -# -# 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 . - -""" -Users and rights management. - -This module loads a list of users with access rights, according to the acl -configuration. - -""" - -from radicale import config, log - - -AUTHORIZATION_PREFIX = "authorization" - -PUBLIC_USERS = [] -PRIVATE_USERS = [] - - - -def _config_users(name): - """Get an iterable of strings from the configuraton string [acl] ``name``. - - The values must be separated by a comma. The whitespace characters are - stripped at the beginning and at the end of the values. - - """ - for user in config.get(AUTHORIZATION_PREFIX, name).split(","): - user = user.strip() - yield None if user == "None" else user - - -def load(): - """Load list of available ACL managers.""" - - PUBLIC_USERS.extend(_config_users("public_users")) - PRIVATE_USERS.extend(_config_users("private_users")) - - authorization_type = config.get(AUTHORIZATION_PREFIX, "type") - log.LOGGER.debug("auth type = " + authorization_type) - if authorization_type == "None": - return None - else: - module = __import__("authorization.%s" % authorization_type, globals=globals(), level=2) - return getattr(module, authorization_type) - - -def may_read(user, collection): - if (collection.owner not in PRIVATE_USERS and user != collection.owner): - # owner is not private and is not user, forbidden - return False - - return read_authorized(user, collection) - -def may_write(user, collection): - return write_authorized(user, collection) - - \ No newline at end of file diff --git a/radicale/authorization/static.py b/radicale/authorization/static.py deleted file mode 100644 index a821b5a..0000000 --- a/radicale/authorization/static.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Radicale Server - Calendar Server -# Copyright © 2011-2012 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 . - -""" -Radicale authorization module. - -Manages who is authorized to access a collection. - -The policy is that the owner may read and write in -all collections and some special rights are hardcoded. - -""" - -import os -import sys - -from radicale import authorization, config, log -from radicale.authorization import owneronly - - - -def read_authorized(user, collection): - """Check if the user is allowed to read the collection""" - log.LOGGER.debug("read_authorized '" + user + "' in '" + collection.owner + "/" + collection.name + "'"); - - if owneronly.read_authorized(user, collection): - return True - - if user == "user1" and collection.owner == "user2" and collection.name == "user2sharedwithuser1": - return True - if user == "user2" and collection.owner == "user1" and collection.name == "user1sharedwithuser2": - return True - - return False - - -def write_authorized(user, collection): - """Check if the user is allowed to write the collection""" - log.LOGGER.debug("write_authorized '" + user + "' in '" + collection.owner + "/" + collection.name + "'"); - - if owneronly.write_authorized(user, collection): - return True - - if user == "user1" and collection.owner == "user2" and collection.name == "user2sharedwithuser1": - return True - if user == "user2" and collection.owner == "user1" and collection.name == "user1sharedwithuser2": - return False - - return False - diff --git a/radicale/config.py b/radicale/config.py index c484e9b..e38a342 100644 --- a/radicale/config.py +++ b/radicale/config.py @@ -49,7 +49,7 @@ INITIAL_CONFIG = { "encoding": { "request": "utf-8", "stock": "utf-8"}, - "acl": { + "auth": { "type": "None", "public_users": "public", "private_users": "private", diff --git a/radicale/ical.py b/radicale/ical.py index d4608c7..62b61cb 100644 --- a/radicale/ical.py +++ b/radicale/ical.py @@ -345,7 +345,7 @@ class Collection(object): items = self.items for new_item in self._parse( - text, (Timezone, Event, Todo, Journal, Card), name): + text, (Timezone, Event, Todo, Journal, Card), name): if new_item.name not in (item.name for item in items): items.append(new_item) diff --git a/radicale/acl/__init__.py b/radicale/rights/__init__.py similarity index 51% rename from radicale/acl/__init__.py rename to radicale/rights/__init__.py index 8be22e4..e401e25 100644 --- a/radicale/acl/__init__.py +++ b/radicale/rights/__init__.py @@ -19,39 +19,20 @@ # along with Radicale. If not, see . """ -Users management. - -ACL is basically the wrong name here since this package deals with authenticating users. - -The authorization part is done in the package "authorization". - -This module loads a list of users with access rights, according to the acl -configuration. +Rights management. """ from radicale import config, log -CONFIG_PREFIX = "acl" - -def _config_users(name): - """Get an iterable of strings from the configuraton string [acl] ``name``. - - The values must be separated by a comma. The whitespace characters are - stripped at the beginning and at the end of the values. - - """ - for user in config.get(CONFIG_PREFIX, name).split(","): - user = user.strip() - yield None if user == "None" else user - def load(): """Load list of available ACL managers.""" - acl_type = config.get(CONFIG_PREFIX, "type") - log.LOGGER.debug("acl_type = " + acl_type) - if acl_type == "None": + rights_type = config.get("rights", "type") + log.LOGGER.debug("Rights type is %s" % rights_type) + if rights_type == "None": return None else: - module = __import__("acl.%s" % acl_type, globals=globals(), level=2) - return getattr(module, acl_type) + module = __import__( + "rights.%s" % rights_type, globals=globals(), level=2) + return getattr(module, rights_type) diff --git a/radicale/authorization/owneronly.py b/radicale/rights/owner_only.py similarity index 58% rename from radicale/authorization/owneronly.py rename to radicale/rights/owner_only.py index d95edb5..0878220 100644 --- a/radicale/authorization/owneronly.py +++ b/radicale/rights/owner_only.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # # This file is part of Radicale Server - Calendar Server -# Copyright © 2011-2012 Guillaume Ayoub +# Copyright © 2012 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 @@ -17,33 +17,18 @@ # along with Radicale. If not, see . """ -Radicale authorization module. +Owner-only based rights. -Manages who is authorized to access a collection. - -The policy here is that owners have read and write access -to their own collections. +Only owners have read and write access to their own collections. """ -import os -import sys - -from radicale import authorization, config, log - - - def read_authorized(user, collection): - """Check if the user is allowed to read the collection""" - log.LOGGER.debug("read_authorized '" + user + "' in '" + collection.owner + "/" + collection.name + "'"); - + """Check if the user is allowed to read the collection.""" return user == collection.owner def write_authorized(user, collection): - """Check if the user is allowed to write the collection""" - log.LOGGER.debug("write_authorized '" + user + "' in '" + collection.owner + "/" + collection.name + "'"); - + """Check if the user is allowed to write the collection.""" return user == collection.owner - diff --git a/radicale/xmlutils.py b/radicale/xmlutils.py index 9a38755..45af81e 100644 --- a/radicale/xmlutils.py +++ b/radicale/xmlutils.py @@ -200,7 +200,7 @@ def propfind(path, xml_request, collections, user=None): multistatus = ET.Element(_tag("D", "multistatus")) for collection in collections: - if access.may_read(user, collection): + if access.read_authorized(user, collection): response = _propfind_response(path, collection, props, user) multistatus.append(response) diff --git a/setup.py b/setup.py index d2ae274..6ae1dc3 100755 --- a/setup.py +++ b/setup.py @@ -51,11 +51,12 @@ setup( author="Guillaume Ayoub", author_email="guillaume.ayoub@kozea.fr", url="http://www.radicale.org/", - download_url="http://pypi.python.org/packages/source/R/Radicale/" \ - "Radicale-%s.tar.gz" % radicale.VERSION, + download_url=("http://pypi.python.org/packages/source/R/Radicale/" + "Radicale-%s.tar.gz" % radicale.VERSION), license="GNU GPL v3", platforms="Any", - packages=["radicale", "radicale.acl", "radicale.storage"], + packages=[ + "radicale", "radicale.auth", "radicale.rights", "radicale.storage"], provides=["radicale"], scripts=["bin/radicale"], keywords=["calendar", "addressbook", "CalDAV", "CardDAV"],