Index: main/waeup.kofa/trunk/src/waeup/kofa/tests/test_webservices.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/tests/test_webservices.py	(revision 9632)
+++ main/waeup.kofa/trunk/src/waeup/kofa/tests/test_webservices.py	(revision 9632)
@@ -0,0 +1,62 @@
+# Tests for webservices
+import xmlrpclib
+from zope.app.testing.xmlrpc import ServerProxy
+from zope.component import createObject
+from zope.component.hooks import setSite
+from zope.testbrowser.testing import Browser
+from waeup.kofa.app import University
+from waeup.kofa.testing import FunctionalLayer, FunctionalTestCase
+
+class XMLRPCTests(FunctionalTestCase):
+    # check XMLRPC services for university portal
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(XMLRPCTests, self).setUp()
+        # create a site
+        self.root = self.getRootFolder()
+        self.app = University()
+        self.root['app'] = self.app
+        return
+
+    def create_student(self, reg_no=u'REG123'):
+        # create a student in site
+        setSite(self.app) # needed to create a student
+        stud = createObject('waeup.Student')
+        stud.reg_number = reg_no
+        self.app['students'].addStudent(stud)
+        setSite(None) # make sure no site is set when returning
+        return stud
+
+    def test_get_student_id_no_match(self):
+        # w/o any students we get none
+        server = ServerProxy('http://mgr:mgrpw@localhost/app')
+        result = server.get_student_id('R123')
+        self.assertTrue(result is None)
+        return
+
+    def test_get_student_id_regno_exists(self):
+        # we can get the id of an existing student with matching reg_no
+        stud1 = self.create_student(reg_no=u'R123')
+        server = ServerProxy('http://mgr:mgrpw@localhost/app')
+        result = server.get_student_id('R123')
+        self.assertEqual(result, 'K1000000')
+        self.assertEqual(stud1.student_id, result)
+        return
+
+    def test_get_student_id_block_unauthorized(self):
+        # requests from unauthorized users are blocked
+        # no username nor password
+        server = ServerProxy('http://localhost/app')
+        self.assertRaises(
+            xmlrpclib.ProtocolError, server.get_student_id, 'R123')
+        # wrong password
+        server = ServerProxy('http://mgr:WRONGPW@localhost/app')
+        self.assertRaises(
+            xmlrpclib.ProtocolError, server.get_student_id, 'R123')
+        # wrong username
+        server = ServerProxy('http://WRONGUSER:mgrpw@localhost/app')
+        self.assertRaises(
+            xmlrpclib.ProtocolError, server.get_student_id, 'R123')
+        return
Index: main/waeup.kofa/trunk/src/waeup/kofa/webservices.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/webservices.py	(revision 9631)
+++ main/waeup.kofa/trunk/src/waeup/kofa/webservices.py	(revision 9632)
@@ -80,2 +80,37 @@
     def render(self):
         return self.output
+
+class XMLRPCPermission(grok.Permission):
+    """Permission for using XMLRPC functions.
+    """
+    grok.name('waeup.xmlrpc')
+
+class UniversityXMLRPC(grok.XMLRPC):
+    """XMLRPC webservices for KOFA portals.
+
+    Please note, that XMLRPC does not support real keyword arguments
+    but positional arguments only.
+    """
+    grok.context(IUniversity)
+
+    @grok.require('waeup.Public')
+    def xmlrpc_api_version(self):
+        """Return the current API version for XMLRPC clients.
+        """
+        return u'0.1'
+
+    @grok.require('waeup.xmlrpc')
+    def get_student_id(self, reg_number=None):
+        """Get the id of a student with registration number `reg_number`.
+
+        Returns the student id as string if successful, ``None`` else.
+        """
+        if reg_number is not None:
+            cat = getUtility(ICatalog, name='students_catalog')
+            result = list(
+                cat.searchResults(reg_number=(reg_number, reg_number),
+                                  _limit=1))
+            if not len(result):
+                return None
+            return result[0].student_id
+        return None
