In this post, I am going to show how to provide authorization to a SAPUI5 / FIORI application using CDS views and BOPF framework.
Basically, we can provide authorizations for CRUD methods in SAPUI5 using BOPF. Every BOPF object has a authorization class associated to it. The methods of this class are automatically triggered whenever we perform a CRUD call from UI (called automatically by the framework). Therefore, we can leverage this BOPF authorization class for adding any authorizations to our UI5 app. Example of such authorizations can be, role based authorization, user name based authorization, authorization using an authorization object, and many more.
Also Read: SAP ABAP 7.5 Certification Preparation Guide
Authorization using BOPF in UI5 can be divided into two types:
- Row Based authorization (Instance Authority): Here, the authorizations are checked at each row level. Every row/record in the table is considered as an instance, and authorization for any row, based on its data, can be given specifically mentioned.
- Operation Based authorization (Static Authority): Here, the authorizations are provided for any operation (CRUD) as a whole, not for any particular row.
In the above image, if we give authorization depending on the data in the table row, it would be an instance authorization. But if we give authorization for any operation like create, update, delete or any function import, then it would be a static authorization.
Now, let us look into this process of adding authorizations using CDS, BOPF and UI5. In this post, we shall be primarily looking into the Static authorizations. Suppose for example, we have a entity set, and we wish to restrict the CRUD operations functionality to only some users based on user id / roles / authorizations object.
Before writing any code, let us first have a look at our SAPUI5 application and the requirement/scenario. In the below application, we wish to restrict users from performing the create operation based on the user name/ authorization object.
It is a normal UI5 app, we use the OData which is generated from the same CDS views (below). On click of the ‘+’ button, we have to create a new sales order. Let us look at the view code.
<mvc:View controllerName="com.sap.sales.orders.custom.VBA_CUST.controller.listView" xmlns:mvc="sap.ui.core.mvc"
xmlns:smartFilterBar="sap.ui.comp.smartfilterbar" xmlns:core="sap.ui.core" xmlns:smartTable="sap.ui.comp.smarttable" displayBlock="true"
xmlns="sap.m">
<Shell id="shell">
<App id="app">
<pages>
<Page id="page" title="Sales Orders">
<content>
<smartFilterBar:SmartFilterBar id="idSmartFilterBar" persistencyKey="configPortalPersistencyKey" considerSelectionVariants="true"
entitySet="ZAB_C_VBAK">
<!-- layout data used to make the table growing but the filter bar fixed -->
<smartFilterBar:layoutData>
<FlexItemData shrinkFactor="0"/>
</smartFilterBar:layoutData>
</smartFilterBar:SmartFilterBar>
<smartTable:SmartTable id="idHeaderSmartTable" ignoreFromPersonalisation="Accrual_period" entitySet="ZAB_C_VBAK"
smartFilterId="idSmartFilterBar" tableType="ResponsiveTable" useExportToExcel="true" useVariantManagement="true"
useTablePersonalisation="true" header="{i18n>ListTableTitle}" showRowCount="true" persistencyKey="configPortalSmartTablePersistencyKey"
enableAutoBinding="false" class="sapUiResponsiveContentPadding">
<smartTable:customToolbar>
<OverflowToolbar>
<ToolbarSpacer/>
<Title/>
<OverflowToolbarButton tooltip="Create" type="Transparent" icon="sap-icon://add" text="Create" press="onCreate"/>
</OverflowToolbar>
</smartTable:customToolbar>
</smartTable:SmartTable>
</content>
</Page>
</pages>
</App>
</Shell>
</mvc:View>
Also, given below is the code in controller for creating a new sales order (static values given in payload as an example):
onCreate: function (oEvt) {
var oModel = this.getOwnerComponent().getModel();
var oPayload = {
vbeln: "9999",
erdat: "2018-09-27T00:00:00",
ernam: "ARJUN BISWAS",
vkorg: "ab01"
};
oModel.create("/ZAB_C_VBAK", oPayload, {
success: function (oData) {
sap.m.MessageToast.show("Created Successfully");
}.bind(this),
error: function (oErr) {
sap.m.MessageToast.show(JSON.parse(oErr.responseText).error.message.value);
}.bind(this)
});
},
In the error call back function, we have used an JSON.parse method to extract the response received from the back-end. This is just for an example. The standard way to do this is by making use of message manager functionality in SAPUI5.
Now, let us look into the coding in CDS and BOPF to achieve the above scenario.
Firstly, we have to create a CDS view, with object model annotations. The object model annotations are for enabling the crud operations. Once we have give the object model annotation, an BOPF object is generated automatically by the framework, for the CDS. Let’s take a look at the CDS view. Here I have two views, one is a implementation view and another one is a consumption view.
Implementation View, for the table VBAK:
@AbapCatalog.sqlViewName: 'ZAB_I_VBAK_CDS'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Implementation on VBAK'
@ObjectModel.compositionRoot: true
@ObjectModel.writeActivePersistence: 'vbak'
@ObjectModel.semanticKey: ['mandt','vbeln']
@ObjectModel.transactionalProcessingEnabled: true
@ObjectModel.createEnabled: true
@ObjectModel.updateEnabled: true
@ObjectModel.deleteEnabled: true
define view ZAB_I_VBAK
as select from vbak
{
key mandt,
key vbeln,
erdat,
erzet,
ernam,
angdt,
bnddt,
audat,
vbtyp,
trvog,
auart,
augru,
gwldt,
netwr,
waerk,
vkorg,
vtweg,
spart,
guebg,
gueen,
knumv,
vdatu,
vprgr,
kalsm,
vsbed,
ps_psp_pnr,
dat_fzau,
fkara,
awahr,
bstnk,
bstdk,
mahza,
mahdt,
kunnr,
stwae,
aedat,
kokrs,
kurst,
kkber,
knkli,
ctlpc,
cmwae,
cmfre,
cmnup,
cmngv,
amtbl,
agrzr,
abhod,
vzeit,
fmbdat
}
Consumption View, on the above implementation view:
@AbapCatalog.sqlViewName: 'ZAB_C_VBAK_PRJ'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Consumption for VBAK'
@VDM.viewType: #CONSUMPTION
@UI.headerInfo: {
title.value: 'Sales Orders',
description.value: 'Sales Orders Details',
typeName: 'Sales Order',
typeNamePlural: 'Sales Order Items'
}
@OData.publish: true
@Search.searchable: true
@ObjectModel.semanticKey: ['vbeln']
@ObjectModel.transactionalProcessingDelegated: true
@ObjectModel.createEnabled: true
@ObjectModel.updateEnabled: true
@ObjectModel.deleteEnabled: true
define view ZAB_C_VBAK
as select from ZAB_I_VBAK
//association [0..*] to ZAB_C_VBAP as _VBAP on $projection.vbeln = _VBAP.vbeln
{
//key ZAB_I_VBAK.mandt,
@Search.defaultSearchElement: true
@UI.selectionField: {position: 10}
@UI.lineItem: [{criticality: 'HIGH',
iconUrl: 'www.google.com',
position: 10
}]
@UI.identification.position: 10
@UI.dataPoint.title: 'Sales Document Number'
key ZAB_I_VBAK.vbeln,
@UI.lineItem.position: 20
@UI.identification.position: 20
@UI.dataPoint.title: 'Sales Document Date'
@Semantics.systemDate.createdAt: true
ZAB_I_VBAK.erdat,
@UI.lineItem.position: 30
@UI.selectionField: {position: 20}
@UI.identification.position: 30
ZAB_I_VBAK.erzet,
@UI.lineItem.position: 40
@UI.selectionField: {position: 30}
@UI.identification.position: 40
@UI.dataPoint.title: 'Owner'
ZAB_I_VBAK.ernam,
@UI.selectionField: {position: 40}
@UI.identification.position: 50
ZAB_I_VBAK.angdt,
@UI.selectionField: {position: 50}
@UI.identification.position: 60
ZAB_I_VBAK.bnddt,
@UI.selectionField: {position: 60}
ZAB_I_VBAK.audat,
@UI.lineItem.position: 50
@UI.selectionField: {position: 70}
@UI.identification.position: 70
ZAB_I_VBAK.vbtyp,
ZAB_I_VBAK.trvog,
@UI.selectionField: {position: 90}
@UI.lineItem.position: 80
ZAB_I_VBAK.auart,
@UI.selectionField: {position: 100}
ZAB_I_VBAK.augru,
ZAB_I_VBAK.gwldt,
@UI.selectionField: {position: 80}
@UI.identification.position: 80
@UI.lineItem.position: 70
@UI.dataPoint.title: 'Net Value'
//@Semantics.quantity.unitOfMeasure: 'ZAB_I_VBAK.waerk'
ZAB_I_VBAK.netwr as Net_Value,
@UI.identification.position: 80
@Semantics.unitOfMeasure: true
ZAB_I_VBAK.waerk,
@UI.lineItem.position: 60
@UI.selectionField: {position: 110}
ZAB_I_VBAK.vkorg,
@UI.selectionField: {position: 120}
ZAB_I_VBAK.vtweg,
@UI.selectionField: {position: 130}
ZAB_I_VBAK.spart,
@UI.selectionField: {position: 140}
ZAB_I_VBAK.guebg,
@UI.selectionField: {position: 150}
@UI.lineItem.position: 90
ZAB_I_VBAK.gueen,
@UI.selectionField: {position: 160}
ZAB_I_VBAK.knumv,
@UI.selectionField: {position: 170}
ZAB_I_VBAK.vdatu,
@UI.lineItem.position: 100
ZAB_I_VBAK.vprgr,
ZAB_I_VBAK.kalsm,
ZAB_I_VBAK.vsbed,
@UI.selectionField: {position: 180}
ZAB_I_VBAK.ps_psp_pnr,
ZAB_I_VBAK.dat_fzau,
ZAB_I_VBAK.fkara,
ZAB_I_VBAK.awahr,
@UI.selectionField: {position: 190}
ZAB_I_VBAK.bstnk,
@UI.selectionField: {position: 200}
ZAB_I_VBAK.bstdk,
ZAB_I_VBAK.mahza,
@UI.selectionField: {position: 210}
ZAB_I_VBAK.mahdt,
@UI.selectionField: {position: 220}
ZAB_I_VBAK.kunnr,
@UI.selectionField: {position: 230}
ZAB_I_VBAK.stwae,
ZAB_I_VBAK.aedat,
ZAB_I_VBAK.kokrs,
@UI.identification.position: 90
ZAB_I_VBAK.kurst,
@UI.identification.position: 100
ZAB_I_VBAK.kkber,
ZAB_I_VBAK.knkli,
ZAB_I_VBAK.ctlpc,
ZAB_I_VBAK.cmwae,
ZAB_I_VBAK.cmfre,
ZAB_I_VBAK.cmnup,
ZAB_I_VBAK.cmngv,
ZAB_I_VBAK.amtbl,
ZAB_I_VBAK.agrzr,
ZAB_I_VBAK.abhod,
ZAB_I_VBAK.vzeit,
ZAB_I_VBAK.fmbdat
}
Here we can check out all the necessary annotations, the create, update and delete enabled annotations are set as true. The transactionalProcessingDelegated annotation indicates that the transactional access to the consumption view is delegated to the transactional runtime of the underlying view. Due the these object model annotations, our BOPF object is automatically generated.
Let us look into the BOPF object now. We can do it either in eclipse/Hana Studio or in the SAP GUI T-Code BOBX.
The generated BOPF object in Hana Studio:
The generated BOBF object in SAP GUI:
Now, let us get into the authorization part. For each BOPF object generated, the framework also generates a authorization class for the BOPF object. The authorization class is automatically generated when we are using CDS generated BOPF. Through this authorization class, we can handle all kinds of authorizations in our SAPUI5 app. This class contains some predefined methods and some methods of the super class which it extends. Let us take a look, on finding this authorization class.
In case of Hana Studio / Eclipse, open the BOPF object, click on Go to the Root Node:
In the next screen, at the footer, select the last option, Authorization:
In the next screen, we can see the authorization class that has been generated for the BOPF.
In case the authorization class is empty, we can create another class in se24 and enter it here. We can make use of the buttons new and browse to do it.
We can check the coding of this authorization class by using the class name from here in TCode se24.
We can also check the authorization class of our BOPF object in the TCode BOBX:
In BOBX, first select the BOPF object, go to the particular node and click on it, in the right hand panel, under authorization checks section, we can get the authorization class name. Double click on it to go to the class.
Now, let us take a look into the authorization check class and its methods. Our authorization class is ZCL_AU_AB_I_VBAK1. In se24, this class looks like below:
Now, let us look into these methods in detail:
- /BOBF/IF_LIB_AUTH_DRAFT_ACTIVE~CHECK_STATIC_AUTHORITY – As I have mentioned above, this method checks whether the user has permission to perform a specific activity or an operation on data (for example: CREATE). The result of a static check does not depend on specific data values. Do not get misled by the draft in the method name. This method can be used for non-draft cases as well (we can see that in this example). This method gets automatically triggered when we try to perform any CRUD operation from the UI.
- /BOBF/IF_LIB_AUTH_DRAFT_ACTIVE~CHECK_INSTANCE_AUTHORITY – Similarly, this method,evaluates the node data and checks whether the user has permission to display or change data, or perform a specific activity where an authorization-relevant attribute has a specific value. The result of this check depends on the node data.This method also, can be used for both draft and non-draft cases. This method gets triggered when we try to perform operations on any specific row of the table.
- CHECK_AUTHORITY – This is a super class method. It has already been redefined as final in the super class, so it cannot be redefined in this class again. It is used for both static and instance based authority check.
- CHECK_AUTHORITY_STATICALLY – Same as above, this method also, has already been redefined as final in the super class, so it cannot be redefined in this class again. It is used in static authority checks.
- GET_QUERY_CONDITION_PROVIDER – This method also cannot be redefined in this class because of the same reason. It gives the authorization condition provider in order to execute the query based on SADL.
- CONSTRUCTOR – It is the constructor method of this class.
In most of the cases we need to concentrate only on the first two methods for providing authorizations with the SAPUI5 applications.
As, in our scenario, we have to restrict the whole CRUD operation, we can place the below code in the /BOBF/IF_LIB_AUTH_DRAFT_ACTIVE~CHECK_STATIC_AUTHORITY method.
method /BOBF/IF_LIB_AUTH_DRAFT_ACTIVE~CHECK_STATIC_AUTHORITY.
IF sy-uname eq 'ANY_USER_NAME'.
rv_failed = abap_true.
MESSAGE e001(ZARJ_1) INTO DATA(lv_message).
ENDIF.
" Message handling if authorization check failed
IF rv_failed = abap_true AND lv_message IS NOT INITIAL.
CALL METHOD /scmtms/cl_common_helper=>msg_helper_add_symsg(
CHANGING
co_message = eo_message ).
ENDIF.
endmethod.
We just place a normal IF check, that if the system variable that contains the name of the user who is currently performing the operation sy-uname is equal to any desired name, then set a parameter rv_failed as true and then return a message to the user in the UI5 app.
Let us look at all the parameters of this method:
Here, among the other parameters, we can see the parameter rv_failed. It is a boolean indicator for the authorization. If set to true (abap_true), then the user doesn’t have any authorization, the operation will not be done and it will fail. If set to false (abap_false), then the user has authorizations and the operation will be successfully completed. Also, we can maintain a message class with all the necessary messages that we can send to the front end:
Now, lets check how this looks from the front end. At the UI side, there is no need for any changes, just do an empty cache and hard reload (optional) and click on the create button. The error call back of the create method should be triggered and the message sent from the back end should be displayed.
Here, in the below image we can see the message on click of the create button:
Also, in the console at developer tools, we can check the same error message:
Now, let us look into a scenario where, we would like to assign authorizations based on the operation being performed (Create/Update/Delete).
We can implement the below code in the /BOBF/IF_LIB_AUTH_DRAFT_ACTIVE~CHECK_STATIC_AUTHORITY method.
METHOD /bobf/if_lib_auth_draft_active~check_static_authority.
IF is_ctx-activity EQ /bobf/cl_frw_authority_check=>sc_activity-create
OR is_ctx-activity EQ /bobf/cl_frw_authority_check=>sc_activity-change.
rv_failed = abap_true. " Deny access by default
ENDIF.
CASE is_ctx-activity.
WHEN /bobf/cl_frw_authority_check=>sc_activity-create.
" Check the creation of new instance here
AUTHORITY-CHECK OBJECT 'ZAUTH_CNFG' FOR USER sy-uname
ID 'ACTVT' FIELD is_ctx-activity. " '01'
IF sy-subrc = 0.
rv_failed = abap_false. " Grant deletion
ELSE. " Deny deletion
MESSAGE e008(zwcp) INTO DATA(lv_message).
ENDIF.
WHEN /bobf/cl_frw_authority_check=>sc_activity-change.
" Check the static UPDATE authorization here...
AUTHORITY-CHECK OBJECT 'ZAUTH_CNFG' FOR USER sy-uname
ID 'ACTVT' FIELD is_ctx-activity. " '02'
IF sy-subrc = 0.
rv_failed = abap_false. " Grant update
ELSE. " Deny update
MESSAGE e009(zwcp) INTO lv_message.
ENDIF.
ENDCASE.
" Message handling if authorization check failed
IF rv_failed = abap_true AND lv_message IS NOT INITIAL.
CALL METHOD /scmtms/cl_common_helper=>msg_helper_add_symsg(
CHANGING
co_message = eo_message ).
ENDIF.
ENDMETHOD.
Here we make use of a authorization object ZAUTH_CNFG. In this authorization object, first we have to configure the actions we wish to be performed.
Now that we have an authorization object, we can configure the field values to the role and assign it to the user and also what operations can be performed by the user in that role. Based on this, the above code that we have written, will check if the user is assigned the authorization object and, if assigned, whether the user has the authorization to perform the action. If all are true, only then the action will be performed else, customized messages will be sent to the SAPUI5 application.