-
Bug
-
Resolution: Unresolved
-
Low
-
None
-
6.13.3, 6.13.6, 6.15.4
-
8
-
Severity 3 - Minor
-
1
-
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
- Install a vanilla instance of Confluence.
- Create a sample Space, let's call it Target Space.
- Create a sample source page as part of Target Space.
- 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
- Create a target page under Target Space.
- 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>
- In the storage format, the macro would look similar to the below.
- Enable page profiling.
- 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
- is related to
-
CONFSERVER-5494 Improve page load time when it contains lots of links
- Gathering Interest
-
CONFSERVER-58735 Asynchronously render attachments when loading a Confluence page
- Gathering Interest
- relates to
-
CONFSERVER-63527 Viewing a large page with attachments macro causes slow loads and timeouts.
- Closed