Demystify SAP SuccessFactors Role Based Permission APIs

SAP SuccessFactors offers a comprehensive set of Role Based Permission (RBP) APIs based on OData V2. Those APIs can be used in several scenarios, the most common one is knowing the permissions of a logged-on End User when building an application/extension to SAP SuccessFactors. I will refer to this extension through out this blog post as the App and to the regular employee without any special administrator permission accessing the App as the End User. When talking about system-to-system communication, e.g. for data replication where no end user is involved, we refer to the Technical User calling the APIs.

While most APIs have permission checks build in, i.e. to return only data the corresponding end user is allowed to access. There are three major extension use cases where this is simply not enough and where more information about the permission of an end user is required. This can be achieved by using the RBP APIs.

Use Case 1: Data was replicated from SuccessFactors to a 3rd party platform to have fast access to it from the App. Data is either stored on a database or in memory. For transfer a technical user has been used. From a security point of view this data should not be stored in the client (browser) but in the backend of the App not visible/directly accessible to the end user.

Use Case 2: Data not present in SuccessFactors is created and stored only in the App and should follow existing permission settings in SAP SuccessFactors. E.g. the app stores values for blood group and other medical data and the permission handling for this should follow the permission handling of emergency contacts in SuccessFactors, i.e. if a end-user A can access the emergency contacts in Employee Central he should also be allowed to access the blood group and other medial data in the App.

Use Case 3: The App is processing data from SAP SuccessFactor but needs for a better user experience information about the permission. Examples of this can be that a navigation link to additional data should only be offered if permission is present (instead of giving an error when being clicked) or fields should only be editable if the end-user has the edit permissions (instead of giving an error when trying to save). As mentioned, those cases would require to try the edit/navigation and fail if checking the permission beforehand is not possible.

Salesdemo example data used in this blog

Our team, the Product Advisory and Partner Success team, manages partners building integrations and extensions to SAP SuccessFactors. Especially when partners are doing development and POCs this puts sometimes some extra load on the systems which can be in extreme cases a thread to the stability of those systems. Hence, I like to do a shoutout to Mel Christie and his team who does not only keep those systems stable but also provide us with this standardized example data. Without this data it would not be possible for me to produce this API example collection which can be executed by our partners and customers. Huge efficiency booster!

By the way, all this nice example data is also used in many example script you can find in our Demo Database. As a SAP partner you can get your own tenant with the same example data by following the guide available here.

Browsing the Permission Model in the UI

In the example below I will use Carla Grant and her Manager Reilly Francis. Carla is allowed to see Reilly’s Personal Information such as First and Last Name and also some of his Job Information fields such as his manager and Job Title, but she is not allowed to see any Compensation Information of Reilly. She can only see her own compensation information, e.g. her current salary.

In the SAP SuccessFactors Admin Center there are two pretty helpful tools to analyse the permission settings one is the “View User Permissions” and the other one “Manage Permission Roles”.

If you look at the user permissions of Carla Grant we will see exactly the behaviour of the UI we mentioned above. For example the reason why Carla can see the first and last name of her manager

This permission is given through the Role All Employee Search Login (New). If we check this role in the Manage Permission Roles UI we can see that this permission is granted to all Employees.

If we check further and click on the link, we will see what we call the target population for this role. This defines what employees the person who got this permission granted is allowed to access:

We can also see that the target population is unlimited (Everyone). As a result, Carla Grant can access all the fields or features granted through this role for all employees in the company.

When we check this for the current salary we figure out that this is permission is granted through several roles:

If we look into the Employee Self Service role we can see that this permission is granted for view only (including history).

The target population is here only Carla herself. As a result, she does not have any access to the salary of others, except her direct reports which are granted to her through the other MSS roles. I didn’t provide screenshots for this but you can check this in your own demo tenant.

When looking into the details here, wee see that there are different levels of permissions. Field level permissions including access type (view, view history, edit, delete), but also section level (compensation view, see below) or even navigation or module level (e.g. Learning Access Permission)

This allows us to let people even not reach certain modules or views within a module, like the compensation view. The above permission allows Carla grant to see her Compensation View but she does not have this permission for any other employee, except for her direct reports.

Now that we have learned about the permission settings for Carla Grant let’s check them using our RBP APIs. Assuming that you have an unchanged demo tenant all the checks should work for you the same way.

Using SAP SuccessFactors RBP APIs

Often it is difficult to map what we see in the UI to what we see in the corresponding APIs since text and labels might not be present and technical API field names are difficult to map to what we know from the UI. SAP SuccessFactors has a powerful APIs to help with this mapping. It’s an OData Function import called getPermissionMetadata mentioned here.

GET https://apisalesdemo4.successfactors.com/odata/v2/getPermissionMetadata?locale=en-US

The output of this API helps to map the UI text in the given locale of the URL parameter (en-US) to what we call the permission type and the permission string value. Below code is a part of the overall response of this API. The response was reduced to a single permission, formatted in a nicer way and stripped by some special characters:

field-permission field-id id="payrollIntegration" message content language="en-US"![CDATA[Compensation]]/content /message /field-id field-action id="view" message key="ADMIN_RBP_READ_ONLY" content language="en-US"![CDATA[View]]/content /message /field-action permission-item perm-type="EmployeeFilesViews_type" permission-long-value="-1" permission-string-value="$_payrollIntegration_view" message key="ADMIN_RBP_READ_ONLY" content language="en-US"![CDATA[View]]/content /message /permission-item
/field-permission

We use the above snippet to map the Compensation view permission from the RBP UI to the permission type EmployeeFilesViews_type and the permission string value $_payrollIntegration_view.

Those two values will help us to check if Carla Grant has the permission to see the Compensation view in the Employee Profile of her own user and this of her manager Reilly. The API requests and responses below contain codes such as %27 which are the http ascii encoding for a quote symbol.

GET https://apisalesdemo4.successfactors.com/odata/v2/checkUserPermission ?accessUserId=%27cgrante%27 &permType=%27EmployeeFilesViews_type%27&permStringValue=%27$_payrollIntegration_view%27 &targetUserId=%27cgrante%27 &permLongValue=-1L

The result of this request to access her own compensation data is as expected “true”:

<?xml version="1.0" encoding="utf-8"?>
<d:checkUserPermission xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
m:type="Edm.Boolean">true</d:checkUserPermission>

Note, that we have used Carla’s userId for both the accessing user and the target user being accessed.

If we check the same permission for her manager Reilly (userId 108726) we get as expected “false”:

GET https://apisalesdemo4.successfactors.com/odata/v2/checkUserPermission ?accessUserId=%27cgrante%27&permType=%27EmployeeFilesViews_type%27 &permStringValue=%27$_payrollIntegration_view%27 &targetUserId=%27108726%27 &permLongValue=-1L <?xml version="1.0" encoding="utf-8"?>
<d:checkUserPermission xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" m:type="Edm.Boolean">false</d:checkUserPermission>

When I started using the APIs to retrieve this data (Compensation and Job Information), I recognized that in the salesdemo environment Carla Grant has the full permission to read all the data in the tenant using APIs. This permission is given by the MSS end Employee Self Service roles. After revoking those permissions (see also table below about security best practices) the API returned the same data from a permission point of view as the UI, i.e. respecting field-level permissions by removing fields from the response and respecting target population by removing the corresponding records. In case you want to see the same behaviour on the APIs you have to remove those permissions as well. If you don’t remove those permissions you will see true and false for both requests above but when querying the Compensation data for Carla’s manager Reilly you would still get the results from the API.

So, when removing those admin permissions, calls to the OData Entity EmpJob have less fields when being called with Carla Grant as the API user compared to a powerful technical user with all fields accessible. At the same time the API call to EmpCompensation for her manager Reilly (userId is 108726) will not return any records.

GET https://apisalesdemo4.successfactors.com/odata/v2/EmpCompensation ?$filter=userId+eq+%27108726%27 &$format=JSON { "d": { "results": [] }
}

In a similar way we can check for other permissions, e.g. Current Salary access, since this is a field level permission in the data model the corresponding metadata from RPB contains also the field-id attribute which can be used again to understand which field is meant. This field-id attribute maps with the field names in the Manage Business Configuration UI. In our example the current salary maps to field-id custom-double5.

The example shows the corresponding RBP metadata for the field Current Salary:

field-permission field-id id="compInfo_custom-double5" message key="EMPFILE_HRIS_CUSTOM_DOUBLE5" content language="en-US"![CDATA[Current Salary]]/content /message /field-id field-action id="read" message key="ADMIN_RBP_VIEW_CURRENT" content language="en-US"![CDATA[View Current]]/content /message /field-action permission-item perm-type="DATA_MODEL" permission-long-value="-1" permission-string- value="$_compInfo_custom-double5_read" message key="ADMIN_RBP_VIEW_CURRENT" content language="en-US"![CDATA[View Current]]/content /message /permission-item
/field-permission

In order to avoid that each individual permission check has to be sent to the system using a single function import I recommend bundling those in a $batch request. Since those operations are quite expensive, Apps should limit their permission model to a minimum number of checks and avoid bundling more than 10 per user interaction. Otherwise, the UI might get less responsive. Sometimes it might even make sense to check not the assignment of a permission to a user but maybe the assignment of a whole permission role instead. E.g. only users who have assigned the MSS role are allowed to start the App. A $batch request to bundle the check for First Name and the Current Salary would look like this:

POST https://apisalesdemo4.successfactors.com/odata/v2/$batch
Content-Type: multipart/mixed; boundary=batch_36522ad7-fc75-4b56-8c71-56071383e77b --batch_36522ad7-fc75-4b56-8c71-56071383e77b
Content-Type: application/http
Content-Transfer-Encoding: binary GET checkUserPermission?accessUserId='{{rbp_login_user}}'&permType='DATA_MODEL'&permStringValue='$_firstName_read'&targetUserId='{{rbp_target_user}}'&permLongValue=-1L&$format=JSON HTTP/1.1
Content-Type: application/json;type=entry --batch_36522ad7-fc75-4b56-8c71-56071383e77b
Content-Type: application/http
Content-Transfer-Encoding: binary GET checkUserPermission?accessUserId='{{rbp_login_user}}'&permType='DATA_MODEL'&permStringValue='$_compInfo_custom-double5_read'&targetUserId='{{rbp_target_user}}'&permLongValue=-1L&$format=JSON HTTP/1.1
Content-Type: application/json;type=entry --batch_36522ad7-fc75-4b56-8c71-56071383e77b--

Alternative Approaches

Using the SuccessFactors RBP API is just one approach to introduce additional permission checks but in many cases not the only and in some even not the right one.

Below is a list of different approaches with their pros and cons to help deciding on the right one. It should be clear that also hybrid approaches are possible combining multiple or all of the below approaches in one App. Different areas of the App might make use of different approaches. The build-in permission checks of the API itself, e.g. returning only records where the end user calling the API has access to, is not mentioned in the table since this has always to be used, otherwise the security of the system is compromised.

Approach Pro Con Comment
Implement an own permission model in your platform, e.g. BTP

Maximum flexibility of implementation of permission model

Better performance

Double maintenance of permission required in SuccessFactors and in App Platform (e.g. BTP) Should be used if app stores data on its own  platform outside of SuccessFactors which needs a permission model too.
Use the permission control information in some of the OData APIs One single and simple API call for data and permissions. Only supported by a few SuccessFactors entities such as User, Goal, JobRequisition, JobOffer, etc. Usually implemented by a navigation giving information about the permission of the user calling the API for this data, e.g. read, write permission per field, also called field level permission control. Should be used where available instead of calling RBP APIs.
Use the RBP APIs to do derive permission checks for you App based on SuccessFactors permission settings

No need for additional permissions to be managed on App platform

Performance limitations

Additional API calls required

More complex API orchestration, potentially also technical users required in backend

Function import checkUserPermission not supported for MDF objects

Achieved by calling API checkUserPermission but also possible by parsing RBP Roles and target populations to calculate permissions results.

Should only be used when other options are not a fit.

Security best practices

End-users who are used to access the App should never have any of those permissions, this list is not complete and list just the most critical permissions in the platform and Employee Central. Of cause there are other module or platform related permissions, like creating Integration Center flows, oAuth client registrations creation, Managing Permissions which should be avoided but are more obvious:

Section Permission Comment
Manage User Employee Export
Manage User Read Access to SCIM User API
Manage User Edit Access to SCIM User API
Employee Central API Employee Central HRIS OData API (read-only)
Employee Central API Employee Central HRIS OData API (editable)
Employee Central API Employee Central Foundation Data (read-only)
Employee Central API Employee Central Foundation Data (editable)
Manage Integration Tools OData API Attachment Import
Manage Integration Tools OData API Attachment Export
Manage Integration Tools OData API Competency Rating Import
Manage Integration Tools OData API Competency Rating Export
Manage Integration Tools OData API To-Do Export
Metadata Framework Admin access to MDF OData API
Metadata Framework Access to non-secured objects MDF Objects should always get secured if needed for APIs, even if the access permission is just as basic as on/off.
Goals Admin Access for Goal ODATA API Export
Manage Calibration

Recruiting Permissions

OData API Candidate Export

Manage Documents

Admin Access to Forms OData AP

Manage Documents

Admin Access to Talent Rating OData API

Even though some of the mentioned APIs for RBP can be used with the end users authentication and even from the browser I would always recommend to hide those permission related API calls in the backend and avoid exposing them with their data on the client.

In some cases RBP APIs have to be called where the end-users would need administrator permission to call them, e.g. getPermissionMetadata, in such a case there has to be anyhow another technical user behind the scenes in the backend to make use of those APIs without exposing their result or authentication to the end-users UI/client. Giving those RBP permissions to the end-users of the App is not an option and would be a security issue.

Common pitfalls and other important thing to know

I mentioned already that our Employee Central OData APIs support RBP checks. They do so by filtering out records a user does not have access too. E.g. if an App access data on behalf of the end-user all records of Job Information in the system, the API would only return those, the end-user has the right to see. Fields the end user is not allowed to see or records he should not see will get filtered out. As a result, an OData API request of the corresponding entity might not have a full page, e.g. even if 100 records have been requested, the result set might be smaller, e.g. 100 minus the ones filter out.

Conclusion

All the three mentioned use cases from the beginning benefit from the capability of checking the users permissions using the checkUserPermission OData function import . Understanding the mapping between the permission labels in the UI and the permissions types and string values returned by the OData API getPermissionMetadata help to fill the right values into this permission check API.

The Employee Central and MDF OData APIs filter already the data based on the permissions settings of the end user used to call the API. Additional permission checks are often needed based on the nature of the use cases mentioned. Multiple checks can be bundled in a $batch call to improve round trip time.

But there are also alternative approaches of using checkUserPermissions, like implementing an own permission model in the platform of the App or using the permission control information.

It’s a good security best practice to check for and avoid admin permission settings for end users as listed above. In addition, as the developer of the App you should hide all security relevant checks in the backend especially if technical users have to be used to call the RBP APIs.