Tracking customer-visible content in Confluence with ScriptRunner

Joshua DeClerck July 8, 2016

I'm still figuring out SR for Confluence, and I'm just wondering if something I want to do is a dead-end or not (before I invest too much time on it).

One of the biggest issues we run into is auditing the visibility of content in Confluence. As a very simple, anybody-can-check auto-audit, I was thinking about making my first real exercise with SR4C to be setting up a routine that checks for pages visible to external user accounts and either adds a panel, footer, or label to the page saying so.

All of our "internal" accounts belong to a common group (let's call it 'internal-users'). This scripted behavior would look for any and all pages that can be successfully loaded by even a single user account that is not a member of 'internal-users'. If it can be, then let's say it adds a "customer-visible" label to the page.

Likewise, in reverse, if the security is changed and a visible page is restricted to internal-only, the "customer-visible" label is removed.

Is this a feasible thing to pursue? Is there maybe a simpler approach? I'm basically ruling out anything that requires mass education of our user-base.

 

Thanks!

11 answers

1 vote
Jonny Carter
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
July 19, 2016

For the basic implementation discussed above (showing a banner or warning panel indicating that the page is by some means public) the Script Fragment shown below should work. I used the Web Panel type, since we don't want this buried in a menu.

One big caveat is that this script does not check for anonymous access, which should probably be done as well for any real implementation.

Example script fragment implementation:

Screen Shot 2016-07-19 at 11.26.38 AM.png

package com.onresolve.examples

import com.atlassian.confluence.core.ContentPermissionManager
import com.atlassian.confluence.pages.PageManager
import com.atlassian.confluence.plugin.descriptor.web.DefaultWebInterfaceContext
import com.atlassian.confluence.security.ContentPermission
import com.atlassian.confluence.security.ContentPermissionSet
import com.atlassian.confluence.security.SpacePermission
import com.atlassian.confluence.security.SpacePermissionManager
import com.atlassian.confluence.user.UserAccessor
import com.atlassian.sal.api.component.ComponentLocator

String internalUsers = "internal-users"
Closure otherGroupsTest = { groupName ->
    groupName = groupName.toString()
    !([internalUsers, "confluence-administrators"].contains(groupName)) //list any internal-only groups here
}
def userAccessor = ComponentLocator.getComponent(UserAccessor)

DefaultWebInterfaceContext context
def page
if (this.binding.variables.context) {
    context = context
    page = context?.contextParameters?.page
    if (userAccessor.getGroupNames(context.currentUser).any(otherGroupsTest)) {
        return false; //Don't show the banner to external users; they know what they can see. :)
    }
}
else {
    //This block was entirely for debugging purposes, so I could run the script via the console
    //In an actual fragment, the context variable would always be defined
    context = new DefaultWebInterfaceContext()
    def pageManager = ComponentLocator.getComponent(PageManager)
    page = pageManager.getPage(98312)
}

if (!page) {
    return false
}

Closure checkPermissionSets = { ContentPermissionSet permissionSet ->
    boolean anyGroups = permissionSet.getGroupNames().any(otherGroupsTest)
    boolean anyUsers = permissionSet.getUserKeys().any { userKey ->
        userAccessor.getGroups(userAccessor.getUserByKey(userKey)).any(otherGroupsTest)
    }
    anyGroups || anyUsers
}

def contentPermissionsManager = ComponentLocator.getComponent(ContentPermissionManager)
def pageLevelRestrictions = contentPermissionsManager.getContentPermissionSets(page, ContentPermission.VIEW_PERMISSION)
if (!pageLevelRestrictions.isEmpty()) {
    def anyPageLevelRestrictions = pageLevelRestrictions.any(checkPermissionSets)
    return anyPageLevelRestrictions //Since there are view-level restrictions, they should win out over any space perms
}

def spacePermissionManager = ComponentLocator.getComponent(SpacePermissionManager)

def spaceGroups = spacePermissionManager.getGroupsForPermissionType(SpacePermission.VIEWSPACE_PERMISSION, page.space)
def anyGroupsExternal = spaceGroups?.keySet()?.any(otherGroupsTest)

def users = spacePermissionManager.getUsersForPermissionType(SpacePermission.VIEWSPACE_PERMISSION, page.space)
def anyUsersExternal = users?.keySet()?.any{ userName ->
    userAccessor.getGroupNamesForUserName(userName).any(otherGroupsTest)
}

anyUsersExternal || anyGroupsExternal
Joshua DeClerck July 19, 2016

Thanks for so much effort! I did run into one place where it seems to break at line 50. A static type checking error is returned by SR when it tries to use getContentPermissionSets(). Here's a quick grab:

methoderror.png

That trickles down into errors for most of what follows. I can't personally determine the problem, since everything looks fine as far as the documentation for my version of Confluence (5.9.3) seems to suggest.

JamieA
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
July 19, 2016

What confluence version are you using?

0 votes
Joshua DeClerck July 18, 2016

Personally I would prefer the original approach still. I'm curious about the setup you're all using. We have a long wishlist of security-related changes people want to see out here. Since you can never prevent all possible brain-farts, I like the idea of approaching this stuff by improving the visibility of a setting rather than implementing complex restrictions and processes.

And honestly, Confluence page security isn't the most intuitive thing in the world for most users.

If you have an outline in your head for the labelling/panel approach, I'd appreciate the assist. I'd prefer a preventative solution (like this) before a reactive one (reports).

0 votes
JamieA
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
July 18, 2016

It's a shame about the labels/panels, because I think that is a very good use case. IMHO people should know it's public at the time of editing, not have to run a report. We use something similar to the original idea in our JIRA instance, and it works well.

In terms of SR I would do it as a report that you run in Script Console... it's exactly in the sweet spot for SR. The database route might be tricky when dealing with inherited perms. Also db changes are harder to cope with than API changes.

Let us know how you go, we are happy to do an outline of the script if you choose that way.

0 votes
Joshua DeClerck July 15, 2016

After fleshing this out more, people lost interest in using panels or labels. The wish now is for some kind of easy-to-review report about what each user account is able to see (without having to SU one account at a time). That's probably well beyond the design of ScriptRunner here, or at least too much work compared to just directly querying the database. :/

0 votes
JamieA
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
July 8, 2016

sure... I would not use a label though. A label would be nice because it would mean you could find these pages quickly, but that would involve listening for all permissions events, then potentially doing large bulk changes. Instead I would go for a web panel that shows the message... at least for v1.

0 votes
Joshua DeClerck July 8, 2016

Okay, since I don't feel like this is hopeless to try, I'm going to take some time and see if I can do it before I spend up yours. =) If I can't pull it off in a day or two, I'll come back for more help. If I succeed, I'll do the honorable thing and post my solution, then just close this out. XD

0 votes
Joshua DeClerck July 8, 2016

...both...combined...probably. An unrestricted page isn't necessarily customer-visible, if the space itself doesn't have any customers on its access list. I guess it would step through it: (1) if the space grants access to 'external-users', then (2) if the page doesn't block them from viewing it.

A lot of leads expect to have space admin rights, so I'm working with the assumption that this will have to deal with the fact that any random lead might add an external user individually or as a group to their space access list.

If a report is the easiest way to do it, I wouldn't be averse to a report-per-space. Using labels just struck me as the more versatile approach, since it makes tagged pages query-able.

0 votes
JamieA
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
July 8, 2016

OK the way I would do this is examine the VIEW_SPACE perms, and then remove any users/groups that are not included in page restrictions, if there are any (inherited or otherwise).

From that list, if there are any groups there other than internal-users it's publicly visible. If there are any users with view perms, that are not in the internal group, it's publicly visible. Otherwise, it's not.

Does that logic make sense? If so, we can help you write that.

0 votes
JamieA
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
July 8, 2016

Are you interested in the space permissions, or page permissions, or both combined? I take it you want the info on the current page, and about the current page, rather than some kind of report?

You can ignore the first part, I re-read your question and I think you want both.

0 votes
Joshua DeClerck July 8, 2016

Probably not opposed to having an 'external-users' group. It's been talked about before, but JQL has supported the 'not in membersOf()' method without issue, so nothing really made it happen.

On the other hand, we're adopting Crowd soon, and every internal account will be switched over to an LDAP directory, while external accounts won't be. So that whole grouping thing might suddenly be a lot simpler.

0 votes
JamieA
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
July 8, 2016

At first glance I would say that's feasible... I think the issue might be checking the permissions for "any user not in your group". If it was a case of checking whether "external group" had access that would be easy, but any user not in a group... off the top of my head I'm not sure if that can be done in an efficient way. The mechanics of how you display it is probably not a problem, just see what works best.

I'll get back to you on Sunday or Monday...

Suggest an answer

Log in or Sign up to answer
TAGS
AUG Leaders

Atlassian Community Events