Get rid of IP in AccessWrapper
[cds-indico.git] / indico / MaKaC / accessControl.py
blob2765bc9b076357cc54ae2f6bdbf35536e9126d4b
1 # This file is part of Indico.
2 # Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN).
4 # Indico is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License as
6 # published by the Free Software Foundation; either version 3 of the
7 # License, or (at your option) any later version.
9 # Indico is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with Indico; if not, see <http://www.gnu.org/licenses/>.
18 import itertools
19 from persistent import Persistent
20 from functools import wraps
22 from indico.core import signals
23 from MaKaC.common import info
24 from indico.modules.users.legacy import AvatarUserWrapper
25 from indico.modules.groups.legacy import GroupWrapper
26 from MaKaC.common.contextManager import ContextManager
28 def isFullyAccess(level):
29 def wrap(func):
30 @wraps(func)
31 def decorator(*args):
32 # if protected and checking for fully public OR
33 # if not protected and checking for full private
34 if (args[0].isProtected() and level == - 1) or (not args[0].isProtected() and level == 1):
35 return False
36 for child in args[0].getNonInheritingChildren():
37 if child.getAccessController().getAccessProtectionLevel() != level:
38 return False
39 return True
40 return decorator
41 return wrap
43 def getChildren(level):
44 def wrap(func):
45 @wraps(func)
46 def decorator(*args):
47 return [child for child in args[0].getNonInheritingChildren()
48 if child.getAccessController().getAccessProtectionLevel() != level]
49 return decorator
50 return wrap
53 class AccessController(Persistent):
54 """This class keeps access control information both for accessing and
55 modifying which can be related to any conference object. The fact that
56 we have a separated class which handles this allows us to reuse the code
57 and have a common policy.
58 Objects of this class provide 2 list of users (one contains the users
59 who can access the related object and another one the users which can
60 modify it) along with methods for managing them (granting or revoking
61 privileges).
62 Conference objects can delegate the access control to one of this
63 objects so they don't need to implement again the AC mechanism.
64 Parameters:
65 __accessProtection -- (int) Flag which indicates whether the resource
66 the current access controller is related to is protected (on) or
67 not (off).
68 managers -- (PList) List of recognised users or groups (Principal)
69 allowed to modify the related resource
70 allowed -- (PList) List of recognised users or groups (Principal)
71 allowed to access the related resource
72 submitters -- (PList) List of recognised chairpersons/speakers allowed to manage event materials
73 """
75 def __init__( self, owner ):
76 self._accessProtection = 0
77 self._fatherProtection = 0
78 self._hideFromUnauthorizedUsers = 0
79 self.managers = []
80 self.managersEmail = []
81 self.allowed = []
82 self.allowedEmail = []
83 self.requiredDomains = []
84 self.accessKey = ""
85 self.owner = owner
86 self.contactInfo = ""
87 self.submitters = []
88 self.nonInheritingChildren = set()
90 def getOwner(self):
91 return self.owner
93 def setOwner(self, owner):
94 self.owner = owner
96 def unlinkAvatars(self, role):
97 for prin in itertools.chain(self.getSubmitterList(), self.managers, self.allowed):
98 if isinstance(prin, AvatarUserWrapper):
99 prin.unlinkTo(self.owner, role)
101 def _getAccessProtection( self ):
102 try:
103 return self._accessProtection
104 except:
105 self._accessProtection = 0
106 return 0
108 def getAccessProtectionLevel( self ):
109 return self._getAccessProtection()
111 def _getFatherProtection( self ):
112 try:
113 from MaKaC.conference import Conference
114 if isinstance(self.getOwner(), Conference):
115 ownerList = self.getOwner().getOwnerList()
116 else:
117 ownerList = [self.getOwner().getOwner()]
118 for o in ownerList:
119 if o is not None and o.isProtected():
120 return 1
121 return 0
122 except Exception:
123 self._fatherProtection = 0
124 return 0
126 def isHidden( self ):
127 try:
128 return self._hideFromUnauthorizedUsers
129 except AttributeError:
130 self._hideFromUnauthorizedUsers = 0
131 return 0
133 def setHidden(self, hidden):
134 self._hideFromUnauthorizedUsers = hidden
135 self._p_changed = 1
137 def isProtected( self ):
138 """tells whether the associated resource is read protected or not"""
139 return (self._getAccessProtection() + self._getFatherProtection()) > 0
141 def isItselfProtected(self):
142 return self._getAccessProtection() > 0
144 def setProtection( self, protected ):
145 self._accessProtection = protected
146 self._p_changed = 1
148 def isFatherProtected(self):
149 return self._getFatherProtection()
151 def setFatherProtection( self, protected ):
152 self._fatherProtection = protected
153 self._p_changed = 1
155 def grantAccess(self, principal):
156 """grants read access for the related resource to the specified
157 principal"""
159 if principal not in self.allowed and isinstance(principal, (AvatarUserWrapper, GroupWrapper)):
160 self.allowed.append(principal)
161 self._p_changed = 1
162 signals.acl.access_granted.send(self, principal=principal)
164 def getAccessEmail(self):
165 try:
166 return self.allowedEmail
167 except:
168 self.allowedEmail = []
169 return self.allowedEmail
171 def grantAccessEmail(self, email):
172 if not email in self.getAccessEmail():
173 self.getAccessEmail().append(email)
174 signals.acl.access_granted.send(self, principal=email)
176 def revokeAccessEmail(self, email):
177 if email in self.getAccessEmail.keys():
178 self.getAccessEmail().remove(email)
179 signals.acl.access_revoked.send(self, principal=email)
181 def revokeAccess( self, principal ):
182 """revokes read access for the related resource to the specified
183 principal"""
184 #ToDo: Revoking access to a user is quite more complex than this. We can
185 # just remove the user, but if there's a group in the list that
186 # contains the user, the access to the user is not revoked, he still
187 # has access. But dunno what to do in that case: raise an exception??
188 # Certainly removing the user from the group or the group itself
189 # wouldn't be acceptable solutions.
190 if principal in self.allowed:
191 self.allowed.remove( principal )
192 self._p_changed = 1
193 signals.acl.access_revoked.send(self, principal=principal)
195 def setAccessKey( self, key="" ):
196 self.accessKey = key
198 def getAccessKey( self ):
199 try:
200 return self.accessKey
201 except:
202 self.setAccessKey()
203 return ""
205 def canKeyAccess( self, key ):
208 if self.getAccessKey()!="":
209 if key == self.getAccessKey():
210 return True
211 return False
213 @classmethod
214 def isHarvesterIP( cls, ip ):
215 minfo = info.HelperMaKaCInfo.getMaKaCInfoInstance()
216 ipList = minfo.getIPBasedACLMgr().get_full_access_acl()
218 return ip in ipList
220 def canIPAccess(self, ip):
222 # Domain protection
223 if not self.getRequiredDomainList():
224 return True
225 return any(domain.belongsTo(ip) for domain in self.getRequiredDomainList())
227 def isAdmin(self, av):
228 return av and av.user and av.user.is_admin
230 def canUserAccess(self, av):
231 if self.isAdmin(av):
232 return True
233 for principal in self.allowed:
234 if principal is None:
235 self.revokeAccess(principal)
236 continue
237 if isinstance(principal, (AvatarUserWrapper, GroupWrapper)) and principal.containsUser(av):
238 return True
239 if isinstance(av, AvatarUserWrapper):
240 for email in av.getEmails():
241 if email in self.getAccessEmail():
242 self.grantAccess(av)
243 self.revokeAccessEmail(email)
244 av.linkTo(self.getOwner(), "manager")
245 return False
247 def getAccessList(self):
248 """returns a list of those principals which have access privileges"""
249 for principal in list(self.allowed):
250 if principal is None:
251 self.revokeAccess(principal)
252 elif isinstance(principal, AvatarUserWrapper) and principal.user is None:
253 self.revokeAccess(principal)
254 return self.allowed
256 def getModificationEmail(self):
257 try:
258 return self.managersEmail
259 except:
260 self.managersEmail = []
261 return self.managersEmail
263 def grantModificationEmail(self, email):
264 """looks if the email is in the managersEmail list (list with the users with access to modification)
265 and if it's not it adds the email to the list
266 Returns True is email was added to the list, False if it was already there.
268 if not email.lower() in map(lambda x: x.lower(), self.getModificationEmail()):
269 self.getModificationEmail().append(email)
270 self._p_changed = 1
271 signals.acl.modification_granted.send(self, principal=email)
272 return True
273 return False
275 def revokeModificationEmail(self, email):
276 if email in self.getModificationEmail():
277 self.getModificationEmail().remove(email)
278 self._p_changed = 1
279 signals.acl.modification_revoked.send(self, principal=email)
281 def grantModification( self, principal ):
282 """grants modification access for the related resource to the specified
283 principal"""
284 # ToDo: should the groups allowed to be managers?
285 if principal not in self.managers and (isinstance(principal, (AvatarUserWrapper, GroupWrapper))):
286 self.managers.append(principal)
287 self._p_changed = 1
288 signals.acl.modification_granted.send(self, principal=principal)
290 def revokeModification( self, principal ):
291 """revokes modification access for the related resource to the
292 specified principal"""
293 if principal in self.managers:
294 self.managers.remove( principal )
295 self._p_changed = 1
296 signals.acl.modification_revoked.send(self, principal=principal)
298 def canModify(self, user):
299 """tells whether the specified user has modification privileges"""
300 if user and user.user.is_admin:
301 return True
303 for principal in self.managers:
304 if isinstance(principal, (AvatarUserWrapper, GroupWrapper)) and principal.containsUser(user):
305 return True
306 ret = False
307 if isinstance(user, AvatarUserWrapper):
308 for email in user.getEmails():
309 if email in self.getModificationEmail():
310 self.grantModification(user)
311 self.revokeModificationEmail(email)
312 ret = True
313 user.linkTo(self.getOwner(), "manager")
314 return ret
316 def getModifierList( self ):
317 """returns a list of those principals which have modification
318 privileges"""
319 for principal in list(self.managers):
320 if principal is None:
321 self.revokeModification(principal)
322 elif isinstance(principal, AvatarUserWrapper) and principal.user is None:
323 self.revokeModification(principal)
324 return self.managers
326 def requireDomain( self, domain ):
327 """adds a domain to the required domain list
329 if domain in self.requiredDomains:
330 return
331 self.requiredDomains.append( domain )
332 self._p_changed = 1
334 def freeDomain( self, domain ):
337 if domain not in self.requiredDomains:
338 return
339 self.requiredDomains.remove( domain )
340 self._p_changed = 1
342 def getRequiredDomainList( self ):
345 return self.requiredDomains
347 def getAnyDomainProtection(self):
349 Checks if the element is protected by domain at any level. It stops checking
350 when it finds an explicitly public or private parent.
352 Returns the list of domains from which the item can be accessed.
355 if self.getAccessProtectionLevel() == 0:
356 owner = self.getOwner().getOwner()
358 # inheriting - get protection from parent
359 if owner:
360 return owner.getAccessController().getAnyDomainProtection()
361 else:
362 # strangely enough, the root category has 2 states
363 # 0 -> inheriting (public) and 1 -> restricted
364 # so, in this case, we really want the category's list
365 return self.getRequiredDomainList()
366 else:
367 return self.getRequiredDomainList()
369 def isDomainProtected(self):
370 if self.getRequiredDomainList():
371 return 1
372 return 0
374 def getAnyContactInfo(self):
375 parent = self.getOwner().getOwner()
376 if not self.getContactInfo() and parent and hasattr(parent, 'getAccessController'):
377 return parent.getAccessController().getAnyContactInfo()
378 else:
379 return self.getContactInfo()
381 def getContactInfo(self):
382 """Defines who to contact in case of access control error.
383 One can use this info to display it along with the exception message"""
384 try:
385 if self.contactInfo:
386 pass
387 except AttributeError:
388 self.contactInfo = ""
389 return self.contactInfo
391 def setContactInfo(self, info):
392 self.contactInfo = info
394 def _grantSubmission(self, av):
395 if av not in self.getSubmitterList():
396 self.submitters.append(av)
397 self._p_changed = 1
399 def grantSubmission(self, sb):
400 """Grants submission privileges for the specified user
402 av = self._getAvatarByEmail(sb.getEmail())
403 if av and av.isActivated():
404 self._grantSubmission(av)
405 elif sb.getEmail():
406 self.getOwner().getConference().getPendingQueuesMgr().addSubmitter(sb, self.getOwner(), False)
408 def _revokeSubmission(self, av):
409 if av in self.getSubmitterList():
410 self.submitters.remove(av)
411 self._p_changed = 1
413 def revokeSubmission(self, sb):
414 """Removes submission privileges for the specified user
416 av = self._getAvatarByEmail(sb.getEmail())
417 self.getOwner().getConference().getPendingQueuesMgr().removeSubmitter(sb, self.getOwner())
418 self._revokeSubmission(av)
420 def _getAvatarByEmail(self, email):
421 from MaKaC.user import AvatarHolder
422 ah = AvatarHolder()
423 avatars = ah.match({"email": email}, exact=1, searchInAuthenticators=False)
424 if not avatars:
425 avatars = ah.match({"email": email}, exact=1)
426 for av in avatars:
427 if av.hasEmail(email):
428 return av
429 return None
431 def getSubmitterList(self, no_groups=False):
432 """Gives the list of users with submission privileges
434 try:
435 return self.submitters
436 except AttributeError:
437 self.submitters = []
438 return self.submitters
440 def canUserSubmit(self, user):
441 """Tells whether a user can submit material
443 return user in self.getSubmitterList()
445 def addNonInheritingChildren(self, obj):
446 self.nonInheritingChildren.add(obj)
447 self._p_changed = 1
449 def removeNonInheritingChildren(self, obj):
450 self.nonInheritingChildren.discard(obj)
451 self._p_changed = 1
453 def getNonInheritingChildren(self):
454 return self.nonInheritingChildren
456 def setNonInheritingChildren(self, nonInheritingChildren):
457 self.nonInheritingChildren = nonInheritingChildren
459 def updateNonInheritingChildren(self, elem, delete=False):
460 if delete or elem.getAccessController().getAccessProtectionLevel() == 0:
461 self.removeNonInheritingChildren(elem)
462 else:
463 self.addNonInheritingChildren(elem)
464 self._p_changed = 1
466 @isFullyAccess(-1)
467 def isFullyPublic(self):
468 pass
470 @isFullyAccess(1)
471 def isFullyPrivate(self):
472 pass
474 @getChildren(-1)
475 def getProtectedChildren(self):
476 pass
478 @getChildren(1)
479 def getPublicChildren(self):
480 pass
483 class CategoryAC(AccessController):
485 def __init__( self ):
486 pass
489 class AccessWrapper:
490 """This class encapsulates the information about an access to the system. It
491 must be initialised by clients if they want to perform access
492 control operations over the objects in the system.
495 def __init__(self, user=None):
496 self._currentUser = user
498 def setUser( self, newAvatar ):
499 self._currentUser = newAvatar
500 ContextManager.set('currentUser', self._currentUser)
502 def getUser( self ):
503 return self._currentUser