501 lines
18 KiB
Python
501 lines
18 KiB
Python
# -*- coding: iso-8859-1 -*-
|
||
"""
|
||
MoinMoin - Wiki Security Interface and Access Control Lists
|
||
|
||
|
||
This implements the basic interface for user permissions and
|
||
system policy. If you want to define your own policy, inherit
|
||
from the base class 'Permissions', so that when new permissions
|
||
are defined, you get the defaults.
|
||
|
||
Then assign your new class to "SecurityPolicy" in wikiconfig;
|
||
and I mean the class, not an instance of it!
|
||
|
||
@copyright: 2000-2004 Juergen Hermann <jh@web.de>,
|
||
2003-2008 MoinMoin:ThomasWaldmann,
|
||
2003 Gustavo Niemeyer,
|
||
2005 Oliver Graf,
|
||
2007 Alexander Schremmer
|
||
@license: GNU GPL, see COPYING for details.
|
||
"""
|
||
|
||
import re
|
||
|
||
from MoinMoin import wikiutil, user
|
||
from MoinMoin.Page import Page
|
||
|
||
### HACK SAUVAGE 1/5
|
||
import sys
|
||
sys.path.append('/usr/scripts/gestion/')
|
||
from iptools import is_crans
|
||
### FIN HACK 1/5
|
||
|
||
#############################################################################
|
||
### Basic Permissions Interface -- most features enabled by default
|
||
#############################################################################
|
||
|
||
def _check(request, pagename, username, right):
|
||
""" Check <right> access permission for user <username> on page <pagename>
|
||
|
||
For cfg.acl_hierarchic=False we just check the page in question.
|
||
|
||
For cfg.acl_hierarchic=True we, we check each page in the hierarchy. We
|
||
start with the deepest page and recurse to the top of the tree.
|
||
If one of those permits, True is returned.
|
||
|
||
For both configurations, we check acl_rights_before before the page/default
|
||
acl and acl_rights_after after the page/default acl, of course.
|
||
|
||
This method should not be called by users, use __getattr__ instead.
|
||
|
||
@param request: the current request object
|
||
@param pagename: pagename to get permissions from
|
||
@param username: the user name
|
||
@param right: the right to check
|
||
|
||
@rtype: bool
|
||
@return: True if you have permission or False
|
||
"""
|
||
cache = request.cfg.cache
|
||
allowed = cache.acl_rights_before.may(request, username, right)
|
||
if allowed is not None:
|
||
return allowed
|
||
|
||
if request.cfg.acl_hierarchic:
|
||
pages = pagename.split('/') # create page hierarchy list
|
||
some_acl = False
|
||
for i in range(len(pages), 0, -1):
|
||
# Create the next pagename in the hierarchy
|
||
# starting at the leaf, going to the root
|
||
name = '/'.join(pages[:i])
|
||
# Get page acl and ask for permission
|
||
acl = Page(request, name).getACL(request)
|
||
if acl.acl:
|
||
some_acl = True
|
||
allowed = acl.may(request, username, right)
|
||
if allowed is not None:
|
||
return allowed
|
||
if not some_acl:
|
||
allowed = cache.acl_rights_default.may(request, username, right)
|
||
if allowed is not None:
|
||
return allowed
|
||
else:
|
||
if request.page is not None and pagename == request.page.page_name:
|
||
p = request.page # reuse is good
|
||
else:
|
||
p = Page(request, pagename)
|
||
acl = p.getACL(request) # this will be fast in a reused page obj
|
||
allowed = acl.may(request, username, right)
|
||
if allowed is not None:
|
||
return allowed
|
||
|
||
allowed = cache.acl_rights_after.may(request, username, right)
|
||
if allowed is not None:
|
||
return allowed
|
||
|
||
return False
|
||
|
||
|
||
class Permissions:
|
||
""" Basic interface for user permissions and system policy.
|
||
|
||
Note that you still need to allow some of the related actions, this
|
||
just controls their behavior, not their activation.
|
||
|
||
When sub classing this class, you must extend the class methods, not
|
||
replace them, or you might break the acl in the wiki. Correct sub
|
||
classing looks like this:
|
||
|
||
def read(self, pagename):
|
||
# Your special security rule
|
||
if something:
|
||
return false
|
||
|
||
# Do not return True or you break acl!
|
||
# This call will use the default acl rules
|
||
return Permissions.read(pagename)
|
||
"""
|
||
|
||
def __init__(self, user):
|
||
self.name = user.name
|
||
self.request = user._request
|
||
|
||
def save(self, editor, newtext, rev, **kw):
|
||
""" Check whether user may save a page.
|
||
|
||
`editor` is the PageEditor instance, the other arguments are
|
||
those of the `PageEditor.saveText` method.
|
||
|
||
@param editor: PageEditor instance.
|
||
@param newtext: new page text, you can enable of disable saving according
|
||
to the content of the text, e.g. prevent link spam.
|
||
@param rev: new revision number? XXX
|
||
@param kw: XXX
|
||
@rtype: bool
|
||
@return: True if you can save or False
|
||
"""
|
||
return self.write(editor.page_name)
|
||
|
||
def __getattr__(self, attr):
|
||
""" Shortcut to export getPermission function for all known ACL rights
|
||
|
||
if attr is one of the rights in acl_rights_valid, then return a
|
||
checking function for it. Else raise an AttributeError.
|
||
|
||
@param attr: one of ACL rights as defined in acl_rights_valid
|
||
@rtype: function
|
||
@return: checking function for that right, accepting a pagename
|
||
"""
|
||
request = self.request
|
||
if attr not in request.cfg.acl_rights_valid:
|
||
raise AttributeError, attr
|
||
return lambda pagename: _check(self.request, pagename, self.name, attr)
|
||
|
||
|
||
# make an alias for the default policy
|
||
Default = Permissions
|
||
|
||
|
||
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_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"] # order is important
|
||
### HACK SAUVAGE 2/5
|
||
special_users = ["All", "Known", "Trusted", "Crans", "NoCrans"] # order is important
|
||
### FIN HACK 2/5
|
||
|
||
def __init__(self, cfg, lines=[]):
|
||
"""Initialize an ACL, starting from <nothing>.
|
||
"""
|
||
if lines:
|
||
self.acl = [] # [ ('User', {"read": 0, ...}), ... ]
|
||
self.acl_lines = []
|
||
for line in lines:
|
||
self._addLine(cfg, line)
|
||
else:
|
||
self.acl = None
|
||
self.acl_lines = None
|
||
|
||
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
|
||
"""
|
||
|
||
# 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._addLine(cfg, cfg.acl_rights_default, remember=0)
|
||
else:
|
||
for entry in entries:
|
||
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.
|
||
|
||
Note: this check does NOT include the acl_rights_before / _after ACL,
|
||
but it WILL use acl_rights_default if there is no (page) ACL.
|
||
"""
|
||
if self.acl is None: # no #acl used on Page
|
||
acl = request.cfg.cache.acl_rights_default.acl
|
||
else: # we have a #acl on the page (self.acl can be [] if #acl is empty!)
|
||
acl = self.acl
|
||
is_group_member = request.dicts.has_member
|
||
group_re = request.cfg.cache.page_group_regexact
|
||
allowed = None
|
||
for entry, rightsdict in acl:
|
||
if entry in self.special_users:
|
||
handler = getattr(self, "_special_"+entry, None)
|
||
allowed = handler(request, name, dowhat, rightsdict)
|
||
elif group_re.search(entry):
|
||
if is_group_member(entry, name):
|
||
allowed = rightsdict.get(dowhat)
|
||
else:
|
||
for special in self.special_users:
|
||
if is_group_member(entry, special):
|
||
handler = getattr(self, "_special_" + special, None)
|
||
allowed = handler(request, name, dowhat, rightsdict)
|
||
break # order of self.special_users is important
|
||
elif entry == name:
|
||
allowed = rightsdict.get(dowhat)
|
||
if allowed is not None:
|
||
return allowed
|
||
return allowed # should be None
|
||
|
||
def getString(self, b='#acl ', e='\n'):
|
||
"""print the acl strings we were fed with"""
|
||
if self.acl_lines:
|
||
acl_lines = ''.join(["%s%s%s" % (b, l, e) for l in self.acl_lines])
|
||
else:
|
||
acl_lines = ''
|
||
return acl_lines
|
||
|
||
def _special_All(self, request, name, dowhat, rightsdict):
|
||
### HACK SAUVAGE 3/5
|
||
if dowhat == "read" and is_page_public(request):
|
||
return True
|
||
### FIN HACK 3/5
|
||
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 has logged in using a trusted
|
||
authentication method.
|
||
Does not work for subsription emails that should be sent to <user>,
|
||
as he is not logged in in that case.
|
||
"""
|
||
if (request.user.name == name and
|
||
request.user.auth_method in request.cfg.auth_methods_trusted):
|
||
return rightsdict.get(dowhat)
|
||
return None
|
||
|
||
### HACK SAUVAGE 4/5
|
||
def _requete_interne(self, request):
|
||
try:
|
||
if is_crans(request.remote_addr):
|
||
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 HACK 4/5
|
||
|
||
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 aclstring: 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:
|
||
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, text):
|
||
""" Parse acl lines from text and return ACL object """
|
||
pi, dummy = wikiutil.get_processing_instructions(text)
|
||
acl_lines = [args for verb, args in pi if verb == 'acl']
|
||
return AccessControlList(request.cfg, acl_lines)
|
||
|
||
### HACK SAUVAGE 5/5
|
||
def is_page_public(request):
|
||
#return True
|
||
## On recherche si la page est publique
|
||
this_page = request.page.page_name
|
||
categories = request.page.getCategories(request)
|
||
if u'Cat<EFBFBD>goriePagePublique' in categories:
|
||
return True
|
||
else:
|
||
return None
|
||
### FIN HACK 5/5
|