Ticket #13165 (confirmed Bug)

Opened 4 years ago

Last modified 4 years ago

CloneBlobs requires user to have AccessPreviousVersions permission

Reported by: nglogic Owned by: alecm
Priority: minor Milestone: 4.x
Component: Versioning Version: 4.1
Keywords: Cc:

Description

In CloneBlobs.getReferencedAttributes, line 1080, repo.retrieve is called, which requires this permission. This prevents users without AccessPreviousVersions perm from creating a new or editing object of the type that involves CloneBlobs.

        repo = getToolByName(obj, 'portal_repository')
        try:
            prior_rev = repo.retrieve(obj)
        except ArchivistRetrieveError:
            prior_rev = None
        for f in blob_fields:

Suggested solution: use the _retrieve method that skips checking for permission.

prior_rev = repo._retrieve(obj, selector=None, preserve=(), countPurged=True)

This affects CMFEditions 2.2.5 and older.

Change History

comment:1 Changed 4 years ago by nglogic

  • Owner set to alecm
  • Component changed from Unknown to Versioning

comment:2 Changed 4 years ago by nglogic

Workaround: define a new modifier and add it to portal_modifier object.

import os,sys
from itertools import izip
from App.class_init import InitializeClass

from Acquisition import aq_base
from zope.interface import implements, Interface
from ZODB.blob import Blob
from OFS.ObjectManager import ObjectManager
from Products.BTreeFolder2.BTreeFolder2 import BTreeFolder2Base
from Products.PageTemplates.PageTemplateFile import PageTemplateFile

from Products.CMFCore.utils import getToolByName
from Products.CMFCore.permissions import ManagePortal
from Products.CMFCore.Expression import Expression

from Products.CMFEditions.interfaces.IArchivist import ArchivistRetrieveError
from Products.CMFEditions.interfaces.IModifier import IAttributeModifier
from Products.CMFEditions.interfaces.IModifier import ICloneModifier
from Products.CMFEditions.interfaces.IModifier import ISaveRetrieveModifier
from Products.CMFEditions.interfaces.IModifier import IConditionalTalesModifier
from Products.CMFEditions.interfaces.IModifier import IReferenceAdapter
from Products.CMFEditions.interfaces.IModifier import FileTooLargeToVersionError
from Products.CMFEditions.Modifiers import ConditionalModifier
from Products.CMFEditions.Modifiers import ConditionalTalesModifier

from Products.CMFEditions.StandardModifiers import CloneBlobs, manage_CloneBlobsAddForm
from Products.CMFEditions.StandardModifiers import modifiers

try:
    from plone.app.blob.interfaces import IBlobField
except ImportError:
    class IBlobField(Interface):
        pass

manage_NewCloneBlobsAddForm =  \
    PageTemplateFile('www/NewCloneBlobs.pt',
                   globals(),
                   __name__='manage_NewCloneBlobsAddForm')

class NewCloneBlobs(CloneBlobs):
    def __init__(self, obj, name):
        """Initialize the adapter.
        """
        self._obj = obj
        self._name = name    
    
    def getReferencedAttributes(self, obj):
        blob_fields = (f for f in obj.Schema().fields()
                       if IBlobField.providedBy(f))
        file_data = {}
        # try to get last revision, only store a new blob if the
        # contents differ from the prior one, otherwise store a
        # reference to the prior one
        # XXX: According to CopyModifyMergeRepository, retrieve and getHistory
        #      should not be used as a part of more complex transactions
        #      due to some resource managers not supporting savepoints.
        repo = getToolByName(obj, 'portal_repository')
        try:
            prior_rev = repo._retrieve(obj, selector=None, preserve=(), countPurged=True)
        except ArchivistRetrieveError:
            prior_rev = None
        for f in blob_fields:
            # XXX: should only do this if data has changed since last
            # version somehow Resave the blob line by line
            blob_file = f.get(obj, raw=True).getBlob().open('r')
            save_new = True
            if prior_rev is not None:
                prior_obj = prior_rev.object
                prior_blob = f.get(prior_obj, raw=True).getBlob()
                prior_file = prior_blob.open('r')
                # Check for file size differences
                if (os.fstat(prior_file.fileno()).st_size ==
                    os.fstat(blob_file.fileno()).st_size):
                    # Files are the same size, compare line by line
                    for line, prior_line in izip(blob_file, prior_file):
                        if line != prior_line:
                            break
                    else:
                        # The files are the same, save a reference
                        # to the prior versions blob on this version
                        file_data[f.getName()] = prior_blob
                        save_new = False
            if save_new:
                new_blob = file_data[f.getName()] = Blob()
                new_blob_file = new_blob.open('w')
                try:
                    blob_file.seek(0)
                    new_blob_file.writelines(blob_file)
                finally:
                    blob_file.close()
                    new_blob_file.close()
        return file_data
    
InitializeClass(NewCloneBlobs)

def initialize(context):
    """Registers modifiers with zope (on zope startup).
    """
    
    m =  {
        'id': 'NewCloneBlobs',
        'title': "Store blobs and files by reference on AT content, new version",
        'enabled': True,
        'condition': "python: portal_type in ('Image', 'File')",
        'wrapper': ConditionalTalesModifier,
        'modifier': NewCloneBlobs,
        'form': manage_NewCloneBlobsAddForm,
        'factory': manage_addNewCloneBlobs,
    }
    
    context.registerClass(
        m['wrapper'], m['id'],
        permission = ManagePortal,
        constructors = (m['form'], m['factory']),
    )    
    
def manage_addNewCloneBlobs(self, id, title=None, REQUEST=None):
    """Add a skip parent pointers modifier
    """
    modifier = NewCloneBlobs(id, title)
    self._setObject(id, modifier)

    if REQUEST is not None:
        REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')    



Requires initialize method to be called from your product similar method and a www/NewCloneBlobs.pt template:

<p tal:replace="structure here/manage_page_header" omit-tag="">Header</p>
<p tal:replace="structure here/manage_tabs" omit-tag="">tabs</p>


<h2>Add Clone Blobs Modifier</h2>

<form action="manage_addNewCloneBlobs" method="post">

  <table border="0">

    <tr>
      <th>
        Id
      </th>
      <td>
        <input type="text" name="id" value="" size="40"/>
      </td>
    </tr>

    <tr>
      <th>
        Title
      </th>
      <td>
        <input type="text" name="title" value="" size="40"/>
      </td>
    </tr>

    <tr>
      <td colspan="2">
        <input type="submit" name="submit" value="Add"/>
      </td>
    </tr>

  </table>

</form>

<p tal:replace="structure here/manage_page_footer" omit-tag="">Footer</p>

comment:3 Changed 4 years ago by kleist

  • Status changed from new to confirmed
Note: See TracTickets for help on using tickets.