sexta-feira, 12 de julho de 2019

Authorization checks in apps using SAP Fiori elements


In one of my developments we came across a requirement to provide display access to a transactional app. This was for a wider audience than the app was previously designed for. Now to cater to this we had two options –
  1. Build a brand-new app which is not transactional. Have a new tile and provide access to the new user group to this tile.
  2. Incorporate authority checks in the existing app.
I would have loved to go with the first option, but it was quickly ruled out since along with using the standard template for list report and object page we also had a lot of custom logic like integrating the 3rd party APIs to display aggregated data in form of KPIs based on business requirement. New consumption CDS views would have to be built in case we had to build a new app. Then we also had to consider double maintenance effort that would be required in future to maintain two parallel source code, double regression testing for any variation etc.
Another driving factor was that we found this statement in “SAP UI5 SDK in the Security section
“Moreover, common security mechanisms, which are usually taken for granted, like user authentication, session handling, authorization handling, or encryption are not part of SAPUI5 and need to be handled by the server-side framework and/or custom code of the application
In this blog I will discuss the second approach and how I implemented it. I have used the same app as my previous blog “10 Interesting hacks for UI5 apps using Fiori Elements”. So if someone would like to experiment you can get the entire source code from there and just plug in the parts that I will discuss here.
The video of the app will show it in action. First, I login with as a user who is authorized to edit. Later as a display only user.

Steps are as follows –
1.  Create a new Authorization Object (SU21)

2.  Create two roles in PFCG and assign the new authorization object to the roles. One is a “Display” role and other is a “Admin” role. Assign the roles to distinct users to test (SU01). You will most probably need SAP security team help for this step. Here is the screenshot of the “Display” user’s role.

3.  To do the authority checks I created a new entity called ETY_AUTHORITY in the odata service. The reason for this is that we need to do the authority checks in ABAP. The actual data for the app is still coming from CDS views (which are referenced in oData service to leverage SADL capabilities)

METHOD ets_authority_get_entityset.
    DATA ls_entity LIKE LINE OF et_entityset.
    AUTHORITY-CHECK OBJECT 'ZORDER'
     ID 'ACTVT' FIELD '02'.
    "if authority check fails set it to uneditable
    IF sy-subrc <> 0.
      ls_entity-editable = ''.
    ELSE.
      ls_entity-editable = 'X'.
    ENDIF.
    APPEND ls_entity TO et_entityset.
ENDMETHOD.
4.  Now the frontend. In our UI5 app create a simple model file with methods to handle authority checks from backend. I have done this to ensure that the backend is called only once to check for authorization during the app lifecycle(The JavaScript file name is Model.js).
sap.ui.define([], function() {
 "use strict";
 var auth;
 return {
  setAuthority:function(){
   auth = 'X';
  },
  
  getAuthority: function(){
   return auth;
  }
 };
});
5.  In the onInit() method of ListReportExt I make the oData backend call for authority and the result I save it in the Model(see step 4). If the authority check fails, I set the Create and Delete button to invisible.
jQuery.sap.require("zcustomorder.model.Model");

sap.ui.controller("zcustomorder.ext.controller.ListReportExt", {
 //Local Model declaration 
 Model: sap.ui.require("zcustomorder/model/Model"),
 
 onInit: function(){
  //Somehow the chart entity is not called automatically so we need to call it explicitly to load
  this._loadChart();
  that = this;
  
  //Authority Check Logic
  var url = "/ETS_AUTHORITY";
  var functionSucess = function (oData, controller) {
   sap.ui.core.BusyIndicator.hide();
   var result = oData.results[0];
   if(result.Editable == ''){
    that.Model.setAuthority();
    var creButAll = that.getView().byId(
     "zcustomorder::sap.suite.ui.generic.template.ListReport.view.ListReport::zcustom_order--addEntry-t0"
    );
    var delButAll = that.getView().byId(
     "zcustomorder::sap.suite.ui.generic.template.ListReport.view.ListReport::zcustom_order--deleteEntry-t0"
    );
    if(creButAll){
     creButAll.setVisible(false);
    }
    if(delButAll){
     delButAll.setVisible(false);
    }
   }
  };
  var params = {
   async: false,
   success: function (oData, controller) {
    functionSucess(oData, controller);
   },
   error: function (oError) {
    sap.ui.core.BusyIndicator.hide();
   }
  };
  var oModel = this.getOwnerComponent().getModel();
  oModel.read(url, params);
  //End of Authority Check Logic
  
  //Auto value help logic for Smart Filter 
  var smFilt = this.getView().byId(
   "zcustomorder::sap.suite.ui.generic.template.ListReport.view.ListReport::zcustom_order--listReportFilter"
  );
  var conConfig = smFilt.getControlConfiguration();
  conConfig.forEach(function(item, idx, arr){
   smFilt.removeControlConfiguration(item);
   item.setPreventInitialDataFetchInValueHelpDialog(false);
   smFilt.addControlConfiguration(item);
  }, this);
 }
});
6.  It was a bit challenging for ObjectPage changes as we have to deal with line item level navigation and initialization is done only once. So, if we simply put the checks in onAfterRendering() method then the checks will work only for first entry and not the subsequent ones. To overcome this problem I put the logic to hide the “Edit” and “Delete” buttons in attachRequestCompleted() method which is called every time since the view data needs to be fetched from backend. Also note that I now use the Model.getAuthority() instead of making a backend call.
jQuery.sap.require("zcustomorder.model.Model");
sap.ui.controller("zcustomorder.ext.controller.ObjectPageExt", {
 //Local Model declaration 
 Model: sap.ui.require("zcustomorder/model/Model"),
 onInit: function () {
  that = this;
  
  //Automatic value help for Object Page smart field
  var fld = this.oView.byId("zcustomorder::sap.suite.ui.generic.template.ObjectPage.view.Details::zcustom_order--RF1::qmnum::Field");
  if (fld) {
   var oConfig = fld.getConfiguration();
   if (!oConfig) {
    oConfig = new sap.ui.comp.smartfield.Configuration();
   }
   oConfig.setPreventInitialDataFetchInValueHelpDialog(false);
   oConfig.setDisplayBehaviour(sap.ui.comp.smartfield.DisplayBehaviour.descriptionAndId);
   fld.setConfiguration(oConfig);
  }
  
  //Convert Smart field into URL
  var notifFld = this.getView().byId(
   "zcustomorder::sap.suite.ui.generic.template.ObjectPage.view.Details::zcustom_order--RF1::qmnum::Field"
  );
  if (notifFld) {
   notifFld.attachPress(this.onNotifPress);
  }
  
  // This will ensure that view context is loaded and Stndard buttons can be accessed
  this.getOwnerComponent().getModel().attachRequestCompleted(function () {
   var sPath = that.getView().getBindingContext() ? that.getView().getBindingContext().getPath() : '';
   if (sPath != '') {
    that.hideButtons();
   }
  });
 },
 
 hideButtons : function(){
  var oEdit = this.getView().byId(
   "zcustomorder::sap.suite.ui.generic.template.ObjectPage.view.Details::zcustom_order--edit");
  var oDelete = this.getView().byId(
   "zcustomorder::sap.suite.ui.generic.template.ObjectPage.view.Details::zcustom_order--delete");
  if(that.Model.getAuthority()){
   oEdit.setVisible(false);
   oDelete.setVisible(false);
  }
 }
});

That’s all folks. Hope you enjoyed the Blog.


Source: https://blogs.sap.com/2019/07/07/authorization-checks-in-apps-using-sap-fiori-elements/

Nenhum comentário:

Postar um comentário