diff --git a/NEWS.rst b/NEWS.rst index 4b7d9f49..bb929c87 100644 --- a/NEWS.rst +++ b/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) * Git support for filesystem storages (by Jean-Marc Martins) diff --git a/config b/config index 545ec1a1..c4898e1d 100644 --- a/config +++ b/config @@ -30,7 +30,7 @@ dns_lookup = True # Root URL of Radicale (starting and ending with a slash) base_prefix = / # Message displayed in the client when a password is needed -realm = Radicale - Password Required lol +realm = Radicale - Password Required [encoding] @@ -45,11 +45,6 @@ stock = utf-8 # Value: None | htpasswd | IMAP | LDAP | PAM | courier | http 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 = /etc/radicale/users # Htpasswd encryption method diff --git a/radicale/__init__.py b/radicale/__init__.py index 80af1a4f..92a4ea33 100644 --- a/radicale/__init__.py +++ b/radicale/__init__.py @@ -55,13 +55,6 @@ VERSION = "git" # tries to access information they don't have rights to 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): """HTTP server.""" @@ -285,25 +278,28 @@ class Application(object): else: user = password = None - if not items or function == self.options or \ - auth.is_authenticated(user, password): + read_allowed_items, write_allowed_items = \ + self.collect_allowed_items(items, user) - read_allowed_items, write_allowed_items = \ - self.collect_allowed_items(items, user) - - if read_allowed_items or write_allowed_items or \ - function == self.options or not items: - # Collections found, or OPTIONS request, or no items at all - status, headers, answer = function( - 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 + if ((read_allowed_items or write_allowed_items) + and auth.is_authenticated(user, password)) or \ + function == self.options or not items: + # Collections found, or OPTIONS request, or no items at all + status, headers, answer = function( + environ, read_allowed_items, write_allowed_items, content, + user) else: + status, headers, answer = NOT_ALLOWED + + if (status, headers, answer) == NOT_ALLOWED and \ + not auth.is_authenticated(user, password): # Unknown or unauthorized 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 if answer: diff --git a/radicale/config.py b/radicale/config.py index 8e23c208..37621614 100644 --- a/radicale/config.py +++ b/radicale/config.py @@ -53,8 +53,6 @@ INITIAL_CONFIG = { "stock": "utf-8"}, "auth": { "type": "None", - "public_users": "public", - "private_users": "private", "htpasswd_filename": "/etc/radicale/users", "htpasswd_encryption": "crypt", "imap_hostname": "localhost", diff --git a/radicale/rights.py b/radicale/rights.py index 232fec28..7f741a14 100644 --- a/radicale/rights.py +++ b/radicale/rights.py @@ -50,8 +50,6 @@ except ImportError: # pylint: enable=F0401 -FILENAME = os.path.expanduser(config.get("rights", "file")) -TYPE = config.get("rights", "type").lower() DEFINED_RIGHTS = { "owner_write": "[r]\nuser:.*\ncollection:.*\npermission:r\n" "[w]\nuser:.*\ncollection:^%(login)s/.+$\npermission:w", @@ -60,17 +58,19 @@ DEFINED_RIGHTS = { def _read_from_sections(user, collection, permission): """Get regex sections.""" + filename = os.path.expanduser(config.get("rights", "file")) + rights_type = config.get("rights", "type").lower() regex = ConfigParser({"login": user, "path": collection}) - if TYPE in DEFINED_RIGHTS: - log.LOGGER.debug("Rights type '%s'" % TYPE) - regex.readfp(io.BytesIO(DEFINED_RIGHTS[TYPE])) - elif TYPE == "from_file": - log.LOGGER.debug("Reading rights from file %s" % FILENAME) - if not regex.read(FILENAME): - log.LOGGER.error("File '%s' not found for rights" % FILENAME) + if rights_type in DEFINED_RIGHTS: + log.LOGGER.debug("Rights type '%s'" % rights_type) + regex.readfp(io.BytesIO(DEFINED_RIGHTS[rights_type])) + elif rights_type == "from_file": + log.LOGGER.debug("Reading rights from file %s" % filename) + if not regex.read(filename): + log.LOGGER.error("File '%s' not found for rights" % filename) return False else: - log.LOGGER.error("Unknown rights type '%s'" % TYPE) + log.LOGGER.error("Unknown rights type '%s'" % rights_type) return False for section in regex.sections(): @@ -91,6 +91,10 @@ def _read_from_sections(user, collection, permission): def authorized(user, collection, right): - """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)) + """Check if the user is allowed to read or write the collection. + + 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)) diff --git a/radicale/storage/database.py b/radicale/storage/database.py index f42e75af..0e40b0a7 100644 --- a/radicale/storage/database.py +++ b/radicale/storage/database.py @@ -78,10 +78,10 @@ class DBLine(Base): """Table of item's lines.""" __tablename__ = "line" - key = Column(String, primary_key=True) + key = Column(String) value = Column(String) - item_name = Column(String, ForeignKey("item.name"), primary_key=True) - timestamp = Column(DateTime, default=datetime.now) + item_name = Column(String, ForeignKey("item.name")) + timestamp = Column(DateTime, default=datetime.now, primary_key=True) item = relationship( "DBItem", backref="lines", order_by=timestamp) @@ -116,7 +116,7 @@ class Collection(ical.Collection): items = ( self.session.query(DBItem) .filter_by(collection_path=self.path, tag=item_type.tag) - .order_by("name").all()) + .order_by(DBItem.name).all()) for item in items: text = "\n".join( "%s:%s" % (line.key, line.value) for line in item.lines) @@ -189,7 +189,7 @@ class Collection(ical.Collection): headers = ( self.session.query(DBHeader) .filter_by(collection_path=self.path) - .order_by("key").all()) + .order_by(DBHeader.key).all()) return [ ical.Header("%s:%s" % (header.key, header.value)) for header in headers] diff --git a/schema.sql b/schema.sql index c748e5f6..b6bb8284 100644 --- a/schema.sql +++ b/schema.sql @@ -19,8 +19,7 @@ create table line ( key varchar not null, value varchar not null, item_name varchar references item (name) not null, - timestamp timestamp not null, - primary key (key, value, item_name, timestamp)); + timestamp timestamp not null); create table property ( key varchar not null, diff --git a/tests/__init__.py b/tests/__init__.py index ec5a84da..b5bd194c 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -33,6 +33,10 @@ from io import BytesIO sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) import radicale + +os.environ["RADICALE_CONFIG"] = os.path.join(os.path.dirname( + os.path.dirname(__file__)), "config") + from radicale import config from radicale.auth import htpasswd from radicale.storage import filesystem, database