Use the same import mechanisms for auth, storage, rights
This commit is contained in:
parent
3ddbb80674
commit
17857654b0
@ -46,11 +46,17 @@ except ImportError:
|
|||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
# pylint: enable=F0401,E0611
|
# pylint: enable=F0401,E0611
|
||||||
|
|
||||||
from radicale import access, config, ical, log, storage, xmlutils
|
from radicale import auth, config, ical, log, rights, storage, xmlutils
|
||||||
|
|
||||||
|
|
||||||
VERSION = "git"
|
VERSION = "git"
|
||||||
|
|
||||||
|
# Standard "not allowed" response
|
||||||
|
NOT_ALLOWED = (
|
||||||
|
client.FORBIDDEN,
|
||||||
|
{"WWW-Authenticate": "Basic realm=\"Radicale - Password Required\""},
|
||||||
|
None)
|
||||||
|
|
||||||
|
|
||||||
class HTTPServer(wsgiref.simple_server.WSGIServer, object):
|
class HTTPServer(wsgiref.simple_server.WSGIServer, object):
|
||||||
"""HTTP server."""
|
"""HTTP server."""
|
||||||
@ -119,7 +125,8 @@ class Application(object):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialize application."""
|
"""Initialize application."""
|
||||||
super(Application, self).__init__()
|
super(Application, self).__init__()
|
||||||
access.load()
|
auth.load()
|
||||||
|
rights.load()
|
||||||
storage.load()
|
storage.load()
|
||||||
self.encoding = config.get("encoding", "request")
|
self.encoding = config.get("encoding", "request")
|
||||||
if config.getboolean("logging", "full_environment"):
|
if config.getboolean("logging", "full_environment"):
|
||||||
@ -202,20 +209,21 @@ class Application(object):
|
|||||||
authorization = environ.get("HTTP_AUTHORIZATION", None)
|
authorization = environ.get("HTTP_AUTHORIZATION", None)
|
||||||
|
|
||||||
if authorization:
|
if authorization:
|
||||||
auth = authorization.lstrip("Basic").strip().encode("ascii")
|
authorization = \
|
||||||
|
authorization.lstrip("Basic").strip().encode("ascii")
|
||||||
user, password = self.decode(
|
user, password = self.decode(
|
||||||
base64.b64decode(auth), environ).split(":")
|
base64.b64decode(authorization), environ).split(":")
|
||||||
else:
|
else:
|
||||||
user = password = None
|
user = password = None
|
||||||
|
|
||||||
if not items or function == self.options or \
|
if not items or function == self.options or \
|
||||||
access.is_authenticated(user, password):
|
auth.is_authenticated(user, password):
|
||||||
last_collection_allowed = None
|
last_collection_allowed = None
|
||||||
allowed_items = []
|
allowed_items = []
|
||||||
for item in items:
|
for item in items:
|
||||||
if isinstance(item, ical.Collection):
|
if isinstance(item, ical.Collection):
|
||||||
if access.read_authorized(user, item) or \
|
if rights.read_authorized(user, item) or \
|
||||||
access.write_authorized(user, item):
|
rights.write_authorized(user, item):
|
||||||
log.LOGGER.info("%s has access to collection %s" % (
|
log.LOGGER.info("%s has access to collection %s" % (
|
||||||
user, item.name or "/"))
|
user, item.name or "/"))
|
||||||
last_collection_allowed = True
|
last_collection_allowed = True
|
||||||
@ -277,13 +285,6 @@ class Application(object):
|
|||||||
# Return response content
|
# Return response content
|
||||||
return [answer] if answer else []
|
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
|
# All these functions must have the same parameters, some are useless
|
||||||
# pylint: disable=W0612,W0613,R0201
|
# pylint: disable=W0612,W0613,R0201
|
||||||
|
|
||||||
@ -304,11 +305,11 @@ class Application(object):
|
|||||||
etag = environ.get("HTTP_IF_MATCH", item.etag).replace("\\", "")
|
etag = environ.get("HTTP_IF_MATCH", item.etag).replace("\\", "")
|
||||||
if etag == item.etag:
|
if etag == item.etag:
|
||||||
# No ETag precondition or precondition verified, delete item
|
# No ETag precondition or precondition verified, delete item
|
||||||
if access.write_authorized(user, collection):
|
if rights.write_authorized(user, collection):
|
||||||
answer = xmlutils.delete(environ["PATH_INFO"], collection)
|
answer = xmlutils.delete(environ["PATH_INFO"], collection)
|
||||||
return client.OK, {}, answer
|
return client.OK, {}, answer
|
||||||
else:
|
else:
|
||||||
return self.response_not_allowed()
|
return NOT_ALLOWED
|
||||||
|
|
||||||
# No item or ETag precondition not verified, do not delete item
|
# No item or ETag precondition not verified, do not delete item
|
||||||
return client.PRECONDITION_FAILED, {}, None
|
return client.PRECONDITION_FAILED, {}, None
|
||||||
@ -334,36 +335,35 @@ class Application(object):
|
|||||||
# Get collection item
|
# Get collection item
|
||||||
item = collection.get_item(item_name)
|
item = collection.get_item(item_name)
|
||||||
if item:
|
if item:
|
||||||
if access.read_authorized(user, collection):
|
if rights.read_authorized(user, collection):
|
||||||
items = collection.timezones
|
items = collection.timezones
|
||||||
items.append(item)
|
items.append(item)
|
||||||
answer_text = ical.serialize(
|
answer_text = ical.serialize(
|
||||||
collection.tag, collection.headers, items)
|
collection.tag, collection.headers, items)
|
||||||
etag = item.etag
|
etag = item.etag
|
||||||
else:
|
else:
|
||||||
return self.response_not_allowed()
|
return NOT_ALLOWED
|
||||||
else:
|
else:
|
||||||
return client.GONE, {}, None
|
return client.GONE, {}, None
|
||||||
else:
|
else:
|
||||||
# Create the collection if it does not exist
|
# Create the collection if it does not exist
|
||||||
if not collection.exists and \
|
if not collection.exists and \
|
||||||
access.write_authorized(user, collection):
|
rights.write_authorized(user, collection):
|
||||||
log.LOGGER.debug("Creating collection %s" % collection.name)
|
log.LOGGER.debug("Creating collection %s" % collection.name)
|
||||||
collection.write()
|
collection.write()
|
||||||
|
|
||||||
if access.read_authorized(user, collection):
|
if rights.read_authorized(user, collection):
|
||||||
# Get whole collection
|
# Get whole collection
|
||||||
answer_text = collection.text
|
answer_text = collection.text
|
||||||
etag = collection.etag
|
etag = collection.etag
|
||||||
|
headers = {
|
||||||
|
"Content-Type": collection.mimetype,
|
||||||
|
"Last-Modified": collection.last_modified,
|
||||||
|
"ETag": etag}
|
||||||
|
answer = answer_text.encode(self.encoding)
|
||||||
|
return client.OK, headers, answer
|
||||||
else:
|
else:
|
||||||
return self.response_not_allowed()
|
return NOT_ALLOWED
|
||||||
|
|
||||||
headers = {
|
|
||||||
"Content-Type": collection.mimetype,
|
|
||||||
"Last-Modified": collection.last_modified,
|
|
||||||
"ETag": etag}
|
|
||||||
answer = answer_text.encode(self.encoding)
|
|
||||||
return client.OK, headers, answer
|
|
||||||
|
|
||||||
def head(self, environ, collections, content, user):
|
def head(self, environ, collections, content, user):
|
||||||
"""Manage HEAD request."""
|
"""Manage HEAD request."""
|
||||||
@ -373,32 +373,32 @@ class Application(object):
|
|||||||
def mkcalendar(self, environ, collections, content, user):
|
def mkcalendar(self, environ, collections, content, user):
|
||||||
"""Manage MKCALENDAR request."""
|
"""Manage MKCALENDAR request."""
|
||||||
collection = collections[0]
|
collection = collections[0]
|
||||||
props = xmlutils.props_from_request(content)
|
if rights.write_authorized(user, collection):
|
||||||
timezone = props.get("C:calendar-timezone")
|
props = xmlutils.props_from_request(content)
|
||||||
if timezone:
|
timezone = props.get("C:calendar-timezone")
|
||||||
collection.replace("", timezone)
|
if timezone:
|
||||||
del props["C:calendar-timezone"]
|
collection.replace("", timezone)
|
||||||
with collection.props as collection_props:
|
del props["C:calendar-timezone"]
|
||||||
for key, value in props.items():
|
with collection.props as collection_props:
|
||||||
collection_props[key] = value
|
for key, value in props.items():
|
||||||
if access.write_authorized(user, collection):
|
collection_props[key] = value
|
||||||
collection.write()
|
collection.write()
|
||||||
|
return client.CREATED, {}, None
|
||||||
else:
|
else:
|
||||||
return self.response_not_allowed()
|
return NOT_ALLOWED
|
||||||
return client.CREATED, {}, None
|
|
||||||
|
|
||||||
def mkcol(self, environ, collections, content, user):
|
def mkcol(self, environ, collections, content, user):
|
||||||
"""Manage MKCOL request."""
|
"""Manage MKCOL request."""
|
||||||
collection = collections[0]
|
collection = collections[0]
|
||||||
props = xmlutils.props_from_request(content)
|
if rights.write_authorized(user, collection):
|
||||||
with collection.props as collection_props:
|
props = xmlutils.props_from_request(content)
|
||||||
for key, value in props.items():
|
with collection.props as collection_props:
|
||||||
collection_props[key] = value
|
for key, value in props.items():
|
||||||
if access.write_authorized(user, collection):
|
collection_props[key] = value
|
||||||
collection.write()
|
collection.write()
|
||||||
|
return client.CREATED, {}, None
|
||||||
else:
|
else:
|
||||||
return self.response_not_allowed()
|
return NOT_ALLOWED
|
||||||
return client.CREATED, {}, None
|
|
||||||
|
|
||||||
def move(self, environ, collections, content, user):
|
def move(self, environ, collections, content, user):
|
||||||
"""Manage MOVE request."""
|
"""Manage MOVE request."""
|
||||||
@ -415,13 +415,13 @@ class Application(object):
|
|||||||
to_path, to_name = to_url.rstrip("/").rsplit("/", 1)
|
to_path, to_name = to_url.rstrip("/").rsplit("/", 1)
|
||||||
to_collection = ical.Collection.from_path(
|
to_collection = ical.Collection.from_path(
|
||||||
to_path, depth="0")[0]
|
to_path, depth="0")[0]
|
||||||
if access.write_authorized(user, to_collection) and \
|
if rights.write_authorized(user, to_collection) and \
|
||||||
access.write_authorized(user.from_collection):
|
rights.write_authorized(user.from_collection):
|
||||||
to_collection.append(to_name, item.text)
|
to_collection.append(to_name, item.text)
|
||||||
from_collection.remove(from_name)
|
from_collection.remove(from_name)
|
||||||
return client.CREATED, {}, None
|
return client.CREATED, {}, None
|
||||||
else:
|
else:
|
||||||
return self.response_not_allowed()
|
return NOT_ALLOWED
|
||||||
else:
|
else:
|
||||||
# Remote destination server, not supported
|
# Remote destination server, not supported
|
||||||
return client.BAD_GATEWAY, {}, None
|
return client.BAD_GATEWAY, {}, None
|
||||||
@ -442,6 +442,7 @@ class Application(object):
|
|||||||
|
|
||||||
def propfind(self, environ, collections, content, user):
|
def propfind(self, environ, collections, content, user):
|
||||||
"""Manage PROPFIND request."""
|
"""Manage PROPFIND request."""
|
||||||
|
# Rights is handled by collection in xmlutils.propfind
|
||||||
headers = {
|
headers = {
|
||||||
"DAV": "1, 2, 3, calendar-access, addressbook, extended-mkcol",
|
"DAV": "1, 2, 3, calendar-access, addressbook, extended-mkcol",
|
||||||
"Content-Type": "text/xml"}
|
"Content-Type": "text/xml"}
|
||||||
@ -452,11 +453,15 @@ class Application(object):
|
|||||||
def proppatch(self, environ, collections, content, user):
|
def proppatch(self, environ, collections, content, user):
|
||||||
"""Manage PROPPATCH request."""
|
"""Manage PROPPATCH request."""
|
||||||
collection = collections[0]
|
collection = collections[0]
|
||||||
answer = xmlutils.proppatch(environ["PATH_INFO"], content, collection)
|
if rights.write_authorized(user, collection):
|
||||||
headers = {
|
answer = xmlutils.proppatch(
|
||||||
"DAV": "1, 2, 3, calendar-access, addressbook, extended-mkcol",
|
environ["PATH_INFO"], content, collection)
|
||||||
"Content-Type": "text/xml"}
|
headers = {
|
||||||
return client.MULTI_STATUS, headers, answer
|
"DAV": "1, 2, 3, calendar-access, addressbook, extended-mkcol",
|
||||||
|
"Content-Type": "text/xml"}
|
||||||
|
return client.MULTI_STATUS, headers, answer
|
||||||
|
else:
|
||||||
|
return NOT_ALLOWED
|
||||||
|
|
||||||
def put(self, environ, collections, content, user):
|
def put(self, environ, collections, content, user):
|
||||||
"""Manage PUT request."""
|
"""Manage PUT request."""
|
||||||
@ -474,7 +479,7 @@ class Application(object):
|
|||||||
# Case 1: No item and no ETag precondition: Add new item
|
# Case 1: No item and no ETag precondition: Add new item
|
||||||
# Case 2: Item and ETag precondition verified: Modify item
|
# Case 2: Item and ETag precondition verified: Modify item
|
||||||
# Case 3: Item and no Etag precondition: Force modifying item
|
# Case 3: Item and no Etag precondition: Force modifying item
|
||||||
if access.write_authorized(user, collection):
|
if rights.write_authorized(user, collection):
|
||||||
xmlutils.put(environ["PATH_INFO"], content, collection)
|
xmlutils.put(environ["PATH_INFO"], content, collection)
|
||||||
status = client.CREATED
|
status = client.CREATED
|
||||||
# Try to return the etag in the header.
|
# Try to return the etag in the header.
|
||||||
@ -485,7 +490,7 @@ class Application(object):
|
|||||||
if new_item:
|
if new_item:
|
||||||
headers["ETag"] = new_item.etag
|
headers["ETag"] = new_item.etag
|
||||||
else:
|
else:
|
||||||
return self.response_not_allowed()
|
return NOT_ALLOWED
|
||||||
else:
|
else:
|
||||||
# PUT rejected in all other cases
|
# PUT rejected in all other cases
|
||||||
status = client.PRECONDITION_FAILED
|
status = client.PRECONDITION_FAILED
|
||||||
@ -495,10 +500,10 @@ class Application(object):
|
|||||||
"""Manage REPORT request."""
|
"""Manage REPORT request."""
|
||||||
collection = collections[0]
|
collection = collections[0]
|
||||||
headers = {"Content-Type": "text/xml"}
|
headers = {"Content-Type": "text/xml"}
|
||||||
if access.read_authorized(user, collection):
|
if rights.read_authorized(user, collection):
|
||||||
answer = xmlutils.report(environ["PATH_INFO"], content, collection)
|
answer = xmlutils.report(environ["PATH_INFO"], content, collection)
|
||||||
return client.MULTI_STATUS, headers, answer
|
return client.MULTI_STATUS, headers, answer
|
||||||
else:
|
else:
|
||||||
return self.response_not_allowed()
|
return NOT_ALLOWED
|
||||||
|
|
||||||
# pylint: enable=W0612,W0613,R0201
|
# pylint: enable=W0612,W0613,R0201
|
||||||
|
@ -1,63 +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 access module.
|
|
||||||
|
|
||||||
Manage access to collections.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
from radicale import auth, rights, log
|
|
||||||
|
|
||||||
AUTH = None
|
|
||||||
RIGHTS = None
|
|
||||||
|
|
||||||
|
|
||||||
def load():
|
|
||||||
"""Load authentication and rights modules."""
|
|
||||||
global AUTH, RIGHTS
|
|
||||||
AUTH = auth.load()
|
|
||||||
RIGHTS = rights.load()
|
|
||||||
|
|
||||||
|
|
||||||
def is_authenticated(user, password):
|
|
||||||
"""Check if the user is authenticated."""
|
|
||||||
if AUTH is None:
|
|
||||||
return True
|
|
||||||
return AUTH.is_authenticated(user, password) if user else False
|
|
||||||
|
|
||||||
|
|
||||||
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 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
|
|
@ -23,6 +23,8 @@ Authentication management.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
from radicale import config, log
|
from radicale import config, log
|
||||||
|
|
||||||
|
|
||||||
@ -33,6 +35,18 @@ def load():
|
|||||||
if auth_type == "None":
|
if auth_type == "None":
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
module = __import__(
|
root_module = __import__(
|
||||||
"auth.%s" % auth_type, globals=globals(), level=2)
|
"auth.%s" % auth_type, globals=globals(), level=2)
|
||||||
return getattr(module, auth_type)
|
module = getattr(root_module, auth_type)
|
||||||
|
# Override auth.is_authenticated
|
||||||
|
sys.modules[__name__].is_authenticated = module.is_authenticated
|
||||||
|
return module
|
||||||
|
|
||||||
|
|
||||||
|
def is_authenticated(user, password):
|
||||||
|
"""Check if the user is authenticated.
|
||||||
|
|
||||||
|
This method is overriden if an auth module is loaded.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return True # Default is always True: no authentication
|
||||||
|
@ -23,6 +23,8 @@ Rights management.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
from radicale import config, log
|
from radicale import config, log
|
||||||
|
|
||||||
|
|
||||||
@ -33,6 +35,28 @@ def load():
|
|||||||
if rights_type == "None":
|
if rights_type == "None":
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
module = __import__(
|
root_module = __import__(
|
||||||
"rights.%s" % rights_type, globals=globals(), level=2)
|
"rights.%s" % rights_type, globals=globals(), level=2)
|
||||||
return getattr(module, rights_type)
|
module = getattr(root_module, rights_type)
|
||||||
|
# Override rights.[read|write]_authorized
|
||||||
|
sys.modules[__name__].read_authorized = module.read_authorized
|
||||||
|
sys.modules[__name__].write_authorized = module.write_authorized
|
||||||
|
return module
|
||||||
|
|
||||||
|
|
||||||
|
def read_authorized(user, collection):
|
||||||
|
"""Check if the user is allowed to read the collection.
|
||||||
|
|
||||||
|
This method is overriden if an auth module is loaded.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def write_authorized(user, collection):
|
||||||
|
"""Check if the user is allowed to write the collection.
|
||||||
|
|
||||||
|
This method is overriden if an auth module is loaded.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
@ -24,12 +24,14 @@ configuration.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from radicale import config
|
from radicale import config, ical
|
||||||
|
|
||||||
|
|
||||||
def load():
|
def load():
|
||||||
"""Load list of available storage managers."""
|
"""Load list of available storage managers."""
|
||||||
storage_type = config.get("storage", "type")
|
storage_type = config.get("storage", "type")
|
||||||
module = __import__(
|
root_module = __import__(
|
||||||
"storage.%s" % storage_type, globals=globals(), level=2)
|
"storage.%s" % storage_type, globals=globals(), level=2)
|
||||||
return getattr(module, storage_type)
|
module = getattr(root_module, storage_type)
|
||||||
|
ical.Collection = module.Collection
|
||||||
|
return module
|
||||||
|
@ -112,6 +112,3 @@ class Collection(ical.Collection):
|
|||||||
self._create_dirs()
|
self._create_dirs()
|
||||||
with open(self._props_path, "w") as prop_file:
|
with open(self._props_path, "w") as prop_file:
|
||||||
json.dump(properties, prop_file)
|
json.dump(properties, prop_file)
|
||||||
|
|
||||||
|
|
||||||
ical.Collection = Collection
|
|
||||||
|
@ -35,7 +35,7 @@ except ImportError:
|
|||||||
import re
|
import re
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
from radicale import client, config, ical, access
|
from radicale import client, config, ical, rights
|
||||||
|
|
||||||
|
|
||||||
NAMESPACES = {
|
NAMESPACES = {
|
||||||
@ -200,7 +200,7 @@ def propfind(path, xml_request, collections, user=None):
|
|||||||
multistatus = ET.Element(_tag("D", "multistatus"))
|
multistatus = ET.Element(_tag("D", "multistatus"))
|
||||||
|
|
||||||
for collection in collections:
|
for collection in collections:
|
||||||
if access.read_authorized(user, collection):
|
if rights.read_authorized(user, collection):
|
||||||
response = _propfind_response(path, collection, props, user)
|
response = _propfind_response(path, collection, props, user)
|
||||||
multistatus.append(response)
|
multistatus.append(response)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user