Code cleaned and modules renamed
*Radicale is probably broken now*
This commit is contained in:
parent
a17ad1b6a3
commit
45afac5353
10
config
10
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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -17,7 +17,7 @@
|
||||
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
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
|
@ -18,7 +18,7 @@
|
||||
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
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)
|
@ -17,7 +17,7 @@
|
||||
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
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):
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
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)
|
@ -17,16 +17,17 @@
|
||||
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
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):
|
@ -19,7 +19,7 @@
|
||||
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
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):
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
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)
|
||||
|
||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
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
|
||||
|
@ -49,7 +49,7 @@ INITIAL_CONFIG = {
|
||||
"encoding": {
|
||||
"request": "utf-8",
|
||||
"stock": "utf-8"},
|
||||
"acl": {
|
||||
"auth": {
|
||||
"type": "None",
|
||||
"public_users": "public",
|
||||
"private_users": "private",
|
||||
|
@ -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)
|
||||
|
||||
|
@ -19,39 +19,20 @@
|
||||
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
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)
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
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
|
||||
|
@ -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)
|
||||
|
||||
|
7
setup.py
7
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"],
|
||||
|
Loading…
Reference in New Issue
Block a user