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 revokeAccess( self
, principal
):
172 """revokes read access for the related resource to the specified
174 #ToDo: Revoking access to a user is quite more complex than this. We can
175 # just remove the user, but if there's a group in the list that
176 # contains the user, the access to the user is not revoked, he still
177 # has access. But dunno what to do in that case: raise an exception??
178 # Certainly removing the user from the group or the group itself
179 # wouldn't be acceptable solutions.
180 if principal
in self
.allowed
:
181 self
.allowed
.remove( principal
)
183 signals
.acl
.access_revoked
.send(self
, principal
=principal
)
185 def setAccessKey( self
, key
="" ):
188 def getAccessKey( self
):
190 return self
.accessKey
195 def canKeyAccess( self
, key
):
198 if self
.getAccessKey()!="":
199 if key
== self
.getAccessKey():
204 def isHarvesterIP( cls
, ip
):
205 minfo
= info
.HelperMaKaCInfo
.getMaKaCInfoInstance()
206 ipList
= minfo
.getIPBasedACLMgr().get_full_access_acl()
210 def canIPAccess(self
, ip
):
213 if not self
.getRequiredDomainList():
215 return any(domain
.belongsTo(ip
) for domain
in self
.getRequiredDomainList())
217 def isAdmin(self
, av
):
218 return av
and av
.user
and av
.user
.is_admin
220 def canUserAccess(self
, av
):
223 for principal
in self
.allowed
:
224 if principal
is None:
225 self
.revokeAccess(principal
)
227 if isinstance(principal
, (AvatarUserWrapper
, GroupWrapper
)) and principal
.containsUser(av
):
229 if isinstance(av
, AvatarUserWrapper
):
230 for email
in av
.getEmails():
231 if email
in self
.getAccessEmail():
233 av
.linkTo(self
.getOwner(), "manager")
236 def getAccessList(self
):
237 """returns a list of those principals which have access privileges"""
238 for principal
in list(self
.allowed
):
239 if principal
is None:
240 self
.revokeAccess(principal
)
241 elif isinstance(principal
, AvatarUserWrapper
) and principal
.user
is None:
242 self
.revokeAccess(principal
)
245 def getModificationEmail(self
):
247 return self
.managersEmail
249 self
.managersEmail
= []
250 return self
.managersEmail
252 def grantModificationEmail(self
, email
):
253 """looks if the email is in the managersEmail list (list with the users with access to modification)
254 and if it's not it adds the email to the list
255 Returns True is email was added to the list, False if it was already there.
257 if not email
.lower() in map(lambda x
: x
.lower(), self
.getModificationEmail()):
258 self
.getModificationEmail().append(email
)
260 signals
.acl
.modification_granted
.send(self
, principal
=email
)
264 def revokeModificationEmail(self
, email
):
265 if email
in self
.getModificationEmail():
266 self
.getModificationEmail().remove(email
)
268 signals
.acl
.modification_revoked
.send(self
, principal
=email
)
270 def grantModification( self
, principal
):
271 """grants modification access for the related resource to the specified
273 # ToDo: should the groups allowed to be managers?
274 if principal
not in self
.managers
and (isinstance(principal
, (AvatarUserWrapper
, GroupWrapper
))):
275 self
.managers
.append(principal
)
277 signals
.acl
.modification_granted
.send(self
, principal
=principal
)
279 def revokeModification( self
, principal
):
280 """revokes modification access for the related resource to the
281 specified principal"""
282 if principal
in self
.managers
:
283 self
.managers
.remove( principal
)
285 signals
.acl
.modification_revoked
.send(self
, principal
=principal
)
287 def canModify(self
, user
):
288 """tells whether the specified user has modification privileges"""
289 if user
and user
.user
.is_admin
:
292 for principal
in self
.managers
:
293 if isinstance(principal
, (AvatarUserWrapper
, GroupWrapper
)) and principal
.containsUser(user
):
296 if isinstance(user
, AvatarUserWrapper
):
297 for email
in user
.getEmails():
298 if email
in self
.getModificationEmail():
299 self
.grantModification(user
)
300 self
.revokeModificationEmail(email
)
302 user
.linkTo(self
.getOwner(), "manager")
305 def getModifierList( self
):
306 """returns a list of those principals which have modification
308 for principal
in list(self
.managers
):
309 if principal
is None:
310 self
.revokeModification(principal
)
311 elif isinstance(principal
, AvatarUserWrapper
) and principal
.user
is None:
312 self
.revokeModification(principal
)
315 def requireDomain( self
, domain
):
316 """adds a domain to the required domain list
318 if domain
in self
.requiredDomains
:
320 self
.requiredDomains
.append( domain
)
323 def freeDomain( self
, domain
):
326 if domain
not in self
.requiredDomains
:
328 self
.requiredDomains
.remove( domain
)
331 def getRequiredDomainList( self
):
334 return self
.requiredDomains
336 def getAnyDomainProtection(self
):
338 Checks if the element is protected by domain at any level. It stops checking
339 when it finds an explicitly public or private parent.
341 Returns the list of domains from which the item can be accessed.
344 if self
.getAccessProtectionLevel() == 0:
345 owner
= self
.getOwner().getOwner()
347 # inheriting - get protection from parent
349 return owner
.getAccessController().getAnyDomainProtection()
351 # strangely enough, the root category has 2 states
352 # 0 -> inheriting (public) and 1 -> restricted
353 # so, in this case, we really want the category's list
354 return self
.getRequiredDomainList()
356 return self
.getRequiredDomainList()
358 def isDomainProtected(self
):
359 if self
.getRequiredDomainList():
363 def getAnyContactInfo(self
):
364 parent
= self
.getOwner().getOwner()
365 if not self
.getContactInfo() and parent
and hasattr(parent
, 'getAccessController'):
366 return parent
.getAccessController().getAnyContactInfo()
368 return self
.getContactInfo()
370 def getContactInfo(self
):
371 """Defines who to contact in case of access control error.
372 One can use this info to display it along with the exception message"""
376 except AttributeError:
377 self
.contactInfo
= ""
378 return self
.contactInfo
380 def setContactInfo(self
, info
):
381 self
.contactInfo
= info
383 def _grantSubmission(self
, av
):
384 if av
not in self
.getSubmitterList():
385 self
.submitters
.append(av
)
388 def grantSubmission(self
, sb
):
389 """Grants submission privileges for the specified user
391 av
= self
._getAvatarByEmail
(sb
.getEmail())
392 if av
and av
.isActivated():
393 self
._grantSubmission
(av
)
395 self
.getOwner().getConference().getPendingQueuesMgr().addSubmitter(sb
, self
.getOwner(), False)
397 def _revokeSubmission(self
, av
):
398 if av
in self
.getSubmitterList():
399 self
.submitters
.remove(av
)
402 def revokeSubmission(self
, sb
):
403 """Removes submission privileges for the specified user
405 av
= self
._getAvatarByEmail
(sb
.getEmail())
406 self
.getOwner().getConference().getPendingQueuesMgr().removeSubmitter(sb
, self
.getOwner())
407 self
._revokeSubmission
(av
)
409 def _getAvatarByEmail(self
, email
):
410 from MaKaC
.user
import AvatarHolder
412 avatars
= ah
.match({"email": email
}, exact
=1, searchInAuthenticators
=False)
414 avatars
= ah
.match({"email": email
}, exact
=1)
416 if av
.hasEmail(email
):
420 def getSubmitterList(self
, no_groups
=False):
421 """Gives the list of users with submission privileges
424 return self
.submitters
425 except AttributeError:
427 return self
.submitters
429 def canUserSubmit(self
, user
):
430 """Tells whether a user can submit material
432 return user
in self
.getSubmitterList()
434 def addNonInheritingChildren(self
, obj
):
435 self
.nonInheritingChildren
.add(obj
)
438 def removeNonInheritingChildren(self
, obj
):
439 self
.nonInheritingChildren
.discard(obj
)
442 def getNonInheritingChildren(self
):
443 return self
.nonInheritingChildren
445 def setNonInheritingChildren(self
, nonInheritingChildren
):
446 self
.nonInheritingChildren
= nonInheritingChildren
448 def updateNonInheritingChildren(self
, elem
, delete
=False):
449 if delete
or elem
.getAccessController().getAccessProtectionLevel() == 0:
450 self
.removeNonInheritingChildren(elem
)
452 self
.addNonInheritingChildren(elem
)
456 def isFullyPublic(self
):
460 def isFullyPrivate(self
):
464 def getProtectedChildren(self
):
468 def getPublicChildren(self
):
472 class CategoryAC(AccessController
):
474 def __init__( self
):
479 """This class encapsulates the information about an access to the system. It
480 must be initialised by clients if they want to perform access
481 control operations over the objects in the system.
484 def __init__(self
, user
=None):
485 self
._currentUser
= user
487 def setUser( self
, newAvatar
):
488 self
._currentUser
= newAvatar
489 ContextManager
.set('currentUser', self
._currentUser
)
492 return self
._currentUser