Index: main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_webservices.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_webservices.py	(revision 11671)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_webservices.py	(revision 11674)
@@ -384,4 +384,58 @@
         return
 
+    def test_put_student_fingerprints_no_stud(self):
+        # invalid student ids will result in `False`
+        server = ServerProxy('http://mgr:mgrpw@localhost/app')
+        self.assertRaises(
+            xmlrpclib.Fault, server.put_student_fingerprints,
+            'invalid id', {})
+
+    def test_put_student_fingerprints_non_dict(self):
+        # fingerprints must be passed in in a dict
+        server = ServerProxy('http://mgr:mgrpw@localhost/app')
+        self.setup_student(self.student)
+        self.assertRaises(
+            xmlrpclib.Fault, server.put_student_fingerprints,
+            self.student.student_id, 'not-a-dict')
+
+    def test_put_student_fingerprints_non_num_keys_ignored(self):
+        # non-numeric keys in fingerprint dict are ignored
+        server = ServerProxy('http://mgr:mgrpw@localhost/app')
+        self.setup_student(self.student)
+        result = server.put_student_fingerprints(
+            self.student.student_id, {'not-a-num': 'foo',
+                                      '12.2': 'bar',
+                                      '123': 'baz'})
+        self.assertEqual(result, False)
+
+    def test_put_student_fingerprints_non_fpm_data(self):
+        # we cannot pass non-.fpm files as values
+        server = ServerProxy('http://mgr:mgrpw@localhost/app')
+        self.setup_student(self.student)
+        self.assertRaises(
+            xmlrpclib.Fault, server.put_student_fingerprints,
+            self.student.student_id, {'1': 'not-a-fingerprint'})
+
+    def test_put_student_fingerprints_invalid_file_format(self):
+        # invalid files will result in `False`
+        server = ServerProxy('http://mgr:mgrpw@localhost/app')
+        self.setup_student(self.student)
+        invalid_fpm = xmlrpclib.Binary('invalid file')
+        self.assertRaises(
+            xmlrpclib.Fault, server.put_student_fingerprints,
+            self.student.student_id, {'1': invalid_fpm})
+
+    def test_put_student_fingerprints(self):
+        # we can store fingerprints
+        server = ServerProxy('http://mgr:mgrpw@localhost/app')
+        self.setup_student(self.student)
+        fpm = xmlrpclib.Binary('FP1faked_fpm')
+        result = server.put_student_fingerprints(
+            self.student.student_id, {'1': fpm})
+        self.assertEqual(result, True)
+        stored_file = getUtility(IExtFileStore).getFileByContext(
+            self.student, attr="1.fpm")
+        self.assertEqual(stored_file.read(), 'FP1faked_fpm')
+
     def test_get_student_fingerprints_no_stud(self):
         # invalid student ids result in empty dict
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/webservices.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/webservices.py	(revision 11671)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/webservices.py	(revision 11674)
@@ -19,7 +19,11 @@
 import os
 import xmlrpclib
+from cStringIO import StringIO
 from zope.component import getUtility, queryUtility
 from zope.catalog.interfaces import ICatalog
-from waeup.kofa.interfaces import IUniversity, IExtFileStore
+from waeup.kofa.interfaces import (
+    IUniversity, IExtFileStore, IFileStoreNameChooser,
+    )
+from waeup.kofa.utils.helpers import get_fileformat
 
 
@@ -230,4 +234,67 @@
                     )
 
+    @grok.require('waeup.putBiometricData')
+    def put_student_fingerprints(self, identifier=None, fingerprints={}):
+        """Store fingerprint files for student identified by `identifier`.
+
+        `fingerprints` is expected to be a dict with strings
+        ``1``..``10`` as keys and binary data as values.
+
+        The keys ``1``..``10`` represent respective fingers: ``1`` is
+        the left thumb, ``10`` the little finger of right hand.
+
+        The binary data values are expected to be fingerprint minutiae
+        files as created by the libfprint library. With the std python
+        `xmlrpclib` client you can create such values with
+        `xmlrpclib.Binary(<BINARY_DATA_HERE>)`.
+
+        The following problems will raise errors:
+
+        - Invalid student identifiers (student does not exist or
+          unknown format of identifier)
+
+        - Fingerprint files that are not put into a dict.
+
+        - Fingerprint dicts that contain non-FPM files (or otherwise
+          invalid .fpm data).
+
+        Returns `True` in case of successful operation (at least one
+        fingerprint was stored), `False` otherwise.
+        """
+        result = False
+        students = self.context['students']
+        student = get_student(students, identifier)
+        if student is None:
+            raise xmlrpclib.Fault(
+                xmlrpclib.INVALID_METHOD_PARAMS,
+                "No such student: '%s'" % identifier)
+        if not isinstance(fingerprints, dict):
+            raise xmlrpclib.Fault(
+                xmlrpclib.INVALID_METHOD_PARAMS,
+                "Invalid fingerprint data: must be in dict")
+        for str_key, val in fingerprints.items():
+            num = 0
+            try:
+                num = int(str_key)
+            except ValueError:
+                pass
+            if num < 1 or num > 10:
+                continue
+            if not isinstance(val, xmlrpclib.Binary):
+                raise xmlrpclib.Fault(
+                    xmlrpclib.INVALID_METHOD_PARAMS,
+                    "Invalid data for finger %s" % num)
+            fmt = get_fileformat(None, val.data)
+            if fmt != 'fpm':
+                raise xmlrpclib.Fault(
+                    xmlrpclib.INVALID_METHOD_PARAMS,
+                    "Invalid file format for finger %s" % num)
+            file_store = getUtility(IExtFileStore)
+            file_id = IFileStoreNameChooser(student).chooseName(
+                attr='%s.fpm' % num)
+            file_store.createFile(file_id, StringIO(val.data))
+            result = True
+        return result
+
     @grok.require('waeup.getBiometricData')
     def get_student_fingerprints(self, identifier=None):
