def delete(self, packagename, key): """ Delete package. ``packagename`` Package name to delete. """ if 'user_id' not in session: log.debug('Requires authentication') session['path_before_login'] = request.path_info session.save() redirect(url('login')) else: user = meta.session.query(User).filter_by(id=session['user_id']).one() package = self._get_package(packagename) if session['user_id'] != package.user_id: log.error("User %d is not allowed to change properties of foreign package %s" %(session['user_id'], packagename)) abort(403) if user.get_upload_key() != key: log.error("Possible CSRF attack, upload key does not match user's session key") abort(402) # The user should have already been prompted with a nice dialog box # confirming their choice, so no mercy here. CheckFiles().delete_files_for_package(package) meta.session.delete(package) meta.session.commit() redirect(url(controller='packages', action='my'))
def index(self, filename): """ Controller entry point. When dput uploads a package via `PUT`, the connection below is made:: PUT /upload/packagename_version.dsc assuming the file being uploaded is the `dsc`. This method takes writes the uploaded file to disk and calls the import script in another process. ``filename`` Name of file being uploaded. The password here is actually a user_upload_key, not the user password. """ if request.method != 'PUT': log.error( 'Request with method %s attempted on Upload controller.' % request.method) abort(405, 'The upload controller only deals with PUT requests.', headers=[('Allow', 'PUT')]) log.debug('File upload: %s' % filename) # Check whether the file extension is supported by debexpo if not CheckFiles().allowed_upload(filename): log.error('File type not supported: %s' % filename) abort(403, 'The uploaded file type is not supported') if 'debexpo.upload.incoming' not in config: log.critical('debexpo.upload.incoming variable not set') abort(500, 'The incoming directory has not been set') if not os.path.isdir(config['debexpo.upload.incoming']): log.critical('debexpo.upload.incoming is not a directory') abort(500, 'The incoming directory has not been set up') if not os.access(config['debexpo.upload.incoming'], os.W_OK): log.critical('debexpo.upload.incoming is not writable') abort(500, 'The incoming directory has not been set up') save_path = os.path.join( os.path.join(config['debexpo.upload.incoming'], "pub"), filename) log.debug('Saving uploaded file to: %s', save_path) if os.path.exists(save_path): log.debug("Aborting. File already exists") abort(403, 'The file was already uploaded') f = open(save_path, 'wb') # The body attribute now contains the entire contents of the uploaded file. # TODO: This looks dangerous if huge files are loaded into memory. f.write(request.body) f.close()
def main(self): """ Actually start the import of the package. Do several environment sanity checks, move files into the right place, and then create the database entries for the imported package. """ # Set up importer self._setup() log.debug('Importer started with arguments: %s' % sys.argv[1:]) filecheck = CheckFiles() gpg = get_gnupg() # Try parsing the changes file, but fail if there's an error. try: self.changes = Changes(filename=self.changes_file) filecheck.test_files_present(self.changes) filecheck.test_md5sum(self.changes) except Exception as e: # XXX: The user won't ever see this message. The changes file was # invalid, we don't know whom send it to self._reject("Your changes file appears invalid. Refusing your upload\n%s" % (e.message)) if not self.skip_gpg: # Next, find out whether the changes file was signed with a valid signature, if not reject immediately v = gpg.verify_file(path=self.changes_file) if v is None: self._reject('Your upload does not appear to be signed') if not v.is_valid: self._reject('Your upload does not contain a valid signature. Output was:\n%s' % (gpg_out)) self._determine_uploader_by_gpg(v.key_id) else: self._determine_uploader_by_changedby_field() if self.user is None: # Creates a fake user object, but only if no user was found before # This is useful to have a user object to send reject mails to # generate user object, but only to send out reject message self.user = User(id=-1, name=maintainer_realname, email=maintainer_email_address) self._reject('Couldn\'t find user %s. Exiting.' % self.user.email) self.user_id = self.user.id log.debug("User found in database. Has id: %s", self.user.id) if self.user.dmup is False: log.debug("User has not accepted the DMUP") self._reject('You must accept the Debian Machine Usage Policies before uploading a package') self.files = self.changes.get_files() self.files_to_remove = [] distribution = self.changes['Distribution'].lower() allowed_distributions = ( 'oldstable', 'stable', 'unstable', 'experimental', 'stable-backports', 'oldstable-backports', 'oldstable-backports-sloppy', 'oldstable-security', 'stable-security', 'testing-security', 'stable-proposed-updates', 'testing-proposed-updates', 'sid', 'wheezy', 'squeeze', 'lenny', 'squeeze-backports', 'lenny-backports', 'lenny-security', 'lenny-backports-sloppy', 'lenny-volatile', 'squeeze-security', 'squeeze-updates', 'wheezy-security', 'unreleased') if distribution not in allowed_distributions: self._reject("You are not uploading to one of those Debian distributions: %s" % (reduce(lambda x, xs: x + " " + xs, allowed_distributions))) # Look whether the orig tarball is present, and if not, try and get it from # the repository. (orig, orig_file_found) = filecheck.find_orig_tarball(self.changes) if orig_file_found != constants.ORIG_TARBALL_LOCATION_LOCAL: log.debug("Upload does not contain orig.tar.gz - trying to find it elsewhere") if orig and orig_file_found == constants.ORIG_TARBALL_LOCATION_REPOSITORY: filename = os.path.join(pylons.config['debexpo.repository'], self.changes.get_pool_path(), orig) if os.path.isfile(filename): log.debug("Found tar.gz in repository as %s" % (filename)) shutil.copy(filename, pylons.config['debexpo.upload.incoming']) # We need the orig.tar.gz for the import run, plugins need to extract the source package # also Lintian needs it. However the orig.tar.gz is in the repository already, so we can # remove it later self.files_to_remove.append(orig) destdir = pylons.config['debexpo.repository'] # Check whether the files are already present log.debug("Checking whether files are already in the repository") toinstall = [] pool_dir = os.path.join(destdir, self.changes.get_pool_path()) log.debug("Pool directory: %s", pool_dir) for file in self.files: if os.path.isfile(file) and os.path.isfile(os.path.join(pool_dir, file)): log.warning('%s is being installed even though it already exists' % file) toinstall.append(file) elif os.path.isfile(file): log.debug('File %s is safe to install' % os.path.join(pool_dir, file)) toinstall.append(file) # skip another corner case, where the dsc contains a orig.tar.gz but wasn't uploaded # by doing nothing here for that case # initiate the git storage gs = GitStorage(os.path.join(destdir, "git", self.changes['Source'])) if os.path.isdir(os.path.join(destdir, 'git', 'last')): log.debug("folder exist, cleaning it") shutil.rmtree(os.path.join(destdir, "git", 'last', ''), True) else: log.debug("last folder doesn't exist, creating one") os.makedirs(os.path.join(destdir, 'git', 'last', '')) #building sources log.debug("building sources!") self._build_source(os.path.join(destdir, 'git', 'last', '')) #removing older file if os.path.isdir(os.path.join(destdir, 'git', self.changes['source'], self.changes['source'])): for i in glob(os.path.join(destdir, 'git', self.changes['source'], self.changes['source'], '*')): if os.path.isdir(i): shutil.rmtree(i) else: os.remove(i) os.rmdir(os.path.join(destdir, 'git', self.changes['source'], self.changes['source'])) shutil.copytree(os.path.join(destdir, 'git', 'last', 'extracted'), os.path.join(destdir, 'git', self.changes['source'], self.changes['source'])) os.path.join(destdir, 'git', self.changes['source'], self.changes['source']) fileToAdd = self._get_files(os.path.join(destdir, 'git', self.changes['source'], self.changes['source'])) fileToAdd = self._clean_path(os.path.join(destdir, 'git', self.changes['source']), fileToAdd) gs.change(fileToAdd) # Run post-upload plugins. post_upload = Plugins('post-upload', self.changes, self.changes_file, user_id=self.user_id) if post_upload.stop(): log.critical('post-upload plugins failed') self._remove_changes() sys.exit(1) # Check whether a post-upload plugin has got the orig tarball from somewhere. if not orig_file_found and not filecheck.is_native_package(self.changes): (orig, orig_file_found) = filecheck.find_orig_tarball(self.changes) if orig_file_found == constants.ORIG_TARBALL_LOCATION_NOT_FOUND: # When coming here it means: # a) The uploader did not include a orig.tar.gz in his upload # b) We couldn't find a orig.tar.gz in our repository # c) No plugin could get the orig.tar.gz # ... time to give up if orig == None: orig = "any original tarball (orig.tar.gz)" self._reject("Rejecting incomplete upload. " "You did not upload %s and we didn't find it on any of our alternative resources.\n"\ "If you tried to upload a package which only increased the Debian revision part, make sure you include the full source (pass -sa to dpkg-buildpackage)" % ( orig )) else: toinstall.append(orig) # Check whether the debexpo.repository variable is set if 'debexpo.repository' not in pylons.config: self._fail('debexpo.repository not set') # Check whether debexpo.repository is a directory if not os.path.isdir(pylons.config['debexpo.repository']): self._fail('debexpo.repository is not a directory') # Check whether debexpo.repository is writeable if not os.access(pylons.config['debexpo.repository'], os.W_OK): self._fail('debexpo.repository is not writeable') qa = Plugins('qa', self.changes, self.changes_file, user_id=self.user_id) if qa.stop(): self._reject('QA plugins failed the package') # Loop through parent directories in the target installation directory to make sure they # all exist. If not, create them. for dir in self.changes.get_pool_path().split('/'): destdir = os.path.join(destdir, dir) if not os.path.isdir(destdir): log.debug('Creating directory: %s' % destdir) os.mkdir(destdir) # Install files in repository for file in toinstall: log.debug("Installing new file %s" % (file)) shutil.move(file, os.path.join(destdir, file)) #git job is done, cleaning shutil.rmtree(os.path.join(pylons.config['debexpo.repository'], 'git', 'last', '')) self._remove_temporary_files() # Create the database rows self._create_db_entries(qa) # Execute I'm happy to have post-successful-upload plugins f = open(self.changes_file) changes_contents = f.read() f.close() Plugins('post-successful-upload', self.changes, self.changes_file, changes_contents=changes_contents) # Remove the changes file self._remove_changes() # Refresh the Sources/Packages files. log.debug('Updating Sources and Packages files') r = Repository(pylons.config['debexpo.repository']) r.update() log.debug('Done') return 0
def test_orig_tarball(self): """ Check whether there is an original tarball referenced by the dsc file, but not actually in the package upload. This procedure is skipped to avoid denial of service attacks when a package is larger than the configured size """ # Set download of files, when the expected file size of the orig.tar.gz is larger # than the configured threshold. # XXX TODO: This size should be the 75% quartile, that is the value where 75% of all # packages are smaller than that. For now, blindly assume this as 15M size = 15728640 log.debug( 'Checking whether an orig tarball mentioned in the dsc is missing') dsc = deb822.Dsc(file(self.changes.get_dsc())) filecheck = CheckFiles() if filecheck.is_native_package(self.changes): log.debug('No orig.tar.gz file found; native package?') return # An orig.tar.gz was found in the dsc, and also in the upload. (orig, orig_file_found) = filecheck.find_orig_tarball(self.changes) if orig_file_found > constants.ORIG_TARBALL_LOCATION_NOT_FOUND: log.debug('%s found successfully', orig) return if not orig: log.debug("Couldn't determine name of the orig.tar.gz?") return for dscfile in dsc['Files']: dscfile['size'] = int(dscfile['size']) if orig == dscfile['name']: if dscfile['size'] > size: log.warning( "Skipping eventual download of orig.tar.gz %s: size %d > %d" % (dscfile['name'], dscfile['size'], size)) return orig = dscfile break else: log.debug( "dsc does not reference our expected orig.tar.gz name '%s'" % (orig)) return log.debug('Could not find %s; looking in Debian for it', orig['name']) url = os.path.join(pylons.config['debexpo.debian_mirror'], self.changes.get_pool_path(), orig['name']) log.debug('Trying to fetch %s' % url) out = urllib.urlopen(url) contents = out.read() f = open(orig['name'], "wb") f.write(contents) f.close() if md5sum(orig['name']) == orig['md5sum']: log.debug('Tarball %s taken from Debian' % orig['name']) self.info('tarball-taken-from-debian', None) else: log.error('Tarball %s not found in Debian' % orig['name']) os.unlink(orig['name'])
def test_orig_tarball(self): """ Check whether there is an original tarball referenced by the dsc file, but not actually in the package upload. This procedure is skipped to avoid denial of service attacks when a package is larger than the configured size """ # Set download of files, when the expected file size of the orig.tar.gz is larger # than the configured threshold. # XXX TODO: This size should be the 75% quartile, that is the value where 75% of all # packages are smaller than that. For now, blindly assume this as 15M size = 15728640 log.debug('Checking whether an orig tarball mentioned in the dsc is missing') dsc = deb822.Dsc(file(self.changes.get_dsc())) filecheck = CheckFiles() if filecheck.is_native_package(self.changes): log.debug('No orig.tar.gz file found; native package?') return # An orig.tar.gz was found in the dsc, and also in the upload. (orig, orig_file_found) = filecheck.find_orig_tarball(self.changes) if orig_file_found > constants.ORIG_TARBALL_LOCATION_NOT_FOUND: log.debug('%s found successfully', orig) return if not orig: log.debug("Couldn't determine name of the orig.tar.gz?") return for dscfile in dsc['Files']: dscfile['size'] = int(dscfile['size']) if orig == dscfile['name']: if dscfile['size'] > size: log.warning("Skipping eventual download of orig.tar.gz %s: size %d > %d" % (dscfile['name'], dscfile['size'], size)) return orig = dscfile break else: log.debug("dsc does not reference our expected orig.tar.gz name '%s'" % (orig)) return log.debug('Could not find %s; looking in Debian for it', orig['name']) url = os.path.join(pylons.config['debexpo.debian_mirror'], self.changes.get_pool_path(), orig['name']) log.debug('Trying to fetch %s' % url) out = urllib.urlopen(url) contents = out.read() f = open(orig['name'], "wb") f.write(contents) f.close() if md5sum(orig['name']) == orig['md5sum']: log.debug('Tarball %s taken from Debian' % orig['name']) self.info('tarball-taken-from-debian', None) else: log.error('Tarball %s not found in Debian' % orig['name']) os.unlink(orig['name'])
def setUp(self): """ Setup the environment for tests """ self.checkfiles = CheckFiles()
def main(self): """ Actually start the import of the package. Do several environment sanity checks, move files into the right place, and then create the database entries for the imported package. """ # Set up importer self._setup() log.debug('Importer started with arguments: %s' % sys.argv[1:]) filecheck = CheckFiles() gpg = get_gnupg() # Try parsing the changes file, but fail if there's an error. try: self.changes = Changes(filename=self.changes_file) filecheck.test_files_present(self.changes) filecheck.test_md5sum(self.changes) except Exception as e: # XXX: The user won't ever see this message. The changes file was # invalid, we don't know whom send it to self._reject( "Your changes file appears invalid. Refusing your upload\n%s" % (e.message)) if not self.skip_gpg: # Next, find out whether the changes file was signed with a valid signature, if not reject immediately v = gpg.verify_file(path=self.changes_file) if v is None: self._reject('Your upload does not appear to be signed') if not v.is_valid: self._reject( 'Your upload does not contain a valid signature. Output was:\n%s' % (gpg_out)) self._determine_uploader_by_gpg(v.key_id) else: self._determine_uploader_by_changedby_field() if self.user is None: # Creates a fake user object, but only if no user was found before # This is useful to have a user object to send reject mails to # generate user object, but only to send out reject message self.user = User(id=-1, name=maintainer_realname, email=maintainer_email_address) self._reject('Couldn\'t find user %s. Exiting.' % self.user.email) self.user_id = self.user.id log.debug("User found in database. Has id: %s", self.user.id) if self.user.dmup is False: log.debug("User has not accepted the DMUP") self._reject( 'You must accept the Debian Machine Usage Policies before uploading a package' ) self.files = self.changes.get_files() self.files_to_remove = [] distribution = self.changes['Distribution'].lower() allowed_distributions = ('oldstable', 'stable', 'unstable', 'experimental', 'stable-backports', 'oldstable-backports', 'oldstable-backports-sloppy', 'oldstable-security', 'stable-security', 'testing-security', 'stable-proposed-updates', 'testing-proposed-updates', 'sid', 'wheezy', 'squeeze', 'lenny', 'squeeze-backports', 'lenny-backports', 'lenny-security', 'lenny-backports-sloppy', 'lenny-volatile', 'squeeze-security', 'squeeze-updates', 'wheezy-security', 'unreleased') if distribution not in allowed_distributions: self._reject( "You are not uploading to one of those Debian distributions: %s" % (reduce(lambda x, xs: x + " " + xs, allowed_distributions))) # Look whether the orig tarball is present, and if not, try and get it from # the repository. (orig, orig_file_found) = filecheck.find_orig_tarball(self.changes) if orig_file_found != constants.ORIG_TARBALL_LOCATION_LOCAL: log.debug( "Upload does not contain orig.tar.gz - trying to find it elsewhere" ) if orig and orig_file_found == constants.ORIG_TARBALL_LOCATION_REPOSITORY: filename = os.path.join(pylons.config['debexpo.repository'], self.changes.get_pool_path(), orig) if os.path.isfile(filename): log.debug("Found tar.gz in repository as %s" % (filename)) shutil.copy(filename, pylons.config['debexpo.upload.incoming']) # We need the orig.tar.gz for the import run, plugins need to extract the source package # also Lintian needs it. However the orig.tar.gz is in the repository already, so we can # remove it later self.files_to_remove.append(orig) destdir = pylons.config['debexpo.repository'] # Check whether the files are already present log.debug("Checking whether files are already in the repository") toinstall = [] pool_dir = os.path.join(destdir, self.changes.get_pool_path()) log.debug("Pool directory: %s", pool_dir) for file in self.files: if os.path.isfile(file) and os.path.isfile( os.path.join(pool_dir, file)): log.warning( '%s is being installed even though it already exists' % file) toinstall.append(file) elif os.path.isfile(file): log.debug('File %s is safe to install' % os.path.join(pool_dir, file)) toinstall.append(file) # skip another corner case, where the dsc contains a orig.tar.gz but wasn't uploaded # by doing nothing here for that case # initiate the git storage gs = GitStorage(os.path.join(destdir, "git", self.changes['Source'])) if os.path.isdir(os.path.join(destdir, 'git', 'last')): log.debug("folder exist, cleaning it") shutil.rmtree(os.path.join(destdir, "git", 'last', ''), True) else: log.debug("last folder doesn't exist, creating one") os.makedirs(os.path.join(destdir, 'git', 'last', '')) #building sources log.debug("building sources!") self._build_source(os.path.join(destdir, 'git', 'last', '')) #removing older file if os.path.isdir( os.path.join(destdir, 'git', self.changes['source'], self.changes['source'])): for i in glob( os.path.join(destdir, 'git', self.changes['source'], self.changes['source'], '*')): if os.path.isdir(i): shutil.rmtree(i) else: os.remove(i) os.rmdir( os.path.join(destdir, 'git', self.changes['source'], self.changes['source'])) shutil.copytree( os.path.join(destdir, 'git', 'last', 'extracted'), os.path.join(destdir, 'git', self.changes['source'], self.changes['source'])) os.path.join(destdir, 'git', self.changes['source'], self.changes['source']) fileToAdd = self._get_files( os.path.join(destdir, 'git', self.changes['source'], self.changes['source'])) fileToAdd = self._clean_path( os.path.join(destdir, 'git', self.changes['source']), fileToAdd) gs.change(fileToAdd) # Run post-upload plugins. post_upload = Plugins('post-upload', self.changes, self.changes_file, user_id=self.user_id) if post_upload.stop(): log.critical('post-upload plugins failed') self._remove_changes() sys.exit(1) # Check whether a post-upload plugin has got the orig tarball from somewhere. if not orig_file_found and not filecheck.is_native_package( self.changes): (orig, orig_file_found) = filecheck.find_orig_tarball(self.changes) if orig_file_found == constants.ORIG_TARBALL_LOCATION_NOT_FOUND: # When coming here it means: # a) The uploader did not include a orig.tar.gz in his upload # b) We couldn't find a orig.tar.gz in our repository # c) No plugin could get the orig.tar.gz # ... time to give up if orig == None: orig = "any original tarball (orig.tar.gz)" self._reject("Rejecting incomplete upload. " "You did not upload %s and we didn't find it on any of our alternative resources.\n"\ "If you tried to upload a package which only increased the Debian revision part, make sure you include the full source (pass -sa to dpkg-buildpackage)" % ( orig )) else: toinstall.append(orig) # Check whether the debexpo.repository variable is set if 'debexpo.repository' not in pylons.config: self._fail('debexpo.repository not set') # Check whether debexpo.repository is a directory if not os.path.isdir(pylons.config['debexpo.repository']): self._fail('debexpo.repository is not a directory') # Check whether debexpo.repository is writeable if not os.access(pylons.config['debexpo.repository'], os.W_OK): self._fail('debexpo.repository is not writeable') qa = Plugins('qa', self.changes, self.changes_file, user_id=self.user_id) if qa.stop(): self._reject('QA plugins failed the package') # Loop through parent directories in the target installation directory to make sure they # all exist. If not, create them. for dir in self.changes.get_pool_path().split('/'): destdir = os.path.join(destdir, dir) if not os.path.isdir(destdir): log.debug('Creating directory: %s' % destdir) os.mkdir(destdir) # Install files in repository for file in toinstall: log.debug("Installing new file %s" % (file)) shutil.move(file, os.path.join(destdir, file)) #git job is done, cleaning shutil.rmtree( os.path.join(pylons.config['debexpo.repository'], 'git', 'last', '')) self._remove_temporary_files() # Create the database rows self._create_db_entries(qa) # Execute I'm happy to have post-successful-upload plugins f = open(self.changes_file) changes_contents = f.read() f.close() Plugins('post-successful-upload', self.changes, self.changes_file, changes_contents=changes_contents) # Remove the changes file self._remove_changes() # Refresh the Sources/Packages files. log.debug('Updating Sources and Packages files') r = Repository(pylons.config['debexpo.repository']) r.update() log.debug('Done') return 0