20
20
from twisted.python import filepath
22
22
from zope.interface import implements
23
from zope.component import getUtility
25
from canonical.launchpad.interfaces.gpghandler import (
24
30
from lp.poppy.filesystem import UploadFileSystem
25
31
from lp.poppy.hooks import Hooks
32
from lp.registry.interfaces.gpg import IGPGKeySet
28
35
class PoppyAccessCheck:
69
76
filename = os.sep.join(file_segments)
70
77
self._create_missing_directories(filename)
71
return super(PoppyAnonymousShell, self).openForWriting(file_segments)
78
path = self._path(file_segments)
81
except (IOError, OSError), e:
82
return ftp.errnoToFailure(e.errno, path)
85
return defer.succeed(PoppyFileWriter(fObj))
73
87
def makeDirectory(self, path):
74
88
"""Make a directory using the secure `UploadFileSystem`."""
122
136
avatar, 'logout', lambda: None)
123
137
raise NotImplementedError(
124
138
"Only IFTPShell interface is supported by this realm")
141
class PoppyFileWriter(ftp._FileWriter):
142
"""An `IWriteFile` that checks for signed changes files."""
145
"""Called after the file has been completely downloaded."""
146
if self.fObj.name.endswith(".changes"):
147
error = self.validateGPG(self.fObj.name)
148
if error is not None:
149
# PermissionDeniedError is one of the few ftp exceptions
150
# that lets us pass an error string back to the client.
151
return defer.fail(ftp.PermissionDeniedError(error))
152
return defer.succeed(None)
154
def validateGPG(self, signed_file):
155
"""Check the GPG signature in the file referenced by signed_file.
157
Return an error string if there's a problem, or None.
160
sig = getUtility(IGPGHandler).getVerifiedSignatureResilient(
161
file(signed_file, "rb").read())
162
except GPGVerificationError, error:
163
return ("Changes file must be signed with a valid GPG "
164
"signature: %s" % error)
166
key = getUtility(IGPGKeySet).getByFingerprint(sig.fingerprint)
169
"Signing key %s not registered in launchpad."
172
if key.active == False:
173
return "Changes file is signed with a deactivated key"