
ABAP Unit - TDF (Authority check)
Can you actually mock an authorization check? In this article, we'll look at the test double for authority checks.
Table of contents
In this article, we will create a new authorization object (ABAP Cloud), implement a method with an authorization check, and then mock the actual check for testing.
Introduction
Authorization checks are standard in ABAP development and are intended to ensure that not everyone has access to certain functionalities in the system. However, until now, this topic has not been well covered in ABAP unit tests, and testing various authorization checks has not always been easy. There is now a test duplicate for mocking the exam.
Preparation
Before we can begin the test, we need to do some preparatory work, which is described in this chapter.
Authorization Object
In the first step, we create a new authorization object. With ABAP Cloud, you can do this via the ABAP Development Tools by searching the objects.
As before, the name length is limited to 10 characters, so unfortunately we still can't work with descriptive names here. We'll name the object ZBS_TDFCHK.
After creation, we receive an input mask for the new authorization object. In the upper part, the object for Cloud Development is selected. The activity (ACTVT) has been automatically supplemented with some characteristics. Here, however, we only need 02 and 03 as variants for our test.
Class
Then we define a class that contains a method with the authorization check. So that we can test different activities, we create constants. The method performs an authorization check and returns a Boolean value for the result.
CLASS zcl_bs_demo_tdf_auth DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES auth_activity TYPE c LENGTH 2.
CONSTANTS: BEGIN OF activities,
change TYPE auth_activity VALUE '02',
display TYPE auth_activity VALUE '03',
END OF activities.
METHODS has_authority
IMPORTING activity TYPE auth_activity
RETURNING VALUE(result) TYPE abap_boolean.
ENDCLASS.
CLASS zcl_bs_demo_tdf_auth IMPLEMENTATION.
METHOD has_authority.
AUTHORITY-CHECK OBJECT 'ZBS_TDFCHK'
ID 'ACTVT' FIELD activity.
RETURN xsdbool( sy-subrc = 0 ).
ENDMETHOD.
ENDCLASS.
Assignment
As the final step in the preparation, we need to assign permissions to our user. Currently, the framework can only restrict permissions; we cannot grant ourselves new permissions. This is therefore an important point for testing and use. In a cloud system, we assign the permission object via an IAM app and a business catalog. On-premises, you extend a PFCG role or create a new one and assign it to yourself.
Test Double
The basics are now in place in the system, so we'll begin by implementing the test class and setting up the test double.
Basic Structure
In the first step, we create a basic configuration and validate that we have all the permissions by default. Since we've just created the object, it shouldn't exist in any role yet, but we assigned it to ourselves with the last step. We'll create our test instance CUT for each unit test using the Setup method. More details on the flow and the basics can be found in this article.
CLASS ltc_authority DEFINITION FINAL
FOR TESTING RISK LEVEL HARMLESS DURATION SHORT.
PRIVATE SECTION.
DATA cut TYPE REF TO zcl_bs_demo_tdf_auth.
METHODS setup.
METHODS change_auth FOR TESTING RAISING cx_static_check.
METHODS display_auth FOR TESTING RAISING cx_static_check.
ENDCLASS.
CLASS ltc_authority IMPLEMENTATION.
METHOD setup.
cut = NEW #( ).
ENDMETHOD.
METHOD change_auth.
DATA(result) = cut->has_authority( cut->activities-change ).
cl_abap_unit_assert=>assert_true( result ).
ENDMETHOD.
METHOD display_auth.
DATA(result) = cut->has_authority( cut->activities-display ).
cl_abap_unit_assert=>assert_true( result ).
ENDMETHOD.
ENDCLASS.
Setting Up the Double
In the next step, we want to set up and use the double. To do this, we use the CL_AUNIT_AUTHORITY_CHECK class to create a controller with which we can control our unit test. The class contains various methods with which we can restrict permissions or, for example, define expectations.
To do this, we create a controller in CLASS_SETUP to make it available for all unit tests.
METHOD class_setup.
auth_controller = cl_aunit_authority_check=>get_controller( ).
ENDMETHOD.
To ensure that the controller does not affect tests with real permissions, we reset the controller after each unit test using the teardown method.
METHOD teardown.
auth_controller->reset( ).
ENDMETHOD.
If we now want to restrict the permissions, we first define the new permissions. We create an object set for this internal table using the CREATE_AUTH_OBJECT_SET method. Finally, we pass the set to the controller to restrict the permissions.
METHOD set_only_display_auth.
DATA(auth_display) = VALUE cl_aunit_auth_check_types_def=>role_auth_objects(
( object = 'ZBS_TDFCHK'
authorizations = VALUE #( ( VALUE #( ( fieldname = 'ACTVT'
fieldvalues = VALUE #( ( lower_value = '03' ) ) ) ) ) ) ) ).
DATA(auth_objectset) = cl_aunit_authority_check=>create_auth_object_set(
VALUE cl_aunit_auth_check_types_def=>user_role_authorizations( ( role_authorizations = auth_display ) ) ).
auth_controller->restrict_authorizations_to( auth_objectset ).
ENDMETHOD.
By RESET, we ensure that the next unit test has full permissions again. To do this, we define additional test methods to check the existing permissions and whether the permissions are still restricted.
Logs
After executing the test, we can have the execution log returned via the controller. We can perform further checks using this object. This is especially useful if the result of the method does not reflect the permission check.
DATA(execution_log) = auth_controller->get_auth_check_execution_log( ).
The log offers us various methods to obtain execution information. For example, using GET_EXECUTION_STATUS, we receive information about the number of checks executed and which ones failed.
If we look at the failed checks, we find the last check that failed for our user. This gives us another opportunity to monitor the tests performed.
Complete Example
Here you can find the complete example of the unit test. You can find the global class in the Preparation chapter; we have not included it again.
CLASS ltc_authority DEFINITION FINAL
FOR TESTING RISK LEVEL HARMLESS DURATION SHORT.
PRIVATE SECTION.
CLASS-DATA auth_controller TYPE REF TO if_aunit_auth_check_controller.
DATA cut TYPE REF TO zcl_bs_demo_tdf_auth.
CLASS-METHODS class_setup.
METHODS setup.
METHODS teardown.
METHODS set_only_display_auth
RAISING cx_abap_auth_check_exception.
METHODS change_auth FOR TESTING RAISING cx_static_check.
METHODS display_auth FOR TESTING RAISING cx_static_check.
METHODS double_without_auth FOR TESTING RAISING cx_static_check.
METHODS double_with_auth FOR TESTING RAISING cx_static_check.
METHODS check_logs FOR TESTING RAISING cx_static_check.
ENDCLASS.
CLASS ltc_authority IMPLEMENTATION.
METHOD class_setup.
auth_controller = cl_aunit_authority_check=>get_controller( ).
ENDMETHOD.
METHOD setup.
cut = NEW #( ).
ENDMETHOD.
METHOD teardown.
auth_controller->reset( ).
ENDMETHOD.
METHOD change_auth.
DATA(result) = cut->has_authority( cut->activities-change ).
cl_abap_unit_assert=>assert_true( result ).
ENDMETHOD.
METHOD display_auth.
DATA(result) = cut->has_authority( cut->activities-display ).
cl_abap_unit_assert=>assert_true( result ).
ENDMETHOD.
METHOD double_without_auth.
set_only_display_auth( ).
DATA(result) = cut->has_authority( cut->activities-change ).
cl_abap_unit_assert=>assert_false( result ).
ENDMETHOD.
METHOD double_with_auth.
set_only_display_auth( ).
DATA(result) = cut->has_authority( cut->activities-display ).
cl_abap_unit_assert=>assert_true( result ).
ENDMETHOD.
METHOD check_logs.
set_only_display_auth( ).
DATA(result) = cut->has_authority( cut->activities-change ).
DATA(execution_log) = auth_controller->get_auth_check_execution_log( ).
execution_log->get_execution_status( IMPORTING failed_execution = DATA(failed_execution) ).
cl_abap_unit_assert=>assert_false( result ).
cl_abap_unit_assert=>assert_equals( exp = 1
act = lines( failed_execution ) ).
ENDMETHOD.
METHOD set_only_display_auth.
DATA(auth_display) = VALUE cl_aunit_auth_check_types_def=>role_auth_objects(
( object = 'ZBS_TDFCHK'
authorizations = VALUE #( ( VALUE #( ( fieldname = 'ACTVT'
fieldvalues = VALUE #( ( lower_value = '03' ) ) ) ) ) ) ) ).
DATA(auth_objectset) = cl_aunit_authority_check=>create_auth_object_set(
VALUE cl_aunit_auth_check_types_def=>user_role_authorizations( ( role_authorizations = auth_display ) ) ).
auth_controller->restrict_authorizations_to( auth_objectset ).
ENDMETHOD.
ENDCLASS.
Conclusion
Using the double for authorization checks, we can simulate missing authorizations and thus better test the behavior of our logic. For security reasons, no additional authorizations can be granted, although this also makes sense.
Source:
Release - ABAP Feature Matrix
SAP Help - Managing Dependencies on ABAP Authority Checks