Wednesday, 12 February 2020

HANA XSA: Securing your application, and ensuring a good User Experience (UX) – Part 1

Is User Experience or Security more important?


In short, both are equally important.  One of the most overlooked areas of user experience is security.  As with most user-oriented design, security should be implemented in a way that guides the user, only allowing them to interact in ways which they are authorized.  on the other hand, security should not force the user to have a bad experience, it should enhance it.

Out-of-the-box methods for securing an application often leaves the user with a bad experience.

This series of blog posts will guide you through some common scenarios where a little bit of effort can lead to a much better user experience.  There will be some additional posts to cover the scenarios below, with example code for each.

Security-related UX Scenarios


These are some example scenarios we will use to discuss and show examples of how to implement a better user experience.

Scenario Out-Of-The-Box UX Better UX  
1. No Access – The user has no authorization to the application. Generic 401 (Unauthorized) Error Redirected to an “Access Denied” page, with instructions/link to how to request access.
2. Role-based actions – The user can only make changes to data when they have the correct authorization.  Generic 401 Error when trying to make changes to data  Dynamic UI which only shows authorized actions (buttons, links, etc.) 
3. Role-based subset of data – The user can only access a certain subset of the data based on their authorizations.  N/A, custom filtering/coding required.  Automatic data filtering using attribute-based analytic privilege (no custom UI coding)  

Pre-requisites


For the purposes of this article, I am using features and code examples which have been built on SAP HANA, Express Edition 2.0 SPS03.  Any other versions of 2.0 should have essentially the same features, however code syntax may vary.

HANA Platform Security Model


HANA XSA uses roles as a way to implement and organize:

◉ Scope-based permissions
◉ Attribute-based permissions

Scope-bases permissions are typically used to control what the user can do (how they can interact with the data).  For example, a “Viewer” can only display/analyze data, while an “Editor” can perform tasks against the data such as making changes or deleting content.

Attribute-based permissions are typically used to control which data the user can interact with (think of them as filters for the data).  For example, a user with a “NorthAmerica” attribute, can only interact with data filtered by this attribute, while a “Europe” attribute would allow only for its data.

Scopes and attributes are part of roles, which can be combined to create aggregated role collections.  Role collections are then assigned to user (or user groups).

In this blog we show how to perform these tasks using the HANA XSA security model using this logic flow.

SAP HANA, SAP HANA Tutorial and Materials, SAP HANA Certifications, SAP HANA Prep

It also possible to use the new Cloud Application Programming model to handle these scenarios.

So, let’s take a look at some example implementations for the scenarios described above.

Scenario 1:  No Access


This may seem like an unimportant use case, but often this is a critical use case for onboarding a new user, which if designed correctly should decrease the support load and ensure happier users.

This example will be shown using a web UI frontend (SAPUI5) and the API layer (NodeJS).

In order to apply role-based logic, we need to be able to get the specifics of the role (scopes and attributes) to the UI.  The UI doesn’t have direct access to this information, but the API layer does, so in this example we will create a NodeJS service to return this information to the UI.

To keep this article simple, we will just give the essentials to begin with, making the assumption that you know how to create a basic MTA and modules.

Step 1 – Create an MTA and Modules (use these names if you want to be able to borrow code from the code samples)

Project/Module Property   Value 
Multi-Target Application Project
Project Name SecureUX
Application ID  SecureUX 
HANA Database Module  
Module Name db 
NodeJS Module  
Module Name  api 
Enable XSJS support true 
SAPUI5 HTML5 Module  
Module Name ui
Namespace   com.sap.secureux 
View Type   XML 
View Name   App 

Step 2 – Create UAA Service

This can be done using either the XSA Cockpit, or the XS CLI tool.  In my sample code, I reference the service with the name “secureux-uaa”

xs-security.json

{
    "xsappname": "SecureUX",
    "scopes": [
        {
            "name": "$XSAPPNAME.Display",
            "description": "display"
        },
        {
            "name": "$XSAPPNAME.Create",
            "description": "create"
        },
        {
            "name": "$XSAPPNAME.Edit",
            "description": "edit"
        },
        {
            "name": "$XSAPPNAME.Delete",
            "description": "delete"
        }
    ],
    "attributes": [
        {
            "name": "region",
            "description": "region",
            "valueType": "s"
        }
    ],
    "role-templates": [
        {
            "name": "Viewer",
            "description": "View all records",
            "scope-references": [
                "$XSAPPNAME.Display"
            ],
            "attribute-references": [
                "region"
            ]
        },
        {
            "name": "Editor",
            "description": "Edit and Delete records",
            "scope-references": [
                "$XSAPPNAME.Create",
                "$XSAPPNAME.Edit",
                "$XSAPPNAME.Delete",
                "$XSAPPNAME.Display"
            ],
            "attribute-references": [
                "region"
            ]
        }
    ]
}

Step 3 – Bind services together in mta.yaml

The main changes here from the default are:

– setting up UAA as a resource and adding to requires for API and UI

– binding UI to API with requires/provides, and passing token from UI to API

mta.yaml

ID: SecureUX
_schema-version: '2.1'
version: 0.0.1
modules:
  - name: ui
    type: html5
    path: ui
    requires:
      - name: core_api
        group: destinations
        properties:
          forwardAuthToken: true
          url: '~{url}'
          name: core_api
      - name: uaa
  - name: api
    type: nodejs
    path: api
    provides:
      - name: core_api
        properties:
          url: '${default-url}'
    requires:
      - name: hdi_db
      - name: uaa
      - name: db
  - name: db
    type: hdb
    path: db
    requires:
      - name: hdi_db
resources:
  - name: uaa
    type: com.sap.xs.uaa
    parameters:
      service-name: secureux-uaa
  - name: hdi_db
    properties:
      hdi-container-name: '${service-name}'
    type: com.sap.xs.hdi-container

Step 4 – Add NodeJS service for user context

◉ In the /api/lib folder, add a new file (userContext.xsjs) for the userContext service.

userContext.xsjs

try {
    // Initialize hana connection/context
    var oConn = $.hdb.getConnection();
    var oSession = $.session;
    $.response.status = $.net.http.OK;
    $.response.contentType = "application/json";
    $.response.setBody(JSON.stringify(oSession.securityContext));
    
    oConn.close();
} catch(ex) {
    // Return error
    $.response.setBody("Failed to retrieve data");
    $.response.status = $.net.http.INTERNAL_SERVER_ERROR;
}

*This code establishes a connection to HANA (to ensure the session is established) and returns the $.session.securityContext to the UI when called.

◉ Don’t forget to force authentication on by setting “anonymous = false” in /api/server.js

server.js (partial)

...

var options = {
        anonymous : false, // remove to authenticate calls
        redirectUrl : "/index.xsjs"

};

...

Step 5 – Enable authentication in the UI approuter and configure the routes for the API.

◉ In the approuter configuration (xs-app.json), you will define these routes

     ◉ /resources – no authentication required, so the UI code will run for all users
     ◉ /*.xsjs – Routes to api (NodeJS) service. Requires authentication, but not any scopes, so the APIs in this folder will run for authenticated users regardless of scopes

xs-app.json

{
    "welcomeFile": "webapp/index.html",
    "authenticationMethod": "route",
    "routes": [
        {
            "source": "^/(.*)(.xsjs)",
            "destination": "core_api",
            "authenticationType": "xsuaa"
        },
        {
            "source": "^/(.*)$",
            "localDir": "resources"
        }
    ]
}

Step 6 – Create UI to display user info when successfully authenticated

◉ Update the /ui/resources/webapp/controller/App.controller.js to request the user info from the API during the onInit() lifecycle method.
     ◉ See inline comments in the code for details on how the verification of the roles occurs.

App.controller.js

sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel",
"sap/m/MessageBox"
], function (Controller, JSONModel, MessageBox) {
"use strict";

return Controller.extend("com.sap.secureux.ui.controller.App", {
onInit: function(){
this.getUserContext();
},
getUserContext: function(){
var oController = this;
var urlUserContext = "/userContext.xsjs";
var oModelUserContext = new JSONModel(urlUserContext);
// This code attached to the data request and parses the response.  This example handles
// error scenarios (where the service is down, where the server returns 403/access denied, 
// and finally it parses the scopes in the success authentication response to see if the 
// user has a specific scope.
oModelUserContext
.attachRequestFailed(function(oEvent) {
var errorObject = oEvent.getParameters();
oController._parseResponse(errorObject);
})
.attachRequestCompleted(function(oEvent) {
var dataResponse = oEvent.getSource();
var errorObject = oEvent.getParameter("errorobject");
oController._parseResponse(errorObject, dataResponse);
});
// Set the resulting sessionContext to a user JSON model and bind to the view.
this.getView().setModel(oModelUserContext, "user");
},
_parseResponse: function(oError, oData){
if (oError){
// If Access Denied is returned by the server, redirect the user to the access denied
// page.
if (oError.statusCode === 403){
this.getOwnerComponent().getRouter().navTo("accessdenied");
}
else {
// If any generic error occurs, let the user know with a message.
jQuery.sap.delayedCall(1000, this, function(){
MessageBox.error("Unable to connect to server.  Please check with IT helpdesk, or try again later. (" + oError.statusCode + ":" + oError.statusText + ")");
});
}
}
else{
// Verify Access - If the user has a "Display" scope then let them have basic access. 
// If no "Display" scope is found then redirect them to the access denied page.
var oScopes = oData.getProperty("/scopes");
if (oScopes.filter(function(row) {
return (row.endsWith(".Display"));
}).length === 0) {  
// No Access
this.getOwnerComponent().getRouter().navTo("accessdenied");
}
}
}
});
});

◉ Update the /ui/resources/webapp/view/App.view.xml to display the user info (assuming there will be a model called “user” which contains the securityContext from the API we created earlier)

App.view.xml

<mvc:View controllerName="com.sap.secureux.ui.controller.App" xmlns:html="http://www.w3.org/1999/xhtml"
    xmlns:mvc="sap.ui.core.mvc" displayBlock="true" xmlns="sap.m">

    <App id="idAppControl">
        <pages>
            <Page title="{i18n>title}">
                <content>
                    <List binding="{user>/userInfo}" headerText="User Context">
                        <DisplayListItem label="logonName" value="{user>logonName}" />
                        <DisplayListItem label="givenName" value="{user>givenName}" />
                        <DisplayListItem label="familyName" value="{user>familyName}" />
                        <DisplayListItem label="email" value="{user>email}" />
                    </List>
                    <List items="{user>/scopes}" headerText="Scopes">
                        <DisplayListItem label="scope" value="{user>}" />
                    </List>
                    <List items="{user>/userAttributes/region}" headerText="Attributes (region)">
                        <DisplayListItem label="region" value="{user>}" />
                    </List>
                </content>
            </Page>
        </pages>
    </App>
</mvc:View>

Step 7 – Create roles, role collections and assign to your test user

This administration step can be done by using either the XSA Cockpit or the XS CLI tool.

For my testing, I created an XSA User for each of the role collections defined below, so I could verify the configuration was correct.

Roles

Role Scope Attribute
SecureUX-Editor-NA  Editor NA
SecureUX-Editor-EU   Editor  EU 
SecureUX-Viewer-NA   Viewer  NA 
SecureUX-Viewer-EU   Viewer  EU 

Role Collections

Role Collection Role
SecureUX-Editor-NA SecureUX-Editor-NA
SecureUX-Editor-EU SecureUX-Editor-EU
SecureUX-Editor-Global SecureUX-Editor-NA
SecureUX-Editor-EU
SecureUX-Viewer-NA SecureUX-Viewer-NA
SecureUX-Viewer-EU SecureUX-Viewer-EU
SecureUX-Viewer-Global SecureUX-Viewer-NA
SecureUX-Viewer-EU

Step 8 – Deploy application and verify service is working

Now is a good time to deploy your code and do some testing of the positive (happy path) scenario.  If your user has been granted access to the application, then you will see this (or a similar) resulting output page:

SAP HANA, SAP HANA Tutorial and Materials, SAP HANA Certifications, SAP HANA Prep

Step 9 – Create an Access Denied view, and set up routing

◉ Create a new view file (/ui/resources/webapp/view/AccessDenied.view.xml)

AccessDenied.view.xml

<mvc:View xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc" xmlns:semantic="sap.m.semantic" xmlns:footerbar="sap.ushell.ui.footerbar" xmlns:l="sap.ui.layout"
        controllerName="com.sap.secureux.ui.controller.AccessDenied">
        <MessagePage
            icon="sap-icon://locked"
            showHeader="true"
            title="{i18n>AccessDenied.title}"
            text="{i18n>AccessDenied.text}"
            description="{i18n>AccessDenied.description}"/>
</mvc:View>

◉ Create a new controller file (/ui/resources/webapp/controller/AccessDenied.controller.js)

AccessDenied.controller.js (default)

sap.ui.define([
"sap/ui/core/mvc/Controller"
], function (Controller ) {
"use strict";

return Controller.extend("com.sap.secureux.ui.controller.AccessDenied", {
onInit : function () {
}
});
}
);

◉ Update the resources file with the new text strings (/ui/resources/webapp/i18n/i18n.properties)

i18n.properties

title=SecureUX
appTitle=ui
appDescription=App Description

AccessDenied.title=Access Denied
AccessDenied.text=You do not have access to this application.
AccessDenied.description=Please check with your manager to request access.

◉ Update the application manifest file (manifest.json) with additional target and route for the access denied view.

manifest.json (partial)

"routing": {
    "config": {
        "routerClass": "sap.m.routing.Router",
        "viewType": "XML",
        "async": true,
        "viewPath": "com.sap.secureux.ui.view",
        "controlAggregation": "pages",
        "controlId": "idAppControl"
    },
    "routes": [
        {
            "name": "RouteView1",
            "pattern": "RouteView1",
            "target": [
                "TargetView1"
            ]
        },
        {
            "pattern": "accessdenied",
            "name": "accessdenied",
            "target": [
                "AccessDenied"
            ]
        }
    ],
    "targets": {
        "TargetView1": {
            "viewType": "XML",
            "transition": "slide",
            "clearAggregation": true,
            "viewName": "View1"
        },
        "AccessDenied": {
            "viewType": "XML",
            "clearAggregation": true,
            "viewName": "AccessDenied"
        }
    }
}

Step 10 – Test “failed access” scenario

◉ Create a test user, and do not assign it to the role collection for this application. Try connecting to the application.  After a successful login, you should be automatically redirected to the “Access Denied” view.

SAP HANA, SAP HANA Tutorial and Materials, SAP HANA Certifications, SAP HANA Prep

No comments:

Post a Comment