[Zope3-checkins] SVN: Zope3/branches/srichter-twisted-integration2/src/zope/app/twisted/ftp/ fixed a number of the error codes returned by the FTP server and also fixed / added more tests

Michael Kerrin michael.kerrin at openapp.biz
Fri Oct 7 18:10:56 EDT 2005


Log message for revision 38910:
  fixed a number of the error codes returned by the FTP server and also fixed / added more tests

Changed:
  U   Zope3/branches/srichter-twisted-integration2/src/zope/app/twisted/ftp/ftp.py
  U   Zope3/branches/srichter-twisted-integration2/src/zope/app/twisted/ftp/test/test_zope_ftp.py
  U   Zope3/branches/srichter-twisted-integration2/src/zope/app/twisted/ftp/tests/demofs.py
  U   Zope3/branches/srichter-twisted-integration2/src/zope/app/twisted/ftp/tests/test_demofs.py
  U   Zope3/branches/srichter-twisted-integration2/src/zope/app/twisted/ftp/tests/test_publisher.py

-=-
Modified: Zope3/branches/srichter-twisted-integration2/src/zope/app/twisted/ftp/ftp.py
===================================================================
--- Zope3/branches/srichter-twisted-integration2/src/zope/app/twisted/ftp/ftp.py	2005-10-07 21:13:08 UTC (rev 38909)
+++ Zope3/branches/srichter-twisted-integration2/src/zope/app/twisted/ftp/ftp.py	2005-10-07 22:10:56 UTC (rev 38910)
@@ -20,6 +20,9 @@
 
 from zope.interface import implements
 
+from zope.publisher.interfaces import NotFound
+from zope.security.interfaces import Unauthorized
+
 from twisted.internet import threads, defer
 from twisted.protocols import ftp
 
@@ -58,27 +61,52 @@
         return '/' + '/'.join(path)
 
     def makeDirectory(self, path):
-        return threads.deferToThread(self.fs_access.mkdir, self._path(path))
+        def failed(failure):
+            raise ftp.PermissionDeniedError(self._path(path))
+        d = threads.deferToThread(self.fs_access.mkdir, self._path(path))
+        d.addErrback(failed)
 
+        return d
+
     def removeDirectory(self, path):
-        return threads.deferToThread(self.fs_access.rmdir, self._path(path))
+        def failed(failure):
+            raise ftp.PermissionDeniedError(self._path(path))
+        d = threads.deferToThread(self.fs_access.rmdir, self._path(path))
+        d.addErrback(failed)
 
+        return d
+
     def removeFile(self, path):
-        return threads.deferToThread(self.fs_access.remove, self._path(path))
+        def failed(failure):
+            raise ftp.PermissionDeniedError(self._path(path))
+        d = threads.deferToThread(self.fs_access.remove, self._path(path))
+        d.addErrback(failed)
 
+        return d
+
     def rename(self, fromPath, toPath):
-        return threads.deferToThread(self.fs_access.rename,
-                                     self._path(fromPath),
-                                     self._path(toPath))
+        def failed(failure):
+            raise ftp.PermissionDeniedError(self._path(path))
+        d = threads.deferToThread(self.fs_access.rename,
+                                  self._path(fromPath),
+                                  self._path(toPath))
+        d.addErrback(failed)
 
+        return d
+
     def access(self, path):
         def success(result):
             return None
-        def failure(result):
-            ## XXX - return more appropriate results
-            raise ftp.PermissionDeniedError(path)
 
-        ## XXX - is ls the right method to use here - seems a bit slow.
+        def failure(failure):
+            if failure.type is NotFound:
+                raise ftp.FileNotFoundError(self._path(path))
+            ## Unauthorized error - is there any other errors I should
+            ## be catching.
+            raise ftp.PermissionDeniedError(self._path(path))
+
+        ## the ls method used where might be a bit slow on a directory
+        ## with lots of entries.
         d = threads.deferToThread(self.fs_access.ls, self._path(path))
         d.addCallback(success)
         d.addErrback(failure)
@@ -112,8 +140,14 @@
                 ret.append(self._gotlisting(result, keys))
             return ret
 
+        def goterror(failure):
+            if failure.type is NotFound:
+                raise ftp.FileNotFoundError(self._path(path))
+            raise ftp.PermissionDeniedError(self._path(path))
+
         d = threads.deferToThread(self.fs_access.ls, self._path(path))
         d.addCallback(gotresults, keys)
+        d.addErrback(goterror)
 
         return d
 
@@ -167,28 +201,34 @@
     def send(self, path, consumer):
         def finished(result, consumer):
             consumer.transport.loseConnection()
-        def failed(result, path):
+
+        def failed(failure, consumer):
             consumer.transport.loseConnection()
-            ## XXX - a more appropriate exception here.
-            raise ftp.PermissionDeniedError(path)
+            if failure.type is NotFound:
+                raise ftp.FileNotFoundError(self._path(path))
+            ## Unauthorized error - is there any other errors I should
+            ## be catching.
+            raise ftp.PermissionDeniedError(self._path(path))
 
         p = self._path(path)
         d = threads.deferToThread(self.fs_access.readfile, p, consumer)
         d.addCallback(finished, consumer)
-        d.addErrback(failed, consumer, p)
+        d.addErrback(failed, consumer)
         return d
 
     def receive(self, path):
-        def accessok(result, fs, path):
+        def accessok(result, fs):
             if not result:
-                raise ftp.PermissionDeniedError(path)
+                raise ftp.PermissionDeniedError(self._path(path))
             return ConsumerObject(fs, p)
-        def failure(result, path):
+
+        def failure(result):
             ## XXX - should be a better exception
             raise ftp.PermissionDeniedError(path)
+
         p = self._path(path)
         d = threads.deferToThread(self.fs_access.writable, p)
-        d.addCallback(accessok, self.fs_access, p)
-        d.addErrback(failure, p)
+        d.addCallback(accessok, self.fs_access)
+        d.addErrback(failure)
 
         return d

Modified: Zope3/branches/srichter-twisted-integration2/src/zope/app/twisted/ftp/test/test_zope_ftp.py
===================================================================
--- Zope3/branches/srichter-twisted-integration2/src/zope/app/twisted/ftp/test/test_zope_ftp.py	2005-10-07 21:13:08 UTC (rev 38909)
+++ Zope3/branches/srichter-twisted-integration2/src/zope/app/twisted/ftp/test/test_zope_ftp.py	2005-10-07 22:10:56 UTC (rev 38910)
@@ -73,7 +73,7 @@
         root = demofs.Directory()
         # the tuple has a user name is used by ZopeSimpleAuthentication to
         # authenticate users.
-        root.grant(('root', 'root'), demofs.write)
+        root.grant('root', demofs.write)
         self.rootfs = rootfs = DemoFileSystem(root, ('root', 'root'))
 
         # Start the server
@@ -110,8 +110,26 @@
         )
 
 class BasicFTPServerTestCase(FTPServerTestCase, test_ftp.BasicFTPServerTestCase):
-    pass
+    def _authLogin(self):
+        responseLines = wait(self.client.queueStringCommand('USER root'))
+        self.assertEquals(
+            ['331 Password required for root.'],
+            responseLines
+        )
 
+        responseLines = wait(self.client.queueStringCommand(
+            'PASS root')
+        )
+        self.assertEquals(
+            ['230 User logged in, proceed'],
+            responseLines
+        )
+
+    def test_MKD(self):
+        self._authLogin()
+        responseLines = wait(self.client.queueStringCommand('MKD /newdir'))
+        self.assertEqual(['257 "/newdir" created'], responseLines)
+
 class FTPServerPasvDataConnectionTestCase(FTPServerTestCase,
                                           test_ftp.FTPServerPasvDataConnectionTestCase):
 
@@ -130,8 +148,6 @@
         # Make some directories
         self.rootfs.mkdir_nocheck('/foo')
         self.rootfs.mkdir_nocheck('/bar')
-##         os.mkdir(os.path.join(self.directory, 'foo'))
-##         os.mkdir(os.path.join(self.directory, 'bar'))
 
         # Download a listing again
         downloader = self._makeDataConnection()
@@ -174,9 +190,6 @@
         # Download a range of different size files
         for size in range(100000, 110000, 500):
             self.rootfs.writefile_nocheck('/%d.txt' % (size,), StringIO('x' * size))
-##             fObj = file(os.path.join(self.directory, '%d.txt' % (size,)), 'wb')
-##             fObj.write('x' * size)
-##             fObj.close()
 
             downloader = self._makeDataConnection()
             d = self.client.queueStringCommand('RETR %d.txt' % (size,))
@@ -193,3 +206,67 @@
         l = [defer.maybeDeferred(port.stopListening) for port in self.dataPorts]
         wait(defer.DeferredList(l, fireOnOneErrback=True))
         return FTPServerPasvDataConnectionTestCase.tearDown(self)
+
+from twisted.test.test_ftp import _BufferingProtocol
+
+class ZopeFTPPermissionTestCases(FTPServerTestCase):
+    def setUp(self):
+        FTPServerTestCase.setUp(self)
+        self.filename = 'nopermissionfolder'
+        self.rootfs.writefile('/%s' % self.filename, StringIO('x' * 100))
+        file = self.rootfs.get(self.filename)
+        file.grant('michael', 0)
+        del file.access['anonymous']
+
+    def _makeDataConnection(self):
+        # Establish a passive data connection (i.e. client connecting to
+        # server).
+        responseLines = wait(self.client.queueStringCommand('PASV'))
+        host, port = ftp.decodeHostPort(responseLines[-1][4:])
+        downloader = wait(
+            protocol.ClientCreator(reactor,
+                                   _BufferingProtocol).connectTCP('127.0.0.1',
+                                                                  port)
+        )
+        return downloader
+
+    def _michaelLogin(self):
+        responseLines = wait(self.client.queueStringCommand('USER michael'))
+        self.assertEquals(
+            ['331 Password required for michael.'],
+            responseLines
+        )
+
+        responseLines = wait(self.client.queueStringCommand(
+            'PASS michael')
+        )
+        self.assertEquals(
+            ['230 User logged in, proceed'],
+            responseLines
+        )
+
+    def testNoSuchDirectory(self):
+        self._michaelLogin()
+        deferred = self.client.queueStringCommand('CWD /nosuchdir')
+        failureResponseLines = self._waitForCommandFailure(deferred)
+        self.failUnless(failureResponseLines[-1].startswith('550'),
+                        "Response didn't start with 550: %r" % failureResponseLines[-1])
+
+    def testListNonPermission(self):
+        self._michaelLogin()
+
+        # Download a listing
+        downloader = self._makeDataConnection()
+        d = self.client.queueStringCommand('NLST ')
+        wait(defer.gatherResults([d, downloader.d]))
+
+        # No files, so the file listing should be empty
+        filenames = downloader.buffer[:-2].split('\r\n')
+        filenames.sort()
+        self.assertEqual([self.filename], filenames)
+
+        downloader = self._makeDataConnection()
+        d = self.client.queueStringCommand('RETR %s' % self.filename)
+        failureResponseLines = self._waitForCommandFailure(d)
+        self.failUnless(failureResponseLines[-1].startswith('426'),
+                        "Response didn't start with 426 i.e. data connection closed error")

Modified: Zope3/branches/srichter-twisted-integration2/src/zope/app/twisted/ftp/tests/demofs.py
===================================================================
--- Zope3/branches/srichter-twisted-integration2/src/zope/app/twisted/ftp/tests/demofs.py	2005-10-07 21:13:08 UTC (rev 38909)
+++ Zope3/branches/srichter-twisted-integration2/src/zope/app/twisted/ftp/tests/demofs.py	2005-10-07 22:10:56 UTC (rev 38910)
@@ -15,8 +15,9 @@
 """
 import posixpath
 from zope.security.interfaces import Unauthorized
+from zope.publisher.interfaces import NotFound
+
 from zope.app.twisted.interfaces import IFileSystem
-## from zope.server.interfaces.ftp import IFileSystemAccess
 from zope.interface import implements
 
 execute = 1
@@ -31,6 +32,8 @@
         self.access = {'anonymous': read}
 
     def accessable(self, user, access=read):
+        if isinstance(user, tuple) and len(user) == 2:
+            user = user[0]
         return (user == 'root'
                 or (self.access.get(user, 0) & access)
                 or (self.access.get('anonymous', 0) & access)
@@ -76,7 +79,8 @@
     File = File
     Directory = Directory
 
-    def __init__(self, files, user=''):
+    def __init__(self, files, user = ''):
+        ## user is not username, password
         self.files = files
         self.user = user
 
@@ -101,7 +105,7 @@
     def getany(self, path):
         d = self.get(path)
         if d is None:
-            raise OSError("No such file or directory:", path)
+            raise NotFound(self, path)
         return d
 
     def getdir(self, path):
@@ -165,6 +169,9 @@
         "See zope.server.interfaces.ftp.IFileSystem"
         f = self.getfile(path)
 
+        if not f.accessable(self.user, read):
+            raise Unauthorized(path)
+
         data = f.data
         if end is not None:
             data = data[:end]
@@ -195,7 +202,7 @@
         if name in d.files:
             raise OSError("Already exists:", name)
         newdir = self.Directory()
-        newdir.grant(self.user, read | write)
+        newdir.grant(self.user[0], read | write)
         d.files[name] = newdir
 
     def remove(self, path):
@@ -244,7 +251,7 @@
         if f is None:
             d = self.getwdir(path)
             f = d.files[name] = self.File()
-            f.grant(self.user, read | write)
+            f.grant(self.user[0], read | write)
         elif f.type != 'f':
             raise OSError("Can't overwrite a directory")
 

Modified: Zope3/branches/srichter-twisted-integration2/src/zope/app/twisted/ftp/tests/test_demofs.py
===================================================================
--- Zope3/branches/srichter-twisted-integration2/src/zope/app/twisted/ftp/tests/test_demofs.py	2005-10-07 21:13:08 UTC (rev 38909)
+++ Zope3/branches/srichter-twisted-integration2/src/zope/app/twisted/ftp/tests/test_demofs.py	2005-10-07 22:10:56 UTC (rev 38910)
@@ -25,7 +25,7 @@
     def setUp(self):
         root = demofs.Directory()
         root.grant('bob', demofs.write)
-        fs = self.filesystem = demofs.DemoFileSystem(root, 'bob')
+        fs = self.filesystem = demofs.DemoFileSystem(root, ('bob', '123'))
         fs.mkdir(self.dir_name)
         fs.writefile(self.file_name, StringIO(self.file_contents))
         fs.writefile(self.unwritable_filename, StringIO("save this"))

Modified: Zope3/branches/srichter-twisted-integration2/src/zope/app/twisted/ftp/tests/test_publisher.py
===================================================================
--- Zope3/branches/srichter-twisted-integration2/src/zope/app/twisted/ftp/tests/test_publisher.py	2005-10-07 21:13:08 UTC (rev 38909)
+++ Zope3/branches/srichter-twisted-integration2/src/zope/app/twisted/ftp/tests/test_publisher.py	2005-10-07 22:10:56 UTC (rev 38910)
@@ -106,13 +106,13 @@
     def setUp(self):
         root = demofs.Directory()
         root.grant('bob', demofs.write)
-        fs = DemoFileSystem(root, 'bob')
+        fs = DemoFileSystem(root, ('bob', '123'))
         fs.mkdir(self.dir_name)
         fs.writefile(self.file_name, StringIO(self.file_contents))
         fs.writefile(self.unwritable_filename, StringIO("save this"))
         fs.get(self.unwritable_filename).revoke('bob', demofs.write)
 
-        self.filesystem = PublisherFileSystem('bob', RequestFactory(fs))
+        self.filesystem = PublisherFileSystem(('bob', '123'), RequestFactory(fs))
 
 def test_suite():
     return TestSuite((



More information about the Zope3-Checkins mailing list