示例#1
0
  def post(self):
    """POST

    Parameters:
      filename: filename of package e.g. 'Firefox-1.0.dmg'
    """
    session = gaeserver.DoMunkiAuth(require_level=gaeserver.LEVEL_UPLOADPKG)

    filename = self.request.get('filename')
    pkginfo = models.PackageInfo.get_by_key_name(filename)
    if not pkginfo:
      self.response.set_status(404)
      self.response.out.write('Pkginfo does not exist: %s' % filename)
      return

    plist = pkginfo.plist
    catalogs = pkginfo.catalogs
    install_types = pkginfo.install_types

    #logging.info('Deleting package: %s', filename)
    blobstore_key = pkginfo.blobstore_key
    # Delete the PackageInfo entity, and then the package Blobstore entity.
    pkginfo.delete()
    gae_util.SafeBlobDel(blobstore_key)
    # Recreate catalogs so references to this package don't exist anywhere.
    for catalog in catalogs:
      models.Catalog.Generate(catalog)

    # Log admin delete to Datastore.
    user = session.uuid
    admin_log = models.AdminPackageLog(
        user=user, action='deletepkg', filename=filename, catalogs=catalogs,
        install_types=install_types, plist=plist)
    admin_log.put()
示例#2
0
class UploadPackage(admin.AdminHandler,
                    blobstore_handlers.BlobstoreUploadHandler):
    """Handler for /admin/uploadpkg."""

    XSRF_PROTECTION = False

    def get(self):
        """GET Handler.

    With no parameters, return the URL to use to submit uploads via
    multipart/form-data.

    With mode and key parameters, return status of the previous uploadpkg
    operation to the calling client.

    This method actually acts as a helper to the starting and finishing
    of the uploadpkg post() method.

    Parameters:
      mode: optionally, 'success' or 'error'
      key: optionally, blobstore key that was uploaded
    """
        if not auth.HasPermission(auth.UPLOAD):
            self.error(httplib.FORBIDDEN)
            return

        mode = self.request.get('mode')
        msg = self.request.get('msg', None)
        if mode == 'success':
            filename = self.request.get('filename')
            msg = '%s successfully uploaded and is ready for deployment.' % filename
            self.redirect('/admin/package/%s?msg=%s' % (filename, msg))
        elif mode == 'error':
            self.response.set_status(httplib.BAD_REQUEST)
            self.response.out.write(msg)
        else:
            filename = self.request.get('filename')
            if not filename:
                self.response.set_status(httplib.NOT_FOUND)
                self.response.out.write('Filename required')
                return

            p = models.PackageInfo.get_by_key_name(filename)
            if not p:
                self.response.set_status(httplib.BAD_REQUEST)
                self.response.out.write(
                    'You must first upload a pkginfo for %s' % filename)
                return
            elif p.blob_info:
                self.response.set_status(httplib.BAD_REQUEST)
                self.response.out.write('This file already exists.')
                return

            upload_url = blobstore.create_upload_url(
                '/admin/uploadpkg', gs_bucket_name=util.GetBlobstoreGSBucket())

            values = {
                'upload_url': upload_url,
                'filename': filename,
                'file_size_kbytes': p.plist['installer_item_size'],
            }
            self.Render('upload_pkg_form.html', values)

    def _RedirectWithErrorMsg(self, msg):
        logging.warning(msg)
        self.redirect('/admin/uploadpkg?mode=error&msg=%s' % msg)

    def post(self):
        """POST Handler.

    This method behaves a little strangely.  BlobstoreUploadHandler
    only allows returns statuses of 301, 302, 303 (not even 200), so
    one must redirect away to return more information to the caller.

    Parameters:
      file: package file contents
      pkginfo: packageinfo file contents
      name: filename of package e.g. 'Firefox-1.0.dmg'
    """
        # Only blobstore/upload service/scotty requests should be
        # invoking this handler.
        if not handlers.IsBlobstore():
            logging.error('POST to uploadpkg not from Blobstore: %s',
                          self.request.headers)
            self.redirect('/admin/packages')
            return

        if not self.get_uploads('file'):
            logging.error('Upload package does not exist.')
            return

        blob_info = self.get_uploads('file')[0]
        blobstore_key = str(blob_info.key())

        # Obtain a lock on the PackageInfo entity for this package.
        lock = models.GetLockForPackage(blob_info.filename)
        try:
            lock.Acquire(timeout=30, max_acquire_attempts=5)
        except datastore_locks.AcquireLockError:
            gae_util.SafeBlobDel(blobstore_key)

            self._RedirectWithErrorMsg('PackageInfo is locked')
            return

        p = models.PackageInfo.get_by_key_name(blob_info.filename)
        if not p:
            lock.Release()
            gae_util.SafeBlobDel(blobstore_key)

            self._RedirectWithErrorMsg('PackageInfo not found')
            return

        if not p.IsSafeToModify():
            lock.Release()
            gae_util.SafeBlobDel(blobstore_key)

            self._RedirectWithErrorMsg('PackageInfo is not modifiable')
            return

        installer_item_size = p.plist['installer_item_size']
        size_difference = int(blob_info.size / 1024) - installer_item_size
        if abs(size_difference) > 1:
            lock.Release()
            gae_util.SafeBlobDel(blobstore_key)
            msg = 'Blob size (%s) does not match PackageInfo plist size (%s)' % (
                blob_info.size, installer_item_size)

            self._RedirectWithErrorMsg(msg)
            return

        old_blobstore_key = None
        if p.blobstore_key:
            # a previous blob exists.  delete it when the update has succeeded.
            old_blobstore_key = p.blobstore_key

        p.blob_info = blob_info

        # update the PackageInfo model with the new plist string and blobstore key.
        try:
            p.put()
            error = None
        except db.Error, e:
            logging.exception('error on PackageInfo.put()')
            error = 'pkginfo.put() failed with: %s' % str(e)

        # if it failed, delete the blob that was just uploaded -- it's
        # an orphan.
        if error is not None:
            gae_util.SafeBlobDel(blobstore_key)
            lock.Release()

            self._RedirectWithErrorMsg(error)
            return

        # if an old blob was associated with this Package, delete it.
        # the new blob that was just uploaded has replaced it.
        if old_blobstore_key:
            gae_util.SafeBlobDel(old_blobstore_key)

        lock.Release()

        user = users.get_current_user().email()
        # Log admin upload to Datastore.
        admin_log = models.AdminPackageLog(user=user,
                                           action='uploadpkg',
                                           filename=blob_info.filename)
        admin_log.put()

        self.redirect('/admin/uploadpkg?mode=success&filename=%s' %
                      blob_info.filename)
示例#3
0
    def post(self):
        """POST method.

    This method behaves a little strangely.  BlobstoreUploadHandler
    only allows returns statuses of 301, 302, 303 (not even 200), so
    one must redirect away to return more information to the caller.

    Parameters:
      file: package file contents
      pkginfo: packageinfo file contents
      name: filename of package e.g. 'Firefox-1.0.dmg'
    """
        # Only blobstore/upload service/scotty requests should be
        # invoking this handler.
        if not handlers.IsBlobstore():
            logging.critical('POST to /uploadpkg not from Blobstore: %s',
                             self.request.headers)
            self.redirect('/')

        gaeserver.DoMunkiAuth(require_level=gaeserver.LEVEL_UPLOADPKG)

        user = self.request.get('user')
        filename = self.request.get('name')
        install_types = self.request.get('install_types')
        catalogs = self.request.get('catalogs', None)
        manifests = self.request.get('manifests', None)
        if catalogs is None or not install_types or not user or not filename:
            msg = 'uploadpkg POST required parameters missing'
            logging.error(msg)
            self.redirect('/uploadpkg?mode=error&msg=%s' % msg)
            return
        if catalogs == '':
            catalogs = []
        else:
            catalogs = catalogs.split(',')
        if manifests in ['', None]:
            manifests = []
        else:
            manifests = manifests.split(',')
        install_types = install_types.split(',')

        upload_files = self.get_uploads('file')
        upload_pkginfo_files = self.get_uploads('pkginfo')
        if not len(upload_pkginfo_files) and not self.request.get('pkginfo'):
            self.redirect('/uploadpkg?mode=error&msg=No%20file%20received')
            return

        if len(upload_pkginfo_files):
            # obtain the pkginfo from a blob, and then throw it away.  this is
            # a necessary hack because the upload handler grabbed it, but we don't
            # intend to keep it in blobstore.
            pkginfo_str = gae_util.GetBlobAndDel(upload_pkginfo_files[0].key())
        else:
            # otherwise, grab the form parameter.
            pkginfo_str = self.request.get('pkginfo')

        blob_info = upload_files[0]
        blobstore_key = str(blob_info.key())

        # Parse, validate, and encode the pkginfo plist.
        plist = plist_lib.MunkiPackageInfoPlist(pkginfo_str)
        try:
            plist.Parse()
        except plist_lib.PlistError:
            logging.exception('Invalid pkginfo plist uploaded:\n%s\n',
                              pkginfo_str)
            gae_util.SafeBlobDel(blobstore_key)
            self.redirect(
                '/uploadpkg?mode=error&msg=No%20valid%20pkginfo%20received')
            return

        filename = plist['installer_item_location']
        pkgdata_sha256 = plist['installer_item_hash']

        # verify the blob was actually written; in case Blobstore failed to write
        # the blob but still POSTed to this handler (very, very rare).
        blob_info = blobstore.BlobInfo.get(blobstore_key)
        if not blob_info:
            logging.critical(
                'Blobstore returned a key for %s that does not exist: %s',
                filename, blobstore_key)
            self.redirect('/uploadpkg?mode=error&msg=Blobstore%20failure')
            return

        # Obtain a lock on the PackageInfo entity for this package.
        lock = 'pkgsinfo_%s' % filename
        if not gae_util.ObtainLock(lock, timeout=5.0):
            gae_util.SafeBlobDel(blobstore_key)
            self.redirect(
                '/uploadpkg?mode=error&msg=Could%20not%20lock%20pkgsinfo')
            return

        old_blobstore_key = None
        pkg = models.PackageInfo.get_or_insert(filename)
        if not pkg.IsSafeToModify():
            gae_util.ReleaseLock(lock)
            gae_util.SafeBlobDel(blobstore_key)
            self.redirect(
                '/uploadpkg?mode=error&msg=Package%20is%20not%20modifiable')
            return

        if pkg.blobstore_key:
            # a previous blob exists.  delete it when the update has succeeded.
            old_blobstore_key = pkg.blobstore_key

        pkg.blobstore_key = blobstore_key
        pkg.name = plist.GetPackageName()
        pkg.filename = filename
        pkg.user = user
        pkg.catalogs = catalogs
        pkg.manifests = manifests
        pkg.install_types = install_types
        pkg.plist = plist
        pkg.pkgdata_sha256 = pkgdata_sha256

        # update the PackageInfo model with the new plist string and blobstore key.
        try:
            pkg.put()
            success = True
        except db.Error:
            logging.exception('error on PackageInfo.put()')
            success = False

        # if it failed, delete the blob that was just uploaded -- it's
        # an orphan.
        if not success:
            gae_util.SafeBlobDel(blobstore_key)
            # if this is a new entity (get_or_insert puts), attempt to delete it.
            if not old_blobstore_key:
                gae_util.SafeEntityDel(pkg)
            gae_util.ReleaseLock(lock)
            self.redirect('/uploadpkg?mode=error')
            return

        # if an old blob was associated with this Package, delete it.
        # the new blob that was just uploaded has replaced it.
        if old_blobstore_key:
            gae_util.SafeBlobDel(old_blobstore_key)

        gae_util.ReleaseLock(lock)

        # Generate catalogs for newly uploaded pkginfo plist.
        for catalog in pkg.catalogs:
            models.Catalog.Generate(catalog, delay=1)

        # Log admin upload to Datastore.
        admin_log = models.AdminPackageLog(user=user,
                                           action='uploadpkg',
                                           filename=filename,
                                           catalogs=catalogs,
                                           manifests=manifests,
                                           install_types=install_types,
                                           plist=pkg.plist.GetXml())
        admin_log.put()

        self.redirect('/uploadpkg?mode=success&key=%s' % blobstore_key)
示例#4
0
class PackagesInfo(handlers.AuthenticationHandler):
    """Handler for /pkgsinfo/"""
    def get(self, filename=None):
        """GET

    Args:
      filename: string like Firefox-1.0.dmg
    """
        auth_return = auth.DoAnyAuth()
        if hasattr(auth_return, 'email'):
            email = auth_return.email()
            if not any((
                    auth.IsAdminUser(email),
                    auth.IsSupportUser(email),
            )):
                raise auth.IsAdminMismatch

        if filename:
            filename = urllib.unquote(filename)
            hash_str = self.request.get('hash')

            if hash_str:
                lock = models.GetLockForPackage(filename)
                try:
                    lock.Acquire(timeout=30, max_acquire_attempts=5)
                except datastore_locks.AcquireLockError:
                    self.response.set_status(httplib.FORBIDDEN)
                    self.response.out.write('Could not lock pkgsinfo')
                    return

            pkginfo = models.PackageInfo.get_by_key_name(filename)
            if pkginfo:
                self.response.headers[
                    'Content-Type'] = 'text/xml; charset=utf-8'
                if hash_str:
                    self.response.headers['X-Pkgsinfo-Hash'] = self._Hash(
                        pkginfo.plist)
                self.response.out.write(pkginfo.plist)
            else:
                if hash_str:
                    lock.Release()
                self.response.set_status(httplib.NOT_FOUND)
                return

            if hash_str:
                lock.Release()
        else:
            query = models.PackageInfo.all()

            filename = self.request.get('filename')
            if filename:
                query.filter('filename', filename)

            install_types = self.request.get_all('install_types')
            for install_type in install_types:
                query.filter('install_types =', install_type)

            catalogs = self.request.get_all('catalogs')
            for catalog in catalogs:
                query.filter('catalogs =', catalog)

            pkgs = []
            for p in query:
                pkg = {}
                for k in p.properties():
                    if k != '_plist':
                        pkg[k] = getattr(p, k)
                pkgs.append(pkg)
            self.response.out.write('<?xml version="1.0" encoding="UTF-8"?>\n')
            self.response.out.write(plist.GetXmlStr(pkgs))
            self.response.headers['Content-Type'] = 'text/xml; charset=utf-8'

    def _Hash(self, s):
        """Return a sha256 hash for a string.

    Args:
      s: str
    Returns:
      str, sha256 digest
    """
        h = hashlib.sha256(str(s))
        return h.hexdigest()

    def put(self, filename):
        """PUT

    Args:
      filename: string like Firefox-1.0.dmg
    """
        session = gaeserver.DoMunkiAuth(
            require_level=gaeserver.LEVEL_UPLOADPKG)

        filename = urllib.unquote(filename)
        hash_str = self.request.get('hash')
        catalogs = self.request.get('catalogs', None)
        manifests = self.request.get('manifests', None)
        install_types = self.request.get('install_types')

        if catalogs == '':
            catalogs = []
        elif catalogs:
            catalogs = catalogs.split(',')
        if manifests == '':
            manifests = []
        elif manifests:
            manifests = manifests.split(',')
        if install_types:
            install_types = install_types.split(',')

        mpl = MunkiPackageInfoPlistStrict(self.request.body)
        try:
            mpl.Parse()
        except plist.PlistError, e:
            logging.exception('Invalid pkginfo plist PUT: \n%s\n',
                              self.request.body)
            self.response.set_status(httplib.BAD_REQUEST)
            self.response.out.write(str(e))
            return

        lock_name = 'pkgsinfo_%s' % filename
        lock = datastore_locks.DatastoreLock(lock_name)
        try:
            lock.Acquire(timeout=30, max_acquire_attempts=5)
        except datastore_locks.AcquireLockError:
            self.response.set_status(httplib.FORBIDDEN)
            self.response.out.write('Could not lock pkgsinfo')
            return

        # To avoid pkginfo uploads without corresponding packages, only allow
        # updates to existing PackageInfo entities, not creations of new ones.
        pkginfo = models.PackageInfo.get_by_key_name(filename)
        if pkginfo is None:
            logging.warning(
                'pkginfo "%s" does not exist; PUT only allows updates.',
                filename)
            self.response.set_status(httplib.FORBIDDEN)
            self.response.out.write('Only updates supported')
            lock.Release()
            return

        # If the pkginfo is not modifiable, ensure only manifests have changed.
        if not pkginfo.IsSafeToModify():
            if not mpl.EqualIgnoringManifestsAndCatalogs(pkginfo.plist):
                logging.warning(
                    'pkginfo "%s" is in stable or testing; change prohibited.',
                    filename)
                self.response.set_status(httplib.FORBIDDEN)
                self.response.out.write('Changes to pkginfo not allowed')
                lock.Release()
                return

        # If the update parameter asked for a careful update, by supplying
        # a hash of the last known pkgsinfo, then compare the hash to help
        # the client make a non destructive update.
        if hash_str:
            if self._Hash(pkginfo.plist) != hash_str:
                self.response.set_status(httplib.CONFLICT)
                self.response.out.write('Update hash does not match')
                lock.Release()
                return

        # All verification has passed, so let's create the PackageInfo entity.
        pkginfo.plist = mpl
        pkginfo.name = mpl.GetPackageName()
        if catalogs is not None:
            pkginfo.catalogs = catalogs
        if manifests is not None:
            pkginfo.manifests = manifests
        if install_types:
            pkginfo.install_types = install_types
        pkginfo.put()

        lock.Release()

        for track in pkginfo.catalogs:
            models.Catalog.Generate(track, delay=1)

        # Log admin pkginfo put to Datastore.
        user = session.uuid
        admin_log = models.AdminPackageLog(user=user,
                                           action='pkginfo',
                                           filename=filename,
                                           catalogs=pkginfo.catalogs,
                                           manifests=pkginfo.manifests,
                                           install_types=pkginfo.install_types,
                                           plist=pkginfo.plist.GetXml())
        admin_log.put()
示例#5
0
文件: uploadpkg.py 项目: smusa/simian
class UploadPackage(admin.AdminHandler,
                    blobstore_handlers.BlobstoreUploadHandler):
    """Handler for /admin/uploadpkg."""
    def get(self):
        """GET Handler.

    With no parameters, return the URL to use to submit uploads via
    multipart/form-data.

    With mode and key parameters, return status of the previous uploadpkg
    operation to the calling client.

    This method actually acts as a helper to the starting and finishing
    of the uploadpkg post() method.

    Parameters:
      mode: optionally, 'success' or 'error'
      key: optionally, blobstore key that was uploaded
    """
        if not handlers.IsHttps(self):
            # TODO(user): Does blobstore support https yet? If so, we can
            # enforce security in app.yaml and not do this check here.
            return

        if not self.IsAdminUser():
            self.error(403)
            return

        mode = self.request.get('mode')
        msg = self.request.get('msg', None)
        if mode == 'success':
            filename = self.request.get('filename')
            msg = '%s successfully uploaded and is ready for deployment.' % filename
            self.redirect('/admin/package/%s?msg=%s' % (filename, msg))
        elif mode == 'error':
            self.response.set_status(400)
            self.response.out.write(msg)
        else:
            filename = self.request.get('filename')
            if not filename:
                self.response.set_status(404)
                self.response.out.write('Filename required')
                return

            p = models.PackageInfo.get_by_key_name(filename)
            if not p:
                self.response.set_status(400)
                self.response.out.write(
                    'You must first upload a pkginfo for %s' % filename)
                return
            elif p.blob_info:
                self.response.set_status(400)
                self.response.out.write('This file already exists.')
                return

            values = {
                'upload_url': blobstore.create_upload_url('/admin/uploadpkg'),
                'filename': filename,
                'file_size_kbytes': p.plist['installer_item_size'],
            }
            self.Render('upload_pkg_form.html', values)

    def post(self):
        """POST Handler.

    This method behaves a little strangely.  BlobstoreUploadHandler
    only allows returns statuses of 301, 302, 303 (not even 200), so
    one must redirect away to return more information to the caller.

    Parameters:
      file: package file contents
      pkginfo: packageinfo file contents
      name: filename of package e.g. 'Firefox-1.0.dmg'
    """
        # Only blobstore/upload service/scotty requests should be
        # invoking this handler.
        if not handlers.IsBlobstore():
            logging.critical('POST to uploadpkg not from Blobstore: %s',
                             self.request.headers)
            self.redirect('/admin/packages')

        # TODO(user): do we check is admin?

        if not self.get_uploads('file'):
            logging.error('Upload package does not exist.')
            return

        blob_info = self.get_uploads('file')[0]
        blobstore_key = str(blob_info.key())

        # Obtain a lock on the PackageInfo entity for this package.
        lock = 'pkgsinfo_%s' % blob_info.filename
        if not gae_util.ObtainLock(lock, timeout=5.0):
            gae_util.SafeBlobDel(blobstore_key)
            self.redirect(
                '/admin/uploadpkg?mode=error&msg=PackageInfo is locked')
            return

        p = models.PackageInfo.get_by_key_name(blob_info.filename)
        if not p:
            gae_util.ReleaseLock(lock)
            gae_util.SafeBlobDel(blobstore_key)
            self.redirect(
                '/admin/uploadpkg?mode=error&msg=PackageInfo not found')
            return

        if not p.IsSafeToModify():
            gae_util.ReleaseLock(lock)
            gae_util.SafeBlobDel(blobstore_key)
            self.redirect(
                '/admin/uploadpkg?mode=error&msg=PackageInfo is not modifiable'
            )
            return

        installer_item_size = p.plist['installer_item_size']
        size_difference = int(blob_info.size / 1024) - installer_item_size
        if abs(size_difference) > 1:
            gae_util.ReleaseLock(lock)
            gae_util.SafeBlobDel(blobstore_key)
            msg = 'Blob size (%s) does not match PackageInfo plist size (%s)' % (
                blob_info.size, installer_item_size)
            self.redirect('/admin/uploadpkg?mode=error&msg=%s' % msg)
            return

        old_blobstore_key = None
        if p.blob_info:
            # a previous blob exists.  delete it when the update has succeeded.
            old_blobstore_key = p.blob_info.key()

        p.blob_info = blob_info

        # update the PackageInfo model with the new plist string and blobstore key.
        try:
            p.put()
            error = None
        except db.Error, e:
            logging.exception('error on PackageInfo.put()')
            error = 'pkginfo.put() failed with: %s' % str(e)

        # if it failed, delete the blob that was just uploaded -- it's
        # an orphan.
        if error is not None:
            gae_util.SafeBlobDel(blobstore_key)
            gae_util.ReleaseLock(lock)
            self.redirect('/admin/uploadpkg?mode=error&msg=%s' % error)
            return

        # if an old blob was associated with this Package, delete it.
        # the new blob that was just uploaded has replaced it.
        if old_blobstore_key:
            gae_util.SafeBlobDel(old_blobstore_key)

        gae_util.ReleaseLock(lock)

        user = users.get_current_user().email()
        # Log admin upload to Datastore.
        admin_log = models.AdminPackageLog(user=user,
                                           action='uploadpkg',
                                           filename=blob_info.filename)
        admin_log.put()

        self.redirect('/admin/uploadpkg?mode=success&filename=%s' %
                      blob_info.filename)