CVE-2023-29208 (Java, published 2023-04-15T16:15Z)
Description: value: XWiki Commons are technical libraries common to several other top level XWiki projects. Rights added to a document are not taken into account for viewing it once it's deleted. Note that this vulnerability only impact deleted documents that where containing view rights: the view rights provided on a space of a deleted document are properly checked. The problem has been patched in XWiki 14.10 by checking the rights of current user: only admin and deleter of the document are allowed to view it.
Severity: nan (CVSS v3 base score: 7.5)
Weakness: CWE-668 \u2014 Exposure of Resource to Wrong Sphere
The product exposes a resource to the wrong control sphere, providing unintended actors with inappropriate access to the resource.
Repository: https://github.com/xwiki/xwiki-platform
Files changed:
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/api/DeletedDocument.java
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/doc/DocumentRevisionProvider.java
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/doc/XWikiDeletedDocument.java
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/internal/doc/DefaultDocumentRevisionProvider.java
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/internal/doc/DeletedDocumentRevisionProvider.java
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/store/XWikiHibernateRecycleBinStore.java
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/store/XWikiRecycleBinStoreInterface.java
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/XWikiAction.java
xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/web/UndeleteActionTest.java
xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/main/resources/templates/recyclebinlist.vm
Commit message: XWIKI-16285: Error when accessing deleted document


Vulnerable code:
import java.util.Objects;
        try {
            return hasAccessLevel(ADMIN_RIGHT, getFullName()) || hasAccessLevel("undelete", getFullName())
                || (Objects.equals(this.context.getUserReference(), getDeleterReference())
                    && hasAccess(Right.EDIT, getDocumentReference()));
        } catch (XWikiException ex) {
            // Public APIs should not throw exceptions
            LOGGER.warn("Exception while checking if entry [{}] can be restored from the recycle bin", getId(), ex);
            return false;
        }
    @Override
    public XWikiDocument getRevision(DocumentReference reference, String revision) throws XWikiException
        // Parse the version
                    throw new XWikiException("Failed to get revision provider for revision [" + revision + "]", e);
        return provider.getRevision(reference, shortRevision);

            XWikiDocument rdoc =
                (!doc.getLocale().equals(tdoc.getLocale())) ? doc : context.getWiki().getDocument(doc, rev, context);
            XWikiDocument rtdoc =
                (doc.getLocale().equals(tdoc.getLocale())) ? rdoc : context.getWiki().getDocument(tdoc, rev, context);

        when(this.oldcore.getMockRightService().hasAccessLevel(any(), any(), any(), any())).thenReturn(true);

        when(this.oldcore.getMockAuthorizationManager().hasAccess(Right.EDIT, null, DELETED_REFERENCE))
        when(this.oldcore.getMockRightService().hasAccessLevel(any(), any(), any(), any())).thenReturn(true);

        when(this.oldcore.getMockRightService().hasAccessLevel(any(), any(), any(), any())).thenReturn(true);

Fixed code:
import org.xwiki.component.util.DefaultParameterizedType;
import org.xwiki.stability.Unstable;
import org.xwiki.user.UserReference;
import org.xwiki.user.UserReferenceResolver;
import com.xpn.xwiki.store.XWikiRecycleBinStoreInterface;
import com.xpn.xwiki.web.Utils;
    private UserReferenceResolver<DocumentReference> userReferenceResolver;

    private UserReferenceResolver<DocumentReference> getUserReferenceResolver()
    {
        if (this.userReferenceResolver == null) {
            this.userReferenceResolver = Utils.getComponent(
                new DefaultParameterizedType(null, UserReferenceResolver.class, DocumentReference.class), "document");
        }
        return this.userReferenceResolver;
    }

    private boolean hasAccess(Right right)
    {
        UserReference userReference = getUserReferenceResolver().resolve(this.context.getUserReference());
        XWikiRecycleBinStoreInterface recycleBinStore = this.context.getWiki().getRecycleBinStore();
        return recycleBinStore.hasAccess(right, userReference, this.deletedDoc);
    }

        return hasAccess(Right.EDIT);
    }

    /**
     * Check if the current user has the right to view the deleted document.
     * This is allowed either if the user has admin right on the original reference of the doc, or if they were the
     * original user who deleted it.
     *
     * @return {code true} if the current user is allowed to view the deleted document.
     * @since 14.10RC1
     * @since 14.4.7
     * @since 13.10.11
     */
    @Unstable
    public boolean canView()
    {
        return hasAccess(Right.VIEW);
import org.xwiki.security.authorization.AccessDeniedException;
import org.xwiki.security.authorization.AuthorizationException;
import org.xwiki.security.authorization.Right;
import org.xwiki.stability.Unstable;
import org.xwiki.user.UserReference;

    /**
     * Check if access is granted on the given document revision, for the given user and right: if the access is not
     * granted this method will throw an {@link AccessDeniedException}.
     * This method allows each revision provider to have their own check depending on the type of revision.
     *
     * @param right the right for which to check if access is granted
     * @param userReference the user for whom to check access
     * @param documentReference the reference of the document
     * @param revision the revision of the document
     * @throws AuthorizationException if the access is denied
     * @throws XWikiException in case of problem when loading the revision
     * @since 14.10RC1
     * @since 14.4.7
     * @since 13.10.11
     */
    @Unstable
    default void checkAccess(Right right, UserReference userReference, DocumentReference documentReference,
        String revision) throws AuthorizationException, XWikiException
    {
    }
import org.apache.commons.lang3.builder.ToStringBuilder;

    @Override
    public String toString()
    {
        return new ToStringBuilder(this)
            .append("id", id)
            .append("fullName", fullName)
            .append("locale", locale)
            .toString();
    }
import org.apache.commons.lang3.tuple.Pair;
import org.xwiki.security.authorization.AuthorizationException;
import org.xwiki.security.authorization.Right;
import org.xwiki.user.UserReference;
    private Pair<String, String> parseRevision(String revision)
        return Pair.of(revisionPrefix, shortRevision);
    }
    private DocumentRevisionProvider getProvider(String revisionPrefix) throws XWikiException
    {
                    throw new XWikiException("Failed to get revision provider for revision [" + revisionPrefix + "]",
                        e);
        return provider;
    }

    @Override
    public XWikiDocument getRevision(DocumentReference reference, String revision) throws XWikiException
    {
        Pair<String, String> parsedRevision = parseRevision(revision);
        return getProvider(parsedRevision.getLeft()).getRevision(reference, parsedRevision.getRight());
    }

    @Override
    public void checkAccess(Right right, UserReference userReference, DocumentReference documentReference,
        String revision) throws AuthorizationException, XWikiException
    {
        Pair<String, String> parsedRevision = parseRevision(revision);

        getProvider(parsedRevision.getLeft())
            .checkAccess(right, userReference, documentReference, parsedRevision.getRight());
import org.xwiki.security.authorization.AuthorizationException;
import org.xwiki.security.authorization.Right;
import org.xwiki.user.UserReference;

    @Override
    public void checkAccess(Right right, UserReference userReference, DocumentReference documentReference,
        String revision) throws AuthorizationException, XWikiException
    {
        XWikiContext xcontext = this.xcontextProvider.get();

        XWikiDeletedDocument deletedDocument = xcontext.getWiki().getDeletedDocument(Long.valueOf(revision), xcontext);
        if (deletedDocument != null) {
            xcontext.getWiki().getRecycleBinStore().checkAccess(right, userReference, deletedDocument);
        }
    }
import java.util.Objects;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.security.authorization.AuthorizationException;
import org.xwiki.security.authorization.AuthorizationManager;
import org.xwiki.security.authorization.Right;
import org.xwiki.user.UserReference;
import org.xwiki.user.UserReferenceSerializer;
    @Inject
    private AuthorizationManager authorizationManager;

    @Inject
    @Named("document")
    private UserReferenceSerializer<DocumentReference> userReferenceSerializer;


    @Override
    public void checkAccess(Right right, UserReference userReference, XWikiDeletedDocument deletedDocument)
        throws AuthorizationException
    {
        if (!this.hasAccess(right, userReference, deletedDocument)) {
            throw new AuthorizationException(
                String.format("[%s] cannot access deleted document [%s] for right [%s]: "
                    + "only admin or deleter of the document are authorized",
                    userReference, deletedDocument, right));
        }
    }

    @Override
    public boolean hasAccess(Right right, UserReference userReference, XWikiDeletedDocument deletedDocument)
    {
        DocumentReference documentReference = deletedDocument.getDocumentReference();
        DocumentReference userDocReference = this.userReferenceSerializer.serialize(userReference);

        boolean result = false;
        if (this.authorizationManager.hasAccess(Right.ADMIN, userDocReference, documentReference)
            || (Objects.equals(deletedDocument.getDeleterReference(), userDocReference)
            && this.authorizationManager.hasAccess(right, userDocReference, documentReference))) {
            result = true;
        }
        return result;
    }
import org.xwiki.security.authorization.AuthorizationException;
import org.xwiki.security.authorization.Right;
import org.xwiki.stability.Unstable;
import org.xwiki.user.UserReference;

    /**
     * Check if the given deleted document can be accessed for the given right by the given user.
     * This method only throw the {@link AuthorizationException} if the right is not granted.
     *
     * @param right the right to check access for
     * @param userReference the user for whom to check access
     * @param deletedDocument the document to be accessed
     * @throws AuthorizationException if the user doesn't have appropriate right
     * @since 14.10RC1
     * @since 14.4.7
     * @since 13.10.11
     */
    @Unstable
    default void checkAccess(Right right, UserReference userReference, XWikiDeletedDocument deletedDocument) throws
        AuthorizationException
    {
    }

    /**
     * Check if the given deleted document can be accessed for the given right by the given user.
     *
     * @param right the right to check access for
     * @param userReference the user for whom to check access
     * @param deletedDocument the document to be accessed
     * @return {@code true} if the user have appropriate right
     * @since 14.10RC1
     * @since 14.4.7
     * @since 13.10.11
     */
    @Unstable
    default boolean hasAccess(Right right, UserReference userReference, XWikiDeletedDocument deletedDocument)
    {
        return false;
    }
import org.xwiki.security.authorization.AuthorizationException;
import org.xwiki.user.UserReference;
import org.xwiki.user.UserReferenceResolver;
import com.xpn.xwiki.doc.DocumentRevisionProvider;
    @Inject
    private DocumentRevisionProvider documentRevisionProvider;

    @Inject
    @Named("document")
    private UserReferenceResolver<DocumentReference> userReferenceResolver;

    private UserReference getCurrentUserReference(XWikiContext context)
    {
        return this.userReferenceResolver.resolve(context.getUserReference());
    }

            DocumentReference documentReference = doc.getDocumentReference();
            try {
                documentRevisionProvider
                    .checkAccess(Right.VIEW, getCurrentUserReference(context), documentReference, rev);
            } catch (AuthorizationException e) {
                Object[] args = { documentReference, rev, context.getUserReference() };
                throw new XWikiException(XWikiException.MODULE_XWIKI_ACCESS, XWikiException.ERROR_XWIKI_ACCESS_DENIED,
                    "Access to document {0} with revision {1} has been denied to user {2}", e, args);
            }

            XWikiDocument rdoc;
            XWikiDocument rtdoc;
            if (doc.getLocale().equals(tdoc.getLocale())) {
                rdoc = this.documentRevisionProvider.getRevision(doc.getDocumentReferenceWithLocale(), rev);
                rtdoc = rdoc;
            } else {
                rdoc = doc;
                rtdoc = this.documentRevisionProvider.getRevision(tdoc.getDocumentReferenceWithLocale(), rev);
            }
import com.xpn.xwiki.store.XWikiHibernateRecycleBinStore;
    @Mock
    private XWikiHibernateRecycleBinStore recycleBinStore;

        this.oldcore.getSpyXWiki().setRecycleBinStore(this.recycleBinStore);
        when(this.recycleBinStore.hasAccess(any(), any(), any()))
            .thenReturn(true);
        when(this.recycleBinStore.hasAccess(any(), any(), any()))
        when(this.recycleBinStore.hasAccess(any(), any(), any()))
            .thenReturn(true);
        when(this.recycleBinStore.hasAccess(any(), any(), any()))
            .thenReturn(true);
    #set ($canView = $list[0].canView())
         #if ($canView)
         #else
           $xwiki.formatDate($dd.getDate())
         #end