ruleset.py
author Paul Crowley <paul@lshift.net>
Wed, 28 May 2008 18:14:15 +0100
changeset 31 d54720d47ca2
parent 23 9fa62cfd2821
child 32 4059dbe9f26a
permissions -rw-r--r--
start to move towards things living where they should and new break-in system. Big change.

# Copyright 2008 LShift Ltd
# Author(s):
# Paul Crowley <paul@lshift.net>
#
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.

import re

allowedchars = "A-Za-z0-9_-"

goodpathre = re.compile("([%s]+/)*[%s]+$" % (allowedchars, allowedchars))
def goodpath(path):
    return goodpathre.match(path) is not None

goodglobre = re.compile("[*/%s]+$" % allowedchars)

def goodglob(pattern):
    return goodglobre.match(pattern) is not None

# Don't put anything except *A-Za-z0-9_- in rule globs or   
# it will match nothing.  No regexp metachars, not even .
# We may fix this later.
def globmatcher(pattern):
    if not goodglob(pattern):
        #fail("Bad glob pattern in auth config: %s" % pattern)
        # FIXME: report it somehow
        return lambda x: False
    # Substitution cunning so ** can be different from *
    pattern = pattern.replace("*", "[]")
    pattern = pattern.replace("[][]", "[/%s]*" % allowedchars)
    pattern = pattern.replace("[]", "[%s]*" % allowedchars)
    rex = re.compile(pattern + "$")
    # None matches everything
    return lambda x: x is None or rex.match(x) is not None

def rule(pairs):
    matchers = [(k, globmatcher(v)) for k, v in pairs]
    def c(**kw):
        for k, m in matchers:
            if k not in kw or not m(kw[k]):
                return False
        return True
    return c

class Ruleset(object):
    '''Class representing the rules in a rule file'''
    
    levels = ["init", "write", "read", "deny"]

    def __init__(self):
        self.rules = []
        self.preset = {}

    def add(self, action, conditions):
        self.rules.append((action, conditions))


    def set(self, **kw):
        self.preset.update(kw)
        
    def matchrule(self, **kw):
        d = self.preset.copy()
        d.update(**kw)
        for a, c in self.rules:
            if c(**d):
                return a
        return None

    def allow(self, level, **kw):
        a = self.matchrule(**kw)
        return a in self.levels and self.levels.index(a) <= self.levels.index(level)
    
    @classmethod
    def readfile(cls, fn):
        res = cls()
        f = open(fn)
        try:
            for l in f:
                l = l.strip()
                if len(l) == 0 or l.startswith("#"):
                    continue
                l = l.split()
                res.add(l[0], rule([c.split("=", 1) for c in l[1:]]))
        finally:
            f.close()
        return res