Self-service Authorization for Admin Rest APIs in WSO2 Identity Server
Intercepting Filter and Decorator Patterns to the Rescue
Introduction
WSO2 Identity Server supports two kinds of Rest APIs.
- Admin Rest APIs
- Self-service Rest APIs
The intention of Admin Rest APIs is to authorize users/systems that have the required level of permission under the Mandatory Access Control (MAC) model, or OAuth 2.0 scope under the delegated access control model, to invoke these Rest APIs. These Rest APIs generally operate on system configuration data or identity data of a different user/system than the one calling the Rest API. In WSO2 Identity Server, any Rest API URL that does not contain “/Me” in its path is an Admin Rest API. E.g. SCIM 2.0 Users API [1].
On the other hand the intension of Self-service Rest APIs is to authorize users/systems to invoke these Rest APIs to operate on their own identity data. Generally the authorization logic in this case is straightforward; if the authenticated user/system is the same as the user/system whose identity data on which the operation is being invoked, then the access is granted. This is called“self-service authorization”. In WSO2 Identity Server, any URL that contains “/Me” in its path is a self-service Rest API. E.g. SCIM 2.0 Me API [2].
However, when designing your identity solutions with WSO2 Identity Server, you can come across instances where the set of self-service Rest APIs that are provided out-of-the-box in WSO2 Identity Server, may not be sufficient to complete your design and therefore you may be contemplating on implementing your own custom Rest APIs.
While you are contemplating on this, you may also find that there is a corresponding Admin Rest API, which fulfils the same function as you need, except that the built-in authorization cannot be satisfied because, you are trying to invoke the particular API with self-service authorization, where as the built-in authorization model checks for permissions or OAuth 2.0 scopes. Unfortunately almost all the time the end-users who are consuming self-service Rest APIs do not possess the necessary administrative permissions to invoke their Admin counterpart and therefore the authorization would fail.
Developing custom Rest APIs incur unnecessary overhead in terms of maintaining the source code for the customization, writing integration test cases, patching the source code for security vulnerabilities uncovered, fixing compatibility issues during upgrades, etc. In addition if you find a corresponding Admin Rest API for the same function, you may find yourself end up duplicating the entire business logic of the Admin Rest API into your custom Rest API.
Given the drawbacks of developing a custom Rest API, and benefits of being able to reuse the Admin Rest API for the purpose of self-service, we are motivated to explore options to reuse the Admin Rest APIs for the purpose of self-service too. You can’t help but wonder,
“Only if I can override the authorization logic for Admin Rest APIs?”.
Intercepting Filter Pattern to the Rescue
The WSO2 Identity Server uses two separate Tomcat valve implementation to authenticate and authorize all of its Rest APIs. This API security implementation follows the “intercepting filter pattern”. The advantage of this pattern is that authentication and authorization of Rest APIs are handled at a centralized layer for all Rest APIs, and therefore any changes that are required are confined to a single logic which makes customization of authentication or authorization much more easier.
Sample Epic: Self-service Access Request
In order to understand this problem and its requirements better, let’s look at a sample epic. The sample epic we will take is self-service access requests. As you may already know, WSO2 Identity Server doesn’t support self-service access requests out-of-the-box. However, it possesses a powerful authorization framework that can do coarse-grained authorization for applications and a very extensible workflow management capability, and by fusing the two, WSO2 Identity Server can be made to support the backend functionality for self-service access requests. You can read more about this solution in “How-To: Access Requests & Approvals in WSO2 Identity Server” [3].
Now let’s break down the epic that we took into two user stories.
- List the applications (roles) that are eligible for self-service access requests to an end-user
- Request access to one of the eligible applications (roles) by the end-user
User Story 1: List the Applications (Roles)
Requirement
To list the applications (using roles with a naming convention) that are eligible for self-service access request to an end-user, we will utilize the GET /Roles API. By default, the GET /Roles API is protected and requires the authenticating client to have the permission “/permission/admin/manage/identity/rolemgt/view” or have delegated access with an “internal_role_mgt_view” OAuth 2.0 scoped access token.
Solution
However, in order to overcome this particular roadblock we can resort to a series of simpler solutions.
- Listing roles is a less sensitive operation compared to creating, updating or deleting roles because, it doesn’t change the state in the server or reveal any sensitive information about any user or the server itself. Therefore we could simply remove the authorization check from the GET /Roles API [4].
- Going one step further, you could engage a XACML 3.0 policy to authorize only the particular client (client_id in the case of OAuth 2.0 client or username in the case of BasicAuth client) to invoke GET /Roles API in addition to the clients who have the required permission or delegated access.
- Going another step further, you could also host a proxy service which could filter the roles returned by the GET /Roles API, and only pass on the roles eligible for self-service requests to the client.
User Story 2: Request Access to an Application (Role)
Requirement
To request access to one of the eligible applications (using roles with a naming convention) by the end-user, we will utilize the PATCH /Roles API. By default, the PATCH /Roles API is protected and requires the authenticating client to have the permission “/permission/admin/manage/identity/rolemgt/update” or have delegated access with an “internal_role_mgt_update” OAuth 2.0 scoped access token.
Solution
We cannot simply remove the authorization check completely from the PATCH /Roles API as we did with the GET /Roles API, since the PATCH /Roles API is a common Rest API that can be used to manipulate all administrative roles, not just the roles eligible for self-service access requests. Therefore the better solution here would be to override the authorization logic as follows:
- Apply default permission-based authorization rules.
- If rule #1 doesn’t permit, check whether the API being invoked is PATCH /Roles.
- If condition #2 passes, check whether the PATCH /Roles API is being invoked on a role eligible for self-service access requests.
- If condition #3 passes, check whether the authenticated client is the same as the user/system whose identity on which the API is being invoked.
Decorator Pattern to the Rescue
In order to check condition #4 above, you will need to consume the HTTP request body in which you can find the user/system whose data on which the operation is being invoked.
Consuming the HTTP request body inside a valve is not straightforward task. Therefore we will utilize the prescribed “decorator” pattern in order to consume the HTTP request body from within a valve [5].
You can find the full implementation of self-service authorization in WSO2 Identity Server here [6].
References
[1] https://is.docs.wso2.com/en/latest/develop/scim2-rest-apis/#/Users%20Endpoint
[2] https://is.docs.wso2.com/en/latest/develop/scim2-rest-apis/#/Me%20Endpoint
[4] https://is.docs.wso2.com/en/latest/develop/authenticating-and-authorizing-rest-apis/
[5] https://howtodoinjava.com/java/servlets/httpservletrequestwrapper-example-read-request-body/
[6] https://github.com/johannnallathamby/wso2-is-authz-rest-selfservice