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/>.
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
):
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):
36 for child
in args
[0].getNonInheritingChildren():
37 if child
.getAccessController().getAccessProtectionLevel() != level
:
43 def getChildren(level
):
47 return [child
for child
in args
[0].getNonInheritingChildren()
48 if child
.getAccessController().getAccessProtectionLevel() != level
]
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
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.
65 __accessProtection -- (int) Flag which indicates whether the resource
66 the current access controller is related to is protected (on) or
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
75 def __init__( self
, owner
):
76 self
._accessProtection
= 0
77 self
._fatherProtection
= 0
78 self
._hideFromUnauthorizedUsers
= 0
80 self
.managersEmail
= []
82 self
.allowedEmail
= []
83 self
.requiredDomains
= []
88 self
.nonInheritingChildren
= set()
93 def setOwner(self
, 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
):
103 return self
._accessProtection
105 self
._accessProtection
= 0
108 def getAccessProtectionLevel( self
):
109 return self
._getAccessProtection
()
111 def _getFatherProtection( self
):
113 from MaKaC
.conference
import Conference
114 if isinstance(self
.getOwner(), Conference
):
115 ownerList
= self
.getOwner().getOwnerList()
117 ownerList
= [self
.getOwner().getOwner()]
119 if o
is not None and o
.isProtected():
123 self
._fatherProtection
= 0
126 def isHidden( self
):
128 return self
._hideFromUnauthorizedUsers
129 except AttributeError:
130 self
._hideFromUnauthorizedUsers
= 0
133 def setHidden(self
, hidden
):
134 self
._hideFromUnauthorizedUsers
= hidden
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
148 def isFatherProtected(self
):
149 return self
._getFatherProtection
()
151 def setFatherProtection( self
, protected
):
152 self
._fatherProtection
= protected
155 def grantAccess(self
, principal
):
156 """grants read access for the related resource to the specified
159 if principal
not in self
.allowed
and isinstance(principal
, (AvatarUserWrapper
, GroupWrapper
)):
160 self
.allowed
.append(principal
)
162 signals
.acl
.access_granted
.send(self
, principal
=principal
)
164 def getAccessEmail(self
):
166 return self
.allowedEmail
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
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
)
193 signals
.acl
.access_revoked
.send(self
, principal
=principal
)
195 def setAccessKey( self
, key
="" ):
198 def getAccessKey( self
):
200 return self
.accessKey
205 def canKeyAccess( self
, key
):
208 if self
.getAccessKey()!="":
209 if key
== self
.getAccessKey():
214 def isHarvesterIP( cls
, ip
):
215 minfo
= info
.HelperMaKaCInfo
.getMaKaCInfoInstance()
216 ipList
= minfo
.getIPBasedACLMgr().get_full_access_acl()
220 def canIPAccess(self
, ip
):
223 if not self
.getRequiredDomainList():
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
):
233 for principal
in self
.allowed
:
234 if principal
is None:
235 self
.revokeAccess(principal
)
237 if isinstance(principal
, (AvatarUserWrapper
, GroupWrapper
)) and principal
.containsUser(av
):
239 if isinstance(av
, AvatarUserWrapper
):
240 for email
in av
.getEmails():
241 if email
in self
.getAccessEmail():
243 self
.revokeAccessEmail(email
)
244 av
.linkTo(self
.getOwner(), "manager")
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
)
256 def getModificationEmail(self
):
258 return self
.managersEmail
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
)
271 signals
.acl
.modification_granted
.send(self
, principal
=email
)
275 def revokeModificationEmail(self
, email
):
276 if email
in self
.getModificationEmail():
277 self
.getModificationEmail().remove(email
)
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
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
)
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
)
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
:
303 for principal
in self
.managers
:
304 if isinstance(principal
, (AvatarUserWrapper
, GroupWrapper
)) and principal
.containsUser(user
):
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
)
313 user
.linkTo(self
.getOwner(), "manager")
316 def getModifierList( self
):
317 """returns a list of those principals which have modification
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
)
326 def requireDomain( self
, domain
):
327 """adds a domain to the required domain list
329 if domain
in self
.requiredDomains
:
331 self
.requiredDomains
.append( domain
)
334 def freeDomain( self
, domain
):
337 if domain
not in self
.requiredDomains
:
339 self
.requiredDomains
.remove( domain
)
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
360 return owner
.getAccessController().getAnyDomainProtection()
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()
367 return self
.getRequiredDomainList()
369 def isDomainProtected(self
):
370 if self
.getRequiredDomainList():
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()
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"""
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
)
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
)
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
)
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
423 avatars
= ah
.match({"email": email
}, exact
=1, searchInAuthenticators
=False)
425 avatars
= ah
.match({"email": email
}, exact
=1)
427 if av
.hasEmail(email
):
431 def getSubmitterList(self
, no_groups
=False):
432 """Gives the list of users with submission privileges
435 return self
.submitters
436 except AttributeError:
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
)
449 def removeNonInheritingChildren(self
, obj
):
450 self
.nonInheritingChildren
.discard(obj
)
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
)
463 self
.addNonInheritingChildren(elem
)
467 def isFullyPublic(self
):
471 def isFullyPrivate(self
):
475 def getProtectedChildren(self
):
479 def getPublicChildren(self
):
483 class CategoryAC(AccessController
):
485 def __init__( self
):
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
)
503 return self
._currentUser