Index: main/waeup.sirp/trunk/src/waeup/sirp/applicants/__init__.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/applicants/__init__.py	(revision 7105)
+++ main/waeup.sirp/trunk/src/waeup/sirp/applicants/__init__.py	(revision 7113)
@@ -10,4 +10,6 @@
     ApplicantsRoot, get_applicant_data, application_exists,
     )
+from waeup.sirp.applicants.securitypolicy import (
+    ApplicantSecurityMap, ApplicantPrincipalRoleManager,)
 
 __all__ = [
@@ -17,4 +19,6 @@
     'ApplicantImageNameChooser',
     'ApplicantImageStoreHandler',
+    'ApplicantPrincipalRoleManager',
+    'ApplicantSecurityMap',
     'ApplicantsContainer',
     'ApplicantsRoot',
Index: main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser.py	(revision 7105)
+++ main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser.py	(revision 7113)
@@ -545,5 +545,5 @@
     grok.context(IApplicant)
     grok.name('index')
-    grok.require('waeup.handleApplication')
+    grok.require('waeup.viewApplication')
     form_fields = grok.AutoFields(IApplicant).omit(
         'locked').omit('course_admitted')
@@ -859,5 +859,5 @@
     grok.name('passport.jpg')
     grok.context(IApplicant)
-    grok.require('waeup.handleApplication')
+    grok.require('waeup.viewApplication')
 
     def render(self):
Index: main/waeup.sirp/trunk/src/waeup/sirp/applicants/securitypolicy.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/applicants/securitypolicy.py	(revision 7113)
+++ main/waeup.sirp/trunk/src/waeup/sirp/applicants/securitypolicy.py	(revision 7113)
@@ -0,0 +1,98 @@
+##
+## securitypolicy.py
+## Login : <uli@pu.smp.net>
+## Started on  Mon Nov 14 09:37:10 2011 Uli Fouquet
+## $Id$
+## 
+## Copyright (C) 2011 Uli Fouquet
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+## 
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+## 
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""Security policy components for applicants.
+
+Applicants need special security policy treatment, as officers with
+local roles for departments and faculties might have additional
+permissions (local roles on depts/faculties) here.
+"""
+import grok
+from zope.securitypolicy.interfaces import (
+    IPrincipalRoleManager, IPrincipalPermissionManager,)
+from zope.securitypolicy.principalrole import AnnotationPrincipalRoleManager
+from zope.securitypolicy.principalpermission import (
+    AnnotationPrincipalPermissionManager,)
+from zope.securitypolicy.securitymap import AnnotationSecurityMap
+from zope.securitypolicy.settings import Allow, Deny, Unset
+from waeup.sirp.applicants.interfaces import IApplicant
+
+# All components in here have the same context: Applicant instances
+grok.context(IApplicant)
+
+class ApplicantSecurityMap(AnnotationSecurityMap):
+    pass
+
+class ApplicantPrincipalRoleManager(AnnotationPrincipalRoleManager,
+                                    grok.Adapter):
+    grok.provides(IPrincipalRoleManager)
+
+    def getRolesForPrincipal(self, principal_id):
+        """Get roles for principal with id `principal_id`.
+
+        Different to the default implementation, this method also
+        takes into account local roles set on any department connected
+        to the context applicant.
+
+        If the given principal has 'waeup.local.ClearanceOfficer'
+        permissions set on the connected department, it additionally
+        gets 'waeup.ApplicationsOfficer' role for the context
+        applicant.
+
+        Some advantages of this approach:
+
+        - we don't have to store extra local roles for clearance
+          officers in ZODB for each applicant
+
+        - when local roles on a department change, we don't have to
+          update thousands of applicants; the local role is assigned
+          dynamically.
+
+        Disadvantage:
+
+        - More expensive role lookups when a clearance officer wants
+          to see an applicant form.
+        """
+        result = super(ApplicantPrincipalRoleManager, self
+                     ).getRolesForPrincipal(principal_id)
+        if result != []:
+            # If there are local roles defined here, no additional
+            # lookup is done.
+            return result
+        # The principal has no local roles yet. Let's lookup the
+        # connected dept.
+        course = getattr(self._context, 'course1', None)
+        dept = getattr(
+            getattr(course, '__parent__', None),
+            '__parent__', None)
+        if dept is None:
+            # No deptartment, no extra roles.
+            return result
+        dept_roles = IPrincipalRoleManager(dept).getRolesForPrincipal(
+            *args, **kw)
+        # 'Grant' 'waeup.ApplicationsOfficer' permissions (allow, deny
+        # or unset) for the passed in principal id if it has clearance
+        # officer role on the connected department.
+        for role_id, setting in dept_roles:
+            if role_id == 'waeup.local.ClearanceOfficer':
+                result.append(
+                    ('waeup.ApplicationsOfficer', setting))
+        return result
Index: main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_browser.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_browser.py	(revision 7105)
+++ main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_browser.py	(revision 7113)
@@ -93,6 +93,8 @@
         certificate.code = 'CERT1'
         certificate.application_category = 'basic'
+        self.certificate = certificate
         self.app['faculties']['fac1'] = Faculty()
         self.app['faculties']['fac1']['dep1'] = Department()
+        self.department = self.app['faculties']['fac1']['dep1']
         self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
             certificate)
Index: main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_securitypolicy.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_securitypolicy.py	(revision 7113)
+++ main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_securitypolicy.py	(revision 7113)
@@ -0,0 +1,91 @@
+##
+## test_securitypolicy.py
+## Login : <uli@pu.smp.net>
+## Started on  Mon Nov 14 09:18:42 2011 Uli Fouquet
+## $Id$
+## 
+## Copyright (C) 2011 Uli Fouquet
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+## 
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+## 
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""
+Tests for local roles concerning applicants and related.
+"""
+from zope.interface import verify
+from zope.securitypolicy.interfaces import IPrincipalRoleManager
+from zope.securitypolicy.settings import Allow, Deny, Unset
+from zope.securitypolicy.tests.test_securitymap import TestSecurityMap
+from zope.securitypolicy.tests.test_annotationprincipalrolemanager import (
+    Test as APRMTest, Manageable)
+from waeup.sirp.testing import FunctionalLayer
+from waeup.sirp.applicants.tests.test_browser import ApplicantsFullSetup
+from waeup.sirp.applicants import (
+    ApplicantSecurityMap, ApplicantPrincipalRoleManager,)
+
+class ApplicantPrincipalRoleManagerTests(APRMTest):
+    # Make sure our PRM behaves like a regular one for usual cases.
+    # Usual cases are ones, that do not interact with external officers.
+    # See next test case for functional tests including officer perms.
+
+    def _make_roleManager(self, obj=None):
+        # Overriding this method of original testcase we make sure
+        # that really an Applicant PRM is tested.
+        if obj is None:
+            obj = Manageable()
+        return ApplicantPrincipalRoleManager(obj)
+
+class ApplicantPrincipalRoleManagerFunctionalTests(ApplicantsFullSetup):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(ApplicantPrincipalRoleManagerFunctionalTests, self).setUp()
+        self.applicant.course1 = self.certificate
+        self.officer_role = 'waeup.ApplicationsOfficer'
+        # assign clearance permissions for a virtual officer
+        prm = IPrincipalRoleManager(self.department)
+        prm.assignRoleToPrincipal('waeup.local.ClearanceOfficer', 'alice')
+        return
+
+    def test_iface(self):
+        # make sure out ApplicantPRM really implements required ifaces.
+        obj = ApplicantPrincipalRoleManager(self.applicant)
+        verify.verifyClass(IPrincipalRoleManager,
+                           ApplicantPrincipalRoleManager)
+        verify.verifyObject(IPrincipalRoleManager, obj)
+        return
+
+    def test_get_as_adapter(self):
+        # we can get an ApplicantPRM for Applicants by adapter lookup
+        prm = IPrincipalRoleManager(self.applicant)
+        self.assertTrue(
+            isinstance(prm, ApplicantPrincipalRoleManager))
+        return
+
+    def test_no_officer_set(self):
+        # if the faculty/dept. of the connected cert has no local
+        # roles set, we won't get any additional roles for our
+        # applicant
+        prm = IPrincipalRoleManager(self.applicant)
+        result = prm.getRolesForPrincipal('bob')
+        self.assertEqual(result, [])
+        return
+
+    def test_valid_officer(self):
+        # for an officer that has clearance role on the connected dept
+        # we get the ApplicationsOfficer role on our applicant
+        prm = IPrincipalRoleManager(self.applicant)
+        result = prm.getRolesForPrincipal('alice')
+        self.assertEqual(result, [('waeup.ApplicationsOfficer', Allow)])
+        return
