Introduction
This blog will show how to check the permissions of users making REST calls by using annotation. These changes were made to our Forms for Confluence plugin.
Problem
In the past we were only concerned with checking that the user making the call was indeed a global admin. Previously only the global admin had access to the Forms for Confluence admin section. Recently we implemented a new feature which required us to change the way permissions are checked. This is because we now also have a space level admin section which is relevant only to forms created in that space. This means that we have to first establish if the call is being made from the global admin level or space level, and then checking the permissions of the user.
Solution
This is part of the original rest endpoint:
@GET
@Path("/emails/{id}")
public Response getEmail(@PathParam("id") Long id,
@QueryParam("fullyLoaded") @DefaultValue("true") boolean fullyLoaded) {
return safeCallAsAdmin(id, fullyLoaded, new RestCallerTwo<Long, Boolean>() {
@Override
public Response call(Long id, Boolean fullyLoaded) {
return entityResponse(inboxManager.findById(id, fullyLoaded));
}
});
}
We took the opportunity to delegate the responsibility of authentication away from the receiver, because this would improve code legibility, reuse and maintenance.
What we ended up with is the following:
@GET
@Path("/emails/{id}")
@ResourceFilters(SpaceOrConfluenceAdminResourceFilter.class)
public Response getEmail(@PathParam("id") Long id,
@QueryParam("fullyLoaded") @DefaultValue("true") boolean fullyLoaded,
@QueryParam("spaceKey") String spaceKey) {
return entityResponse(inboxManager.findById(id, fullyLoaded));
}
Notice the annotation @ResourceFilters. The argument is the class we created which will be responsible for checking permissions.
Here is a snippet of the SpaceOrConfluenceAdminResourceFilter class:
@Override
public ContainerRequest filter(ContainerRequest containerRequest) {
String spaceKey = "";
if (containerRequest.getQueryParameters().get(SPACE_KEY) != null) {
spaceKey = containerRequest.getQueryParameters().get(SPACE_KEY).get(0);
}
if (isAdminOrSpaceAdmin(spaceKey)) {
return containerRequest;
} else {
throw new AuthenticationRequiredException();
}
}
protected boolean isAdminOrSpaceAdmin(final String spaceKey) {
if (spaceKey.isEmpty()) {
return permissionManager.hasPermission(AuthenticatedUserThreadLocal.get(),
Permission.ADMINISTER, PermissionManager.TARGET_SYSTEM);
} else {
return spacePermissionManager.hasPermission(SpacePermission.ADMINISTER_SPACE_PERMISSION,
spaceManager.getSpace(spaceKey), AuthenticatedUserThreadLocal.get());
}
}
We can determine if the user making the originating call is in a space by using the following javascript, and then adding it to the rest call as a QueryParam.
spaceKey = AJS.params.spaceKey;
$http.post(AJS.contextPath() + path + '?' + "spaceKey=" + spaceKey, postData);
If the query parameter passed to the rest endpoint is empty, that means that the call is being made from the global admin section of our pluging. This is because the global administration section of Confluence is not contained within any space. This means that we know we have to check if the user has global permissions.
If the query parameter passed is a space, then we check the space permission privileges for a space administrator. Since a global admin will have space admin privileges for all spaces we do not distinguish global admin from space admin in this instance. However we do need to consider when it is a space admin, if this specific space admin does have space admin privileges for the space in question.
Worth mentioning here is whether to use PathParam or QueryParam to send the space key. I learned it is better to use QueryParam if the parameter is only sent in certain situations. PathParam is useful in situations where the parameter is always sent. In this case the parameter is only sent if the call originates from a space, so a QueryParam is appropriate.
Conclusion
By using the ResourceFilters annotation we have simplified the REST endpoint and made the code easier to understand and modify, by abstracting away the permission checking of the caller. Any new REST endpoint added simply needs to add one line for the user permissions to be checked.
- 09 Oct 2018 » A strange bug on AWS Lambda
- 17 Jan 2018 » How to run Karma tests in browsers in Docker
- 07 Dec 2017 » Switching from Javascript to Typescript
- 30 Oct 2017 » Fun with React event handlers
- 17 Jul 2017 » Switching from Groovy to Java
- 24 May 2017 » Useful Git Aliases
- 27 Mar 2017 » Practical Ratpack Promises
- 03 Nov 2016 » Custom Content in Forms for Confluence Connect
- 04 Oct 2016 » Checking user permissions from REST calls
- 30 Sep 2016 » Using the reflection API in Confluence
- 28 Sep 2016 » Creating a custom Confluence Blueprint
- 06 Sep 2016 » ReactJS in Forms for Confluence Connect
- 25 Apr 2016 » Migrating to ES6 in Atlassian Add-ons
- 17 Mar 2016 » All kinds of things I learnt trying to performance test against Fisheye/Crucible
- 24 Dec 2015 » Adaptavist’s Holiday Gift of Atlassian Deployment Automation
- 17 Dec 2015 » Getting a Custom Field value safely
- 07 Dec 2015 » Putting Google Analytics to work with plugins for Confluence
- 02 Dec 2015 » Devoxx Voting, A retrospective
- 25 Nov 2015 » Some things I've learnt about SingleSelect
- 15 Oct 2015 » Using SOY for JIRA actions
- 26 Sep 2015 » Object Reflection in Groovy
- 22 Sep 2015 » Introducing Adaptavist Labs