Uploaded image for project: 'Confluence Data Center'
  1. Confluence Data Center
  2. CONFSERVER-58733

Loading a page with links to multiple attachments from the same source page makes Confluence to check for permissions on every link instead of just once

    XMLWordPrintable

Details

    Description

      Issue Summary

      On a Confluence page, users may choose to add links to attachments that are from other pages within the same or different Space.
      Attachments can be added as a preview or a link. More on this functionality can be found in Display Files and Images.

      Since these are internal links, Confluence needs to ensure the current user has the necessary permissions to view the target attachment by checking the current permissions and restrictions of the page where the attachment resides.
      It is important to note that restrictions are not directly applied to attachments, but to their pages.

      If a page contains multiple links to different attachments from the same source page, Confluence will validate permissions to every attachment, instead of doing this task just once.

      Since permissions validation could be an expensive task within Confluence, performing the same validation multiple times instead of just once might become a problem.

      Steps to Reproduce

      1. Install a vanilla instance of Confluence.
      2. Create a sample Space, let's call it Target Space.
      3. Create a sample source page as part of Target Space.
      4. Create 100 attachment files to the source page – the following shell script might help on that.
        CONFLUENCE_BASE_URL=http://localhost:26136/c6136
        ADMIN_USR=admin
        ADMIN_PWD=admin
        PAGE_ID=327702
         
        #### Add lots of attachments to a page
        for i in $(seq 1 100); do
         FILENAME=testing${i}.txt
         touch $FILENAME
         curl -D- -u $ADMIN_USR:$ADMIN_PWD -X POST -H "X-Atlassian-Token: nocheck" -F "file=@${FILENAME}" -F "comment=This is my File" ${CONFLUENCE_BASE_URL}/rest/api/content/${PAGE_ID}/child/attachment
         rm -f $FILENAME
        done
        
      5. Create a target page under Target Space.
      6. Edit the target page and add all the attachments from the source page as attachments links.
        • In the storage format, the macro would look similar to the below.
          <ac:link><ri:attachment ri:filename="testing1.txt"><ri:page ri:content-title="Source Page" /></ri:attachment></ac:link>
          
      7. Enable page profiling.
      8. As a regular user (not part of confluence-administrators group), access the view mode of the target page.

      Expected Results

      In order to load the attachments' links, Confluence would validate if the current user has access to the source page just once and load all 100 links based on that initial validation.

      Actual Results

      Confluence checks for permissions for each and every link, even though they are from the same source page.
      Checking atlassian-confluence.log with page profiling enabled shows an entry similar to the below,

      2019-08-19 13:36:45,065 DEBUG [http-nio-26136-exec-16] [atlassian.util.profiling.UtilTimerStack] log [279ms] - /c6136/display/TA/New+target+page
        [9ms] - UserAccessor.getExistingUserByKey()
        [2ms] - UserAccessor.getUserByName()
          [2ms] - CrowdService.getUser()
            [0ms] - ApplicationDAO.findByName()
            [1ms] - UserDao.findByName()
        [22ms] - PermissionManager.isSystemAdministrator()
          [0ms] - UserAccessor.isDeactivated()
          [13ms] - CrowdService.isUserMemberOfGroup()
            [0ms] - ApplicationDAO.findByName()
            [13ms] - MembershipDao.isUserDirectMember()
          [0ms] - UserAccessor.isDeactivated()
          [0ms] - CrowdService.isUserMemberOfGroup()
            [0ms] - ApplicationDAO.findByName()
            [0ms] - MembershipDao.isUserDirectMember()
          [1ms] - CrowdService.isUserMemberOfGroup()
            [0ms] - ApplicationDAO.findByName()
            [0ms] - MembershipDao.isUserDirectMember()
          [1ms] - DefaultSpacePermissionManager.hasPermissionNoExemptions(SYSTEMADMINISTRATOR, user001, global)
            [0ms] - CrowdService.isUserMemberOfGroup()
              [0ms] - ApplicationDAO.findByName()
              [0ms] - MembershipDao.isUserDirectMember()
        [0ms] - UserAccessor.getExistingUserByKey()
        [0ms] - UserAccessor.getUserByName()
          [0ms] - CrowdService.getUser()
            [0ms] - ApplicationDAO.findByName()
            [0ms] - UserDao.findByName()
        [0ms] - PermissionManager.isSystemAdministrator()
        [157ms] - XW Interceptor: Before defaultStack: /pages/viewpage.action (ViewPageAction.execute())
          [0ms] - UserAccessor.exists()
            [0ms] - CrowdService.getUser()
              [0ms] - ApplicationDAO.findByName()
              [0ms] - UserDao.findByName()
          [0ms] - UserAccessor.getPropertySet()
          [0ms] - SpaceAwareInterceptor.intercept()
          [155ms] - PageAwareInterceptor.intercept()
            [2ms] - PageManager.getPageWithComments()
            [2ms] - PermissionManager.hasPermission()
              [2ms] - DefaultSpacePermissionManager.hasPermissionNoExemptions(VIEWSPACE, user001, TA)
                [0ms] - CrowdService.isUserMemberOfGroup()
                  [0ms] - ApplicationDAO.findByName()
                  [0ms] - MembershipDao.isUserDirectMember()
            [4ms] - PermissionManager.hasPermission()
            [0ms] - PermissionManager.hasPermission()
            [0ms] - CommentAwareInterceptor.intercept()
            [0ms] - UserAwareInterceptor.intercept()
            [0ms] - BootstrapAwareInterceptor.intercept()
            [0ms] - PermissionManager.hasPermission()
            [145ms] - XW Interceptor: After defaultStack: /pages/viewpage.action (ViewPageAction.execute())
              [145ms] - XW Interceptor: After validatingStack: /pages/viewpage.action (ViewPageAction.execute())
                [0ms] - UserAccessor.getPropertySet()
                [28ms] - DefaultRenderer.render
                  [28ms] - DefaultRenderer.renderWithResult
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [1ms] - PageManager.getPage()
                    [1ms] - PermissionManager.hasPermission()
                    [0ms] - PageManager.getPage()
                    [0ms] - PermissionManager.hasPermission()
                    [0ms] - PageManager.getPage()
                    [0ms] - PermissionManager.hasPermission()
                    [0ms] - PageManager.getPage()
                    [0ms] - PermissionManager.hasPermission()
                    [0ms] - PageManager.getPage()
                    [0ms] - PermissionManager.hasPermission()
                    [0ms] - PageManager.getPage()
                    [0ms] - PermissionManager.hasPermission()
                    [0ms] - PageManager.getPage()
                    [0ms] - PermissionManager.hasPermission()
                    [0ms] - PageManager.getPage()
                    [0ms] - PermissionManager.hasPermission()
                    [0ms] - PageManager.getPage()
                    [0ms] - PermissionManager.hasPermission()
                    [0ms] - PageManager.getPage()
                    [0ms] - PermissionManager.hasPermission()
                    [0ms] - PageManager.getPage()
                    [0ms] - PermissionManager.hasPermission()
                    [0ms] - PageManager.getPage()
                    [0ms] - PermissionManager.hasPermission()
                    [0ms] - PageManager.getPage()
                    [0ms] - PermissionManager.hasPermission()
                    [0ms] - PageManager.getPage()
                    [0ms] - PermissionManager.hasPermission()
                    [0ms] - PageManager.getPage()
                    [0ms] - PermissionManager.hasPermission()
                    [0ms] - PageManager.getPage()
                    [0ms] - PermissionManager.hasPermission()
                    [0ms] - PageManager.getPage()
                    [0ms] - PermissionManager.hasPermission()
                    [0ms] - PageManager.getPage()
                    [0ms] - PermissionManager.hasPermission()
                    [0ms] - PageManager.getPage()
                    [0ms] - PermissionManager.hasPermission()
                    [0ms] - PageManager.getPage()
                    [0ms] - PermissionManager.hasPermission()
                    [0ms] - PageManager.getPage()
                    [0ms] - PermissionManager.hasPermission()
      

      In this case, the more links we add to attachments from the source page, the more permissions validations we see in the profiling:

      (...)
                    [0ms] - PageManager.getPage()
                    [0ms] - PermissionManager.hasPermission()
      (...)
      

      If attachments are added as file preview, then the page profiling would slightly change as the example below:

      2019-08-19 12:30:19,589 DEBUG [http-nio-26136-exec-42] [atlassian.util.profiling.UtilTimerStack] log [1056ms] - /c6136/display/TA/Target+Page
        [0ms] - UserAccessor.getExistingUserByKey()
        [0ms] - UserAccessor.getUserByName()
          [0ms] - CrowdService.getUser()
            [0ms] - ApplicationDAO.findByName()
            [0ms] - UserDao.findByName()
        [0ms] - PermissionManager.isSystemAdministrator()
          [0ms] - UserAccessor.isDeactivated()
          [0ms] - CrowdService.isUserMemberOfGroup()
            [0ms] - ApplicationDAO.findByName()
            [0ms] - MembershipDao.isUserDirectMember()
          [0ms] - UserAccessor.isDeactivated()
          [0ms] - CrowdService.isUserMemberOfGroup()
            [0ms] - ApplicationDAO.findByName()
            [0ms] - MembershipDao.isUserDirectMember()
          [0ms] - CrowdService.isUserMemberOfGroup()
            [0ms] - ApplicationDAO.findByName()
            [0ms] - MembershipDao.isUserDirectMember()
          [0ms] - DefaultSpacePermissionManager.hasPermissionNoExemptions(SYSTEMADMINISTRATOR, user001, global)
            [0ms] - CrowdService.isUserMemberOfGroup()
              [0ms] - ApplicationDAO.findByName()
              [0ms] - MembershipDao.isUserDirectMember()
        [0ms] - UserAccessor.getExistingUserByKey()
        [0ms] - UserAccessor.getUserByName()
          [0ms] - CrowdService.getUser()
            [0ms] - ApplicationDAO.findByName()
            [0ms] - UserDao.findByName()
        [0ms] - PermissionManager.isSystemAdministrator()
        [930ms] - XW Interceptor: Before defaultStack: /pages/viewpage.action (ViewPageAction.execute())
          [0ms] - UserAccessor.exists()
            [0ms] - CrowdService.getUser()
              [0ms] - ApplicationDAO.findByName()
              [0ms] - UserDao.findByName()
          [0ms] - UserAccessor.getPropertySet()
          [0ms] - SpaceAwareInterceptor.intercept()
          [921ms] - PageAwareInterceptor.intercept()
            [24ms] - PageManager.getPageWithComments()
            [0ms] - PermissionManager.hasPermission()
              [0ms] - DefaultSpacePermissionManager.hasPermissionNoExemptions(VIEWSPACE, user001, TA)
                [0ms] - CrowdService.isUserMemberOfGroup()
                  [0ms] - ApplicationDAO.findByName()
                  [0ms] - MembershipDao.isUserDirectMember()
            [0ms] - PermissionManager.hasPermission()
            [0ms] - PermissionManager.hasPermission()
            [0ms] - CommentAwareInterceptor.intercept()
            [0ms] - UserAwareInterceptor.intercept()
            [0ms] - BootstrapAwareInterceptor.intercept()
            [0ms] - PermissionManager.hasPermission()
            [891ms] - XW Interceptor: After defaultStack: /pages/viewpage.action (ViewPageAction.execute())
              [891ms] - XW Interceptor: After validatingStack: /pages/viewpage.action (ViewPageAction.execute())
                [0ms] - UserAccessor.getPropertySet()
                [684ms] - DefaultRenderer.render
                  [684ms] - DefaultRenderer.renderWithResult
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [1ms] - PageManager.getPage()
                    [16ms] - PermissionManager.hasPermission()
                    [1ms] - CommentManager.countUnresolvedComments()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - PageManager.getPage()
                    [1ms] - PermissionManager.hasPermission()
                    [0ms] - CommentManager.countUnresolvedComments()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - PageManager.getPage()
                    [0ms] - PermissionManager.hasPermission()
                    [1ms] - CommentManager.countUnresolvedComments()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - PageManager.getPage()
                    [0ms] - PermissionManager.hasPermission()
                    [1ms] - CommentManager.countUnresolvedComments()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - PageManager.getPage()
                    [1ms] - PermissionManager.hasPermission()
                    [1ms] - CommentManager.countUnresolvedComments()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - PageManager.getPage()
                    [0ms] - PermissionManager.hasPermission()
                    [1ms] - CommentManager.countUnresolvedComments()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - PageManager.getPage()
                    [0ms] - PermissionManager.hasPermission()
                    [1ms] - CommentManager.countUnresolvedComments()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
                    [0ms] - UserAccessor.getPropertySet()
      

      Notes

      The same occurs if we add a link to the same attachment multiple times. Confluence disregards it is the same file and performs permissions check on every link.
      Since permissions and restrictions are on Space and page level, we shouldn't perform the validation on every attachment, just on source pages.

      Workaround

      Currently there is no known workaround for this behavior. A workaround will be added here when available

      Attachments

        Issue Links

          Activity

            People

              Unassigned Unassigned
              tmasutti Thiago Masutti
              Votes:
              2 Vote for this issue
              Watchers:
              4 Start watching this issue

              Dates

                Created:
                Updated: