Merge branch 'authentication' of github.com:cristen/Radicale into authentication
This commit is contained in:
commit
6c40f5e24a
4
NEWS.rst
4
NEWS.rst
@ -3,8 +3,8 @@
|
|||||||
======
|
======
|
||||||
|
|
||||||
|
|
||||||
0.8.1 - *Not released yet*
|
0.9 - *Not released yet*
|
||||||
==========================
|
========================
|
||||||
|
|
||||||
* 1-file-per-event storage (by Jean-Marc Martins)
|
* 1-file-per-event storage (by Jean-Marc Martins)
|
||||||
* Git support for filesystem storages (by Jean-Marc Martins)
|
* Git support for filesystem storages (by Jean-Marc Martins)
|
||||||
|
7
config
7
config
@ -30,7 +30,7 @@ dns_lookup = True
|
|||||||
# Root URL of Radicale (starting and ending with a slash)
|
# Root URL of Radicale (starting and ending with a slash)
|
||||||
base_prefix = /
|
base_prefix = /
|
||||||
# Message displayed in the client when a password is needed
|
# Message displayed in the client when a password is needed
|
||||||
realm = Radicale - Password Required lol
|
realm = Radicale - Password Required
|
||||||
|
|
||||||
|
|
||||||
[encoding]
|
[encoding]
|
||||||
@ -45,11 +45,6 @@ stock = utf-8
|
|||||||
# Value: None | htpasswd | IMAP | LDAP | PAM | courier | http
|
# Value: None | htpasswd | IMAP | LDAP | PAM | courier | http
|
||||||
type = None
|
type = None
|
||||||
|
|
||||||
# Usernames used for public collections, separated by a comma
|
|
||||||
public_users = public
|
|
||||||
# Usernames used for private collections, separated by a comma
|
|
||||||
private_users = private
|
|
||||||
|
|
||||||
# Htpasswd filename
|
# Htpasswd filename
|
||||||
htpasswd_filename = /etc/radicale/users
|
htpasswd_filename = /etc/radicale/users
|
||||||
# Htpasswd encryption method
|
# Htpasswd encryption method
|
||||||
|
@ -55,13 +55,6 @@ VERSION = "git"
|
|||||||
# tries to access information they don't have rights to
|
# tries to access information they don't have rights to
|
||||||
NOT_ALLOWED = (client.FORBIDDEN, {}, None)
|
NOT_ALLOWED = (client.FORBIDDEN, {}, None)
|
||||||
|
|
||||||
# Standard "authenticate" response that is returned when a user tries to access
|
|
||||||
# non-public information w/o submitting proper authentication credentials
|
|
||||||
WRONG_CREDENTIALS = (
|
|
||||||
client.UNAUTHORIZED,
|
|
||||||
{"WWW-Authenticate": "Basic realm=\"%s\"" % config.get("server", "realm")},
|
|
||||||
None)
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPServer(wsgiref.simple_server.WSGIServer, object):
|
class HTTPServer(wsgiref.simple_server.WSGIServer, object):
|
||||||
"""HTTP server."""
|
"""HTTP server."""
|
||||||
@ -285,25 +278,28 @@ class Application(object):
|
|||||||
else:
|
else:
|
||||||
user = password = None
|
user = password = None
|
||||||
|
|
||||||
if not items or function == self.options or \
|
read_allowed_items, write_allowed_items = \
|
||||||
auth.is_authenticated(user, password):
|
self.collect_allowed_items(items, user)
|
||||||
|
|
||||||
read_allowed_items, write_allowed_items = \
|
if ((read_allowed_items or write_allowed_items)
|
||||||
self.collect_allowed_items(items, user)
|
and auth.is_authenticated(user, password)) or \
|
||||||
|
function == self.options or not items:
|
||||||
if read_allowed_items or write_allowed_items or \
|
# Collections found, or OPTIONS request, or no items at all
|
||||||
function == self.options or not items:
|
status, headers, answer = function(
|
||||||
# Collections found, or OPTIONS request, or no items at all
|
environ, read_allowed_items, write_allowed_items, content,
|
||||||
status, headers, answer = function(
|
user)
|
||||||
environ, read_allowed_items, write_allowed_items, content,
|
|
||||||
user)
|
|
||||||
else:
|
|
||||||
# Good user but has no rights to any of the given collections
|
|
||||||
status, headers, answer = NOT_ALLOWED
|
|
||||||
else:
|
else:
|
||||||
|
status, headers, answer = NOT_ALLOWED
|
||||||
|
|
||||||
|
if (status, headers, answer) == NOT_ALLOWED and \
|
||||||
|
not auth.is_authenticated(user, password):
|
||||||
# Unknown or unauthorized user
|
# Unknown or unauthorized user
|
||||||
log.LOGGER.info("%s refused" % (user or "Anonymous user"))
|
log.LOGGER.info("%s refused" % (user or "Anonymous user"))
|
||||||
status, headers, answer = WRONG_CREDENTIALS
|
status = client.UNAUTHORIZED
|
||||||
|
headers = {
|
||||||
|
"WWW-Authenticate":
|
||||||
|
"Basic realm=\"%s\"" % config.get("server", "realm")}
|
||||||
|
answer = None
|
||||||
|
|
||||||
# Set content length
|
# Set content length
|
||||||
if answer:
|
if answer:
|
||||||
|
@ -53,8 +53,6 @@ INITIAL_CONFIG = {
|
|||||||
"stock": "utf-8"},
|
"stock": "utf-8"},
|
||||||
"auth": {
|
"auth": {
|
||||||
"type": "None",
|
"type": "None",
|
||||||
"public_users": "public",
|
|
||||||
"private_users": "private",
|
|
||||||
"htpasswd_filename": "/etc/radicale/users",
|
"htpasswd_filename": "/etc/radicale/users",
|
||||||
"htpasswd_encryption": "crypt",
|
"htpasswd_encryption": "crypt",
|
||||||
"imap_hostname": "localhost",
|
"imap_hostname": "localhost",
|
||||||
|
@ -50,8 +50,6 @@ except ImportError:
|
|||||||
# pylint: enable=F0401
|
# pylint: enable=F0401
|
||||||
|
|
||||||
|
|
||||||
FILENAME = os.path.expanduser(config.get("rights", "file"))
|
|
||||||
TYPE = config.get("rights", "type").lower()
|
|
||||||
DEFINED_RIGHTS = {
|
DEFINED_RIGHTS = {
|
||||||
"owner_write": "[r]\nuser:.*\ncollection:.*\npermission:r\n"
|
"owner_write": "[r]\nuser:.*\ncollection:.*\npermission:r\n"
|
||||||
"[w]\nuser:.*\ncollection:^%(login)s/.+$\npermission:w",
|
"[w]\nuser:.*\ncollection:^%(login)s/.+$\npermission:w",
|
||||||
@ -60,17 +58,19 @@ DEFINED_RIGHTS = {
|
|||||||
|
|
||||||
def _read_from_sections(user, collection, permission):
|
def _read_from_sections(user, collection, permission):
|
||||||
"""Get regex sections."""
|
"""Get regex sections."""
|
||||||
|
filename = os.path.expanduser(config.get("rights", "file"))
|
||||||
|
rights_type = config.get("rights", "type").lower()
|
||||||
regex = ConfigParser({"login": user, "path": collection})
|
regex = ConfigParser({"login": user, "path": collection})
|
||||||
if TYPE in DEFINED_RIGHTS:
|
if rights_type in DEFINED_RIGHTS:
|
||||||
log.LOGGER.debug("Rights type '%s'" % TYPE)
|
log.LOGGER.debug("Rights type '%s'" % rights_type)
|
||||||
regex.readfp(io.BytesIO(DEFINED_RIGHTS[TYPE]))
|
regex.readfp(io.BytesIO(DEFINED_RIGHTS[rights_type]))
|
||||||
elif TYPE == "from_file":
|
elif rights_type == "from_file":
|
||||||
log.LOGGER.debug("Reading rights from file %s" % FILENAME)
|
log.LOGGER.debug("Reading rights from file %s" % filename)
|
||||||
if not regex.read(FILENAME):
|
if not regex.read(filename):
|
||||||
log.LOGGER.error("File '%s' not found for rights" % FILENAME)
|
log.LOGGER.error("File '%s' not found for rights" % filename)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
log.LOGGER.error("Unknown rights type '%s'" % TYPE)
|
log.LOGGER.error("Unknown rights type '%s'" % rights_type)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
for section in regex.sections():
|
for section in regex.sections():
|
||||||
@ -91,6 +91,10 @@ def _read_from_sections(user, collection, permission):
|
|||||||
|
|
||||||
|
|
||||||
def authorized(user, collection, right):
|
def authorized(user, collection, right):
|
||||||
"""Check if the user is allowed to read or write the collection."""
|
"""Check if the user is allowed to read or write the collection.
|
||||||
return TYPE == "none" or (user and _read_from_sections(
|
|
||||||
user, collection.url.rstrip("/") or "/", right))
|
If the user is empty it checks for anonymous rights
|
||||||
|
"""
|
||||||
|
rights_type = config.get("rights", "type").lower()
|
||||||
|
return rights_type == "none" or (_read_from_sections(
|
||||||
|
user or "", collection.url.rstrip("/") or "/", right))
|
||||||
|
@ -78,10 +78,10 @@ class DBLine(Base):
|
|||||||
"""Table of item's lines."""
|
"""Table of item's lines."""
|
||||||
__tablename__ = "line"
|
__tablename__ = "line"
|
||||||
|
|
||||||
key = Column(String, primary_key=True)
|
key = Column(String)
|
||||||
value = Column(String)
|
value = Column(String)
|
||||||
item_name = Column(String, ForeignKey("item.name"), primary_key=True)
|
item_name = Column(String, ForeignKey("item.name"))
|
||||||
timestamp = Column(DateTime, default=datetime.now)
|
timestamp = Column(DateTime, default=datetime.now, primary_key=True)
|
||||||
|
|
||||||
item = relationship(
|
item = relationship(
|
||||||
"DBItem", backref="lines", order_by=timestamp)
|
"DBItem", backref="lines", order_by=timestamp)
|
||||||
@ -116,7 +116,7 @@ class Collection(ical.Collection):
|
|||||||
items = (
|
items = (
|
||||||
self.session.query(DBItem)
|
self.session.query(DBItem)
|
||||||
.filter_by(collection_path=self.path, tag=item_type.tag)
|
.filter_by(collection_path=self.path, tag=item_type.tag)
|
||||||
.order_by("name").all())
|
.order_by(DBItem.name).all())
|
||||||
for item in items:
|
for item in items:
|
||||||
text = "\n".join(
|
text = "\n".join(
|
||||||
"%s:%s" % (line.key, line.value) for line in item.lines)
|
"%s:%s" % (line.key, line.value) for line in item.lines)
|
||||||
@ -189,7 +189,7 @@ class Collection(ical.Collection):
|
|||||||
headers = (
|
headers = (
|
||||||
self.session.query(DBHeader)
|
self.session.query(DBHeader)
|
||||||
.filter_by(collection_path=self.path)
|
.filter_by(collection_path=self.path)
|
||||||
.order_by("key").all())
|
.order_by(DBHeader.key).all())
|
||||||
return [
|
return [
|
||||||
ical.Header("%s:%s" % (header.key, header.value))
|
ical.Header("%s:%s" % (header.key, header.value))
|
||||||
for header in headers]
|
for header in headers]
|
||||||
|
@ -19,8 +19,7 @@ create table line (
|
|||||||
key varchar not null,
|
key varchar not null,
|
||||||
value varchar not null,
|
value varchar not null,
|
||||||
item_name varchar references item (name) not null,
|
item_name varchar references item (name) not null,
|
||||||
timestamp timestamp not null,
|
timestamp timestamp not null);
|
||||||
primary key (key, value, item_name, timestamp));
|
|
||||||
|
|
||||||
create table property (
|
create table property (
|
||||||
key varchar not null,
|
key varchar not null,
|
||||||
|
@ -33,6 +33,10 @@ from io import BytesIO
|
|||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
|
||||||
import radicale
|
import radicale
|
||||||
|
|
||||||
|
os.environ["RADICALE_CONFIG"] = os.path.join(os.path.dirname(
|
||||||
|
os.path.dirname(__file__)), "config")
|
||||||
|
|
||||||
from radicale import config
|
from radicale import config
|
||||||
from radicale.auth import htpasswd
|
from radicale.auth import htpasswd
|
||||||
from radicale.storage import filesystem, database
|
from radicale.storage import filesystem, database
|
||||||
|
Loading…
Reference in New Issue
Block a user