import initial
darcs-hash:20060424144524-4ec08-ffa238679b7d042c8189c88ed0987e6dc5d04b3e.gz
This commit is contained in:
parent
0c55eb2eda
commit
2043d004fa
1 changed files with 359 additions and 0 deletions
359
wiki/wikiacl.py
Normal file
359
wiki/wikiacl.py
Normal file
|
@ -0,0 +1,359 @@
|
|||
# -*- coding: iso-8859-1 -*-
|
||||
"""
|
||||
MoinMoin Access Control Lists
|
||||
|
||||
@copyright: 2003 by Thomas Waldmann, http://linuxwiki.de/ThomasWaldmann
|
||||
@copyright: 2003 by Gustavo Niemeyer, http://moin.conectiva.com.br/GustavoNiemeyer
|
||||
@license: GNU GPL, see COPYING for details.
|
||||
"""
|
||||
|
||||
import re
|
||||
from MoinMoin import user
|
||||
|
||||
class AccessControlList:
|
||||
''' Access Control List
|
||||
|
||||
Control who may do what on or with a wiki page.
|
||||
|
||||
Syntax of an ACL string:
|
||||
|
||||
[+|-]User[,User,...]:[right[,right,...]] [[+|-]SomeGroup:...] ...
|
||||
... [[+|-]Known:...] [[+|-]All:...]
|
||||
|
||||
"User" is a user name and triggers only if the user matches. Up
|
||||
to version 1.2 only WikiNames were supported, as of version 1.3,
|
||||
any name can be used in acl lines, including name with spaces
|
||||
using esoteric languages.
|
||||
|
||||
"SomeGroup" is a page name matching cfg.page_group_regex with
|
||||
some lines in the form " * Member", defining the group members.
|
||||
|
||||
"Known" is a group containing all valid / known users.
|
||||
|
||||
"All" is a group containing all users (Known and Anonymous users).
|
||||
|
||||
"right" may be an arbitrary word like read, write, delete, admin.
|
||||
Only words in cfg.acl_validrights are accepted, others are
|
||||
ignored. It is allowed to specify no rights, which means that no
|
||||
rights are given.
|
||||
|
||||
How ACL is processed
|
||||
|
||||
When some user is trying to access some ACL-protected resource,
|
||||
the ACLs will be processed in the order they are found. The first
|
||||
matching ACL will tell if the user has access to that resource
|
||||
or not.
|
||||
|
||||
For example, the following ACL tells that SomeUser is able to
|
||||
read and write the resources protected by that ACL, while any
|
||||
member of SomeGroup (besides SomeUser, if part of that group)
|
||||
may also admin that, and every other user is able to read it.
|
||||
|
||||
SomeUser:read,write SomeGroup:read,write,admin All:read
|
||||
|
||||
In this example, SomeUser can read and write but can not admin,
|
||||
revert or delete pages. Rights that are NOT specified on the
|
||||
right list are automatically set to NO.
|
||||
|
||||
Using Prefixes
|
||||
|
||||
To make the system more flexible, there are also two modifiers:
|
||||
the prefixes "+" and "-".
|
||||
|
||||
+SomeUser:read -OtherUser:write
|
||||
|
||||
The acl line above will grant SomeUser read right, and OtherUser
|
||||
write right, but will NOT block automatically all other rights
|
||||
for these users. For example, if SomeUser ask to write, the
|
||||
above acl line does not define if he can or can not write. He
|
||||
will be able to write if acl_rights_before or acl_rights_after
|
||||
allow this (see configuration options).
|
||||
|
||||
Using prefixes, this acl line:
|
||||
|
||||
SomeUser:read,write SomeGroup:read,write,admin All:read
|
||||
|
||||
Can be written as:
|
||||
|
||||
-SomeUser:admin SomeGroup:read,write,admin All:read
|
||||
|
||||
Or even:
|
||||
|
||||
+All:read -SomeUser:admin SomeGroup:read,write,admin
|
||||
|
||||
Notice that you probably will not want to use the second and
|
||||
third examples in ACL entries of some page. They are very
|
||||
useful on the moin configuration entries though.
|
||||
|
||||
Configuration options
|
||||
|
||||
cfg.acl_enabled
|
||||
If true will enable ACL support.
|
||||
Default: 0
|
||||
|
||||
cfg.acl_rights_default
|
||||
It is is ONLY used when no other ACLs are given.
|
||||
Default: "Known:read,write,delete All:read,write",
|
||||
|
||||
cfg.acl_rights_before
|
||||
When the page has ACL entries, this will be inserted BEFORE
|
||||
any page entries.
|
||||
Default: ""
|
||||
|
||||
cfg.acl_rights_after
|
||||
When the page has ACL entries, this will be inserted AFTER
|
||||
any page entries.
|
||||
Default: ""
|
||||
|
||||
cfg.acl_rights_valid
|
||||
These are the acceptable (known) rights (and the place to
|
||||
extend, if necessary).
|
||||
Default: ["read", "write", "delete", "admin"]
|
||||
'''
|
||||
|
||||
special_users = ["All", "Known", "Trusted"]
|
||||
|
||||
def __init__(self, request, lines=[]):
|
||||
"""Initialize an ACL, starting from <nothing>.
|
||||
"""
|
||||
self.setLines(request.cfg, lines)
|
||||
|
||||
def setLines(self, cfg, lines=[]):
|
||||
self.clean()
|
||||
self.addBefore(cfg)
|
||||
if not lines:
|
||||
self.addDefault(cfg)
|
||||
else:
|
||||
for line in lines:
|
||||
self.addLine(cfg, line)
|
||||
self.addAfter(cfg)
|
||||
|
||||
def clean(self):
|
||||
self.acl = [] # [ ('User', {"read": 0, ...}), ... ]
|
||||
self.acl_lines = []
|
||||
self._is_group = {}
|
||||
|
||||
def addBefore(self, cfg):
|
||||
self.addLine(cfg, cfg.acl_rights_before, remember=0)
|
||||
def addDefault(self, cfg):
|
||||
self.addLine(cfg, cfg.acl_rights_default, remember=0)
|
||||
def addAfter(self, cfg):
|
||||
self.addLine(cfg, cfg.acl_rights_after, remember=0)
|
||||
|
||||
def addLine(self, cfg, aclstring, remember=1):
|
||||
""" Add another ACL line
|
||||
|
||||
This can be used in multiple subsequent calls to process longer
|
||||
lists.
|
||||
|
||||
@param cfg: current config
|
||||
@param aclstring: acl string from page or cfg
|
||||
@param remember: should add the line to self.acl_lines
|
||||
"""
|
||||
# FIXME: should compile this once and cache (in cfg?)
|
||||
group_re = re.compile(cfg.page_group_regex)
|
||||
|
||||
# Remember lines
|
||||
if remember:
|
||||
self.acl_lines.append(aclstring)
|
||||
|
||||
# Iterate over entries and rights, parsed by acl string iterator
|
||||
acliter = ACLStringIterator(cfg.acl_rights_valid, aclstring)
|
||||
for modifier, entries, rights in acliter:
|
||||
if entries == ['Default']:
|
||||
self.addDefault(cfg)
|
||||
continue
|
||||
|
||||
for entry in entries:
|
||||
if group_re.search(entry):
|
||||
self._is_group[entry] = 1
|
||||
rightsdict = {}
|
||||
if modifier:
|
||||
# Only user rights are added to the right dict.
|
||||
# + add rights with value of 1
|
||||
# - add right with value of 0
|
||||
for right in rights:
|
||||
rightsdict[right] = (modifier == '+')
|
||||
else:
|
||||
# All rights from acl_rights_valid are added to the
|
||||
# dict, user rights with value of 1, and other with
|
||||
# value of 0
|
||||
for right in cfg.acl_rights_valid:
|
||||
rightsdict[right] = (right in rights)
|
||||
self.acl.append((entry, rightsdict))
|
||||
|
||||
def may(self, request, name, dowhat):
|
||||
"""May <name> <dowhat>?
|
||||
Returns boolean answer.
|
||||
"""
|
||||
if not request.cfg.acl_enabled:
|
||||
# everybody may read and write:
|
||||
if dowhat in ["read", "write",]:
|
||||
return 1
|
||||
# only known users may do some more dangerous things:
|
||||
if request.user.valid:
|
||||
if dowhat in ["delete", "revert",]:
|
||||
return 1
|
||||
# in any other case, we better disallow it:
|
||||
return 0
|
||||
|
||||
is_group_member = request.dicts.has_member
|
||||
|
||||
allowed = None
|
||||
for entry, rightsdict in self.acl:
|
||||
if entry in self.special_users:
|
||||
handler = getattr(self, "_special_"+entry, None)
|
||||
allowed = handler(request, name, dowhat, rightsdict)
|
||||
elif self._is_group.get(entry) and is_group_member(entry, name):
|
||||
allowed = rightsdict.get(dowhat)
|
||||
elif entry == name:
|
||||
allowed = rightsdict.get(dowhat)
|
||||
if allowed is not None:
|
||||
return allowed
|
||||
return 0
|
||||
|
||||
def getString(self, b='#acl ', e='\n'):
|
||||
"""print the acl strings we were fed with"""
|
||||
return ''.join(["%s%s%s" % (b,l,e) for l in self.acl_lines])
|
||||
|
||||
def _special_All(self, request, name, dowhat, rightsdict):
|
||||
return rightsdict.get(dowhat)
|
||||
|
||||
def _special_Known(self, request, name, dowhat, rightsdict):
|
||||
""" check if user <name> is known to us,
|
||||
that means that there is a valid user account present.
|
||||
works for subscription emails.
|
||||
"""
|
||||
if user.getUserId(request, name): # is a user with this name known?
|
||||
return rightsdict.get(dowhat)
|
||||
return None
|
||||
|
||||
def _special_Trusted(self, request, name, dowhat, rightsdict):
|
||||
""" check if user <name> is known AND even has logged in using a password.
|
||||
does not work for subsription emails that should be sent to <user>,
|
||||
as he is not logged in in that case.
|
||||
"""
|
||||
if request.user.trusted and name == request.user.name:
|
||||
return rightsdict.get(dowhat)
|
||||
return None
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.acl_lines == other.acl_lines
|
||||
def __ne__(self, other):
|
||||
return self.acl_lines != other.acl_lines
|
||||
|
||||
|
||||
class ACLStringIterator:
|
||||
""" Iterator for acl string
|
||||
|
||||
Parse acl string and return the next entry on each call to
|
||||
next. Implement the Iterator protocol.
|
||||
|
||||
Usage:
|
||||
iter = ACLStringIterator(cfg.acl_rights_valid, 'user name:right')
|
||||
for modifier, entries, rights in iter:
|
||||
# process data
|
||||
"""
|
||||
|
||||
def __init__(self, rights, aclstring):
|
||||
""" Initialize acl iterator
|
||||
|
||||
@param rights: the acl rights to consider when parsing
|
||||
@param aclstirng: string to parse
|
||||
"""
|
||||
self.rights = rights
|
||||
self.rest = aclstring.strip()
|
||||
self.finished = 0
|
||||
|
||||
def __iter__(self):
|
||||
""" Required by the Iterator protocol """
|
||||
return self
|
||||
|
||||
def next(self):
|
||||
""" Return the next values from the acl string
|
||||
|
||||
When the iterator is finished and you try to call next, it
|
||||
raises a StopIteration. The iterator finish as soon as the
|
||||
string is fully parsed or can not be parsed any more.
|
||||
|
||||
@rtype: 3 tuple - (modifier, [entry, ...], [right, ...])
|
||||
@return: values for one item in an acl string
|
||||
"""
|
||||
# Handle finished state, required by iterator protocol
|
||||
if self.rest == '':
|
||||
self.finished = 1
|
||||
if self.finished:
|
||||
raise StopIteration
|
||||
|
||||
# Get optional modifier [+|-]entries:rights
|
||||
modifier = ''
|
||||
if self.rest[0] in ('+', '-'):
|
||||
modifier, self.rest = self.rest[0], self.rest[1:]
|
||||
|
||||
# Handle the Default meta acl
|
||||
if self.rest.startswith('Default ') or self.rest == 'Default':
|
||||
self.rest = self.rest[8:]
|
||||
entries, rights = ['Default'], []
|
||||
|
||||
# Handle entries:rights pairs
|
||||
else:
|
||||
# Get entries
|
||||
try:
|
||||
# XXX TODO disallow : and , in usernames
|
||||
entries, self.rest = self.rest.split(':', 1)
|
||||
except ValueError:
|
||||
self.finished = 1
|
||||
raise StopIteration("Can't parse rest of string")
|
||||
if entries == '':
|
||||
entries = []
|
||||
else:
|
||||
# TODO strip each entry from blanks?
|
||||
entries = entries.split(',')
|
||||
|
||||
# Get rights
|
||||
try:
|
||||
rights, self.rest = self.rest.split(' ', 1)
|
||||
# Remove extra white space after rights fragment,
|
||||
# allowing using multiple spaces between items.
|
||||
self.rest = self.rest.lstrip()
|
||||
except ValueError:
|
||||
rights, self.rest = self.rest, ''
|
||||
rights = [r for r in rights.split(',') if r in self.rights]
|
||||
|
||||
return modifier, entries, rights
|
||||
|
||||
|
||||
def parseACL(request, body):
|
||||
""" Parse acl lines on page and return ACL object
|
||||
|
||||
Use ACL object may(request, dowhat) to get acl rights.
|
||||
"""
|
||||
if not request.cfg.acl_enabled:
|
||||
return AccessControlList(request)
|
||||
|
||||
acl_lines = []
|
||||
while body and body[0] == '#':
|
||||
# extract first line
|
||||
try:
|
||||
line, body = body.split('\n', 1)
|
||||
except ValueError:
|
||||
line = body
|
||||
body = ''
|
||||
|
||||
# end parsing on empty (invalid) PI
|
||||
if line == "#":
|
||||
break
|
||||
|
||||
# skip comments (lines with two hash marks)
|
||||
if line[1] == '#':
|
||||
continue
|
||||
|
||||
tokens = line.split(None, 1)
|
||||
if tokens[0].lower() == "#acl":
|
||||
if len(tokens) == 2:
|
||||
args = tokens[1].rstrip()
|
||||
else:
|
||||
args = ""
|
||||
acl_lines.append(args)
|
||||
return AccessControlList(request, acl_lines)
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue