Index: main/waeup.sirp/trunk/src/waeup/sirp/authentication.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/authentication.py	(revision 6179)
+++ main/waeup.sirp/trunk/src/waeup/sirp/authentication.py	(revision 6180)
@@ -59,4 +59,6 @@
     grok.implements(IUserAccount)
 
+    _local_roles = dict()
+
     def __init__(self, name, password, title=None, description=None,
                  roles = []):
@@ -70,4 +72,6 @@
         self.setPassword(password)
         self.setRoles(roles)
+        # We don't want to share this dict with other accounts
+        self._local_roles = dict()
 
     def setPassword(self, password):
@@ -98,4 +102,18 @@
 
     roles = property(getRoles, setRoles)
+
+    def getLocalRoles(self):
+        return self._local_roles
+
+    def notifyLocalRoleChanged(self, obj, role_id, granted=True):
+        objects = self._local_roles.get(role_id, [])
+        if granted and obj not in objects:
+            objects.append(obj)
+        if not granted and obj in objects:
+            objects.remove(obj)
+        self._local_roles[role_id] = objects
+        if len(objects) == 0:
+            del self._local_roles[role_id]
+        return
 
     def _getPrincipalRoleManager(self):
Index: main/waeup.sirp/trunk/src/waeup/sirp/authentication.txt
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/authentication.txt	(revision 6179)
+++ main/waeup.sirp/trunk/src/waeup/sirp/authentication.txt	(revision 6180)
@@ -12,8 +12,10 @@
 Before we can check access we have to create an app:
 
+  >>> from zope.component.hooks import setSite # only needed in tests
   >>> from waeup.sirp.app import University
   >>> root = getRootFolder()
   >>> u = University()
   >>> root['app'] = u
+  >>> setSite(root['app'])                     # only needed in tests
 
 To make sure, we can 'watch' pages, we first have to initialize our
@@ -42,4 +44,70 @@
 See ``users.txt`` for details about the UserContainer we use here.
 
+Users and local roles
+=====================
+
+Accounts also hold infos about local roles assigned to a user. In the
+beginning, users have no local roles at all:
+
+  >>> alice.getLocalRoles()
+  {}
+
+But we can tell an account, that Alice got some role for a certain
+object:
+
+  >>> chalet = object()
+  >>> root['app']['chalet'] = chalet
+  >>> alice.notifyLocalRoleChanged(chalet, 'BigBoss', granted=True)
+
+Now Alice is the Big Boss:
+
+  >>> alice.getLocalRoles()
+  {'BigBoss': [<object object at 0x...>]}
+
+When we do not want Alice to be the Big Boss we can tell that too:
+
+  >>> alice.notifyLocalRoleChanged(chalet, 'BigBoss', granted=False)
+  >>> alice.getLocalRoles()
+  {}
+
+We can also use events to trigger such actions. This is recommended
+because we do not neccessarily know where Alice lives:
+
+  >>> from waeup.sirp.users import LocalRoleSetEvent
+  >>> from zope.event import notify
+  >>> notify(LocalRoleSetEvent(chalet, 'BigBoss', 'alice',
+  ...                          granted=True))
+  >>> alice.getLocalRoles()
+  {'BigBoss': [<object object at 0x...>]}
+
+When objects are deleted, local roles are also deleted
+semi-magically. This happens through event subscribers listening to
+IObjectRemovedEvents. The latters are naturally only fired when ZODB
+stored objects are removed. Furthermore this subscriber reads the
+internal local roles table.
+
+We create a faculty and grant Bob a local role:
+
+   >>> from zope.securitypolicy.interfaces import IPrincipalRoleManager
+   >>> from waeup.sirp.university.faculty import Faculty
+   >>> faculty = Faculty()
+   >>> root['app']['bobs_fac'] = faculty
+   >>> role_manager = IPrincipalRoleManager(faculty)
+   >>> role_manager.assignRoleToPrincipal(
+   ...    'waeup.PortalManager', 'bob')
+
+We notify the machinery about that fact:
+
+   >>> notify(LocalRoleSetEvent(faculty, 'waeup.PortalManager', 'bob',
+   ...                          granted=True))
+   >>> bob = root['app']['users']['bob']
+   >>> bob.getLocalRoles()
+   {'waeup.PortalManager': [<waeup.sirp...Faculty object at 0x...>]}
+
+When we delete the faculty from ZODB, also Bobs roles are modified:
+
+   >>> del root['app']['bobs_fac']
+   >>> bob.getLocalRoles()
+   {}
 
 Logging in via side bar
Index: main/waeup.sirp/trunk/src/waeup/sirp/interfaces.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/interfaces.py	(revision 6179)
+++ main/waeup.sirp/trunk/src/waeup/sirp/interfaces.py	(revision 6180)
@@ -266,4 +266,14 @@
     """
 
+class ILocalRoleSetEvent(IObjectEvent):
+    """A local role was granted/revoked for a principal on an object.
+    """
+    role_id = Attribute(
+        "The role id that was set.")
+    principal_id = Attribute(
+        "The principal id for which the role was granted/revoked.")
+    granted = Attribute(
+        "Boolean. If false, then the role was revoked.")
+
 class IQueryResultItem(Interface):
     """An item in a search result.
Index: main/waeup.sirp/trunk/src/waeup/sirp/users.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/users.py	(revision 6179)
+++ main/waeup.sirp/trunk/src/waeup/sirp/users.py	(revision 6180)
@@ -2,6 +2,9 @@
 """
 import grok
+from zope.event import notify
+from zope.interface import Interface
+from zope.securitypolicy.interfaces import IPrincipalRoleMap
 from waeup.sirp.authentication import Account
-from waeup.sirp.interfaces import IUserContainer
+from waeup.sirp.interfaces import IUserContainer, ILocalRoleSetEvent
 
 class UserContainer(grok.Container):
@@ -26,5 +29,5 @@
         """
         self[account.name] = account
-        
+
     def delUser(self, name):
         """Delete user, if an account with the given name exists.
@@ -34,2 +37,33 @@
         if name in self.keys():
             del self[name]
+
+class LocalRoleSetEvent(object):
+
+    grok.implements(ILocalRoleSetEvent)
+
+    def __init__(self, object, role_id, principal_id, granted=True):
+        self.object = object
+        self.role_id = role_id
+        self.principal_id = principal_id
+        self.granted = granted
+
+@grok.subscribe(Interface, ILocalRoleSetEvent)
+def handle_local_role_changed(obj, event):
+    site = grok.getSite()
+    if site is None:
+        return
+    users = site['users']
+    role_id = event.role_id
+    if event.principal_id not in users.keys():
+        return
+    user = users[event.principal_id]
+    user.notifyLocalRoleChanged(event.object, event.role_id, event.granted)
+    return
+
+@grok.subscribe(Interface, grok.IObjectRemovedEvent)
+def handle_local_roles_on_obj_removal(obj, event):
+    role_map = IPrincipalRoleMap(obj)
+    for local_role, user_name, setting in role_map.getPrincipalsAndRoles():
+        notify(LocalRoleSetEvent(
+                obj, local_role, user_name, granted=False))
+    return
