Browse Source

Merge branch 'authentication' of github.com:cristen/Radicale into authentication

Jean-Marc Martins 12 years ago
parent
commit
6c40f5e24a
8 changed files with 48 additions and 52 deletions
  1. 2 2
      NEWS.rst
  2. 1 6
      config
  3. 18 22
      radicale/__init__.py
  4. 0 2
      radicale/config.py
  5. 17 13
      radicale/rights.py
  6. 5 5
      radicale/storage/database.py
  7. 1 2
      schema.sql
  8. 4 0
      tests/__init__.py

+ 2 - 2
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)

+ 1 - 6
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

+ 18 - 22
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:

+ 0 - 2
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",

+ 17 - 13
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))

+ 5 - 5
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]

+ 1 - 2
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,

+ 4 - 0
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