409 lines
14 KiB
Python
409 lines
14 KiB
Python
# -*- 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, search
|
||
|
||
#### HACK SAUVAGE 1/4
|
||
import sys
|
||
sys.path.append('/usr/scripts/gestion/')
|
||
from iptools import is_crans
|
||
#### FIN DU HACK 1/4
|
||
|
||
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"]
|
||
#### HACK SAUVAGE 2/4
|
||
special_users = ["All", "Known", "Trusted", "Crans", "NoCrans"]
|
||
#### FIN DU HACK 2/4
|
||
|
||
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):
|
||
if dowhat == "read" and is_page_public(request):
|
||
return True
|
||
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
|
||
|
||
#### HACK SAUVAGE 3/4
|
||
def _requete_interne(self, request):
|
||
try:
|
||
if str(request.__class__)=='MoinMoin.request.RequestCLI' or is_crans(request.remote_addr) and (request.remote_addr != u'138.231.136.3' or is_crans(request.mpyreq.headers_in['X-Forwarded-For'].split(",")[-1].strip())):
|
||
return True
|
||
except:
|
||
pass
|
||
|
||
return False
|
||
|
||
|
||
def _special_Crans(self, request, name, dowhat, rightsdict):
|
||
if self._requete_interne(request):
|
||
return rightsdict.get(dowhat)
|
||
return None
|
||
|
||
def _special_NoCrans(self, request, name, dowhat, rightsdict):
|
||
if dowhat == "read" and is_page_public(request):
|
||
return True
|
||
if not self._requete_interne(request):
|
||
return rightsdict.get(dowhat)
|
||
return None
|
||
#### FIN Du HACK 3/4
|
||
|
||
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)
|
||
|
||
#### HACK SAUVAGE 4/4
|
||
def is_page_public(request):
|
||
#return True
|
||
## On recherche si la page est publique
|
||
if not request.page:
|
||
return False
|
||
this_page = request.page.page_name
|
||
query = search.QueryParser().parse_query(u'Cat<EFBFBD>goriePagePublique')
|
||
page = search.Page(request, this_page)
|
||
result = query.search(page)
|
||
if result:
|
||
return True
|
||
else:
|
||
return False
|
||
#### FIn DU HACK 4/4
|