Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions docs/advanced/fiori.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,126 @@ ENDMETHOD.
Every value arrives as a string. The table also carries `app_start`, the class
the formatter named.

## Running it in production

Three questions decide whether an embedded section goes live: who may start
which class, what an upgrade of abap2UI5 does to the apps already running, and
what several containers on one page cost the server.

### Who may start which class

The class name and the parameters are in the fragment, and the fragment runs
in the browser. Whoever can reach the service node can send another class name
or another key - exactly as with `?app_start=` on abap2UI5's own page, so
embedding the app changes nothing here. The framework checks the type and not
the user: a class that does not implement `z2ui5_if_app` is refused before it
is instantiated, and a draft belongs to the user who created it. Who may run
what is decided in two places, and both are ordinary ABAP:

- **The node decides who reaches abap2UI5 at all.** Logon procedure, SSO and
the authorization field of the node in `SICF` (checked against `S_ICF`)
work as on any other service. A node of its own for the apps of one area -
a handler class that calls `z2ui5_cl_ui5_http_handler=>run( server )` - is
named in the control's `endpoint` property:
`<z2ui5:Container endpoint="/sap/bc/z2ui5_sd" .../>`. Give every container
on a page the same endpoint: the first one loads the frontend for all of
them.
- **The class decides who may run it.** An `AUTHORITY-CHECK` in `main( )`,
before anything is selected or displayed, with a message when it fails. It
is the one check that holds however the class is started - its own page,
the launchpad, a container, or `nav_app_call( )` from another app. The
node cannot make this decision for it: the first POST carries the class in
its body, and the roundtrips after it carry none. See
[Authorization](/configuration/authorization).

The parameters are user input too. A credit check started with
`salesorder = 4711` can be started with any other number, so the class checks
the object, not only the app - the authorization for the sales organization of
the order, for example. Reading through a CDS view entity with an access
control applies that access control, and EML on the RAP business object runs
its authorization control; a plain `SELECT` on the database table checks
nothing.

```abap
METHOD z2ui5_if_app~main.

IF client->check_on_init( ).

AUTHORITY-CHECK OBJECT `Z_APP_AUTH`
ID `APP` FIELD `ZCL_SALES_ORDER_CREDIT_CHECK`.
IF sy-subrc <> 0.
client->message_box_display( text = `You are not authorized to use this app.`
type = `error`
title = `Not authorized` ).
RETURN.
ENDIF.

" read the order through a view with access control (or EML), and
" refuse the same way when the user may not see it

ENDIF.

ENDMETHOD.
```

### Upgrades

Nothing changes by itself. abap2UI5 is code in the system: an upgrade is an
abapGit pull on the development system and a transport, like any other custom
code, and the version of the control is pinned by the `package-lock.json` of
the Fiori elements app. What is true is that every embedded section moves with
the backend at once, because the frontend comes from it - the same as every
Fiori app moves with the SAPUI5 release of the system.

- **Pin a [release](https://github.com/abap2UI5/abap2UI5/releases/)** and
upgrade deliberately, not by tracking `main`
([Productive Usage](/configuration/productive_usage)).
- **Test on the quality system before the transport goes on.** The list of
what to open is short and can be found by searching the Fiori elements apps
for `z2ui5:Container`: every object page with a section, once. An OPA5 or
wdi5 journey of the host app that opens the object page covers it
automatically; the framework itself runs the frontend embedded in a host
page it does not own, in its browser tests on every commit.
- **No mismatch between browser and server.** The frontend is the backend's
own, it carries an `ETag`, and the browser takes the new one on the first
page load after the transport - no cache to clear, no second version to keep
in step.
- **Freeze it if you have to.** A second installation under another prefix
([Renaming](/advanced/renaming)) with a node of its own serves the embedded
sections, and the control's `endpoint` points at it. That installation is
upgraded on its own schedule, while new development continues on the first.

### Sessions and load

An abap2UI5 app is stateless by default
([Statefulness](/cookbook/expert_more/statefulness)): between two clicks there
is no open ABAP session and no work process held. What a container keeps on the
server is its draft - one row in `Z2UI5_T_01`, written on each roundtrip and
deleted when it expires (after four hours by default, `draft_exp_time_in_hours`
in the [user exit](/advanced/extensibility/user_exits)). Three containers on a
page are three drafts, not three sessions.

What a page costs:

| When | Requests |
|---|---|
| The page opens | one GET for the frontend, for the whole page (a `304` once the browser has it), and one POST per container to start its app |
| The user acts in a container | one POST, from that container only |
| Another object is opened | one POST per container - a new start with the new key |
| Nobody touches the section | nothing |

The server time of a roundtrip is the app's `main( )` plus reading and writing
its draft, so a section is as fast as its start is cheap: read what the section
shows when it starts, and leave the expensive part to a button. The
[roundtrip monitor](/configuration/monitoring) measures it per class - total
time, draft load, `main( )`, response - and the Admin Cockpit shows the p95 per
app, which is the number to look at on the quality system with the real data.
*From abap2UI5 1.147.0 on.*

Do not switch an embedded app to `set_session_stateful( )`: that is the one
setting that turns a container into an open session, one work process per user
and container for as long as the page stays open.

## Examples

Both examples are developed in
Expand Down
56 changes: 15 additions & 41 deletions docs/configuration/authorization.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@ description: Authorization for abap2UI5 apps at the service level or in the app
abap2UI5 gives you flexibility in managing authorization. It has no built-in authorization mechanism, so you can build your own solution at either the service or the app level.

## Service-Level
The easiest way to manage access to different apps is to add authorization checks in the HTTP handler. This approach restricts access to individual apps based on the `app_start` URL parameter, directly in the ICF service handler class.
The HTTP handler decides **who reaches abap2UI5 at all** - not which app runs. Logon procedure, SSO and the authorization field of the node in `SICF` (checked against `S_ICF`) work as on any other ICF service, and need no code. For a check of your own, put it in the handler before it calls the framework:

### Example: Restricting Access Based on URL Parameters
In this example, we use the ICF handler class to control which apps users can access. The ``get_form_field( `app_start` )`` call reads the `app_start` URL parameter that names the abap2UI5 app class to launch (e.g. `...?app_start=my_app`). If the user requests an unauthorized app, the handler refuses access.
```abap
CLASS z2ui5_cl_my_http_handler DEFINITION PUBLIC.

Expand All @@ -22,54 +20,31 @@ ENDCLASS.
CLASS z2ui5_cl_my_http_handler IMPLEMENTATION.

METHOD if_http_extension~handle_request.
" Read the app name from the request
DATA(lv_app) = to_upper( server->request->get_form_field( `app_start` ) ).

" Restrict access to a specific app
IF lv_app <> `MY_APP`.
RETURN.
ENDIF.

" Call the abap2UI5 handler
z2ui5_cl_ui5_http_handler=>run( server ).
ENDMETHOD.

ENDCLASS.
```
### Example: Authorization Objects in Service Handlers
You can also pair this with SAP authorization objects. The example below uses a custom authorization object `Z_APP_AUTH` with an `APP` field — define the object in transaction `SU21` and assign it to the matching roles on your system:
```abap
CLASS z2ui5_cl_my_http_handler DEFINITION PUBLIC.

PUBLIC SECTION.
INTERFACES if_http_extension.

ENDCLASS.

CLASS z2ui5_cl_my_http_handler IMPLEMENTATION.

METHOD if_http_extension~handle_request.
" Read the app name from the request
DATA(lv_app) = to_upper( server->request->get_form_field( `app_start` ) ).

" Run an authorization check
" may this user use the apps behind this node at all?
AUTHORITY-CHECK OBJECT `Z_APP_AUTH`
ID `APP` FIELD lv_app.
ID `APP` FIELD `SALES`.

IF sy-subrc <> 0.
" Authorization failed, refuse access
server->response->set_status( code = 403
reason = `Forbidden` ).
RETURN.
ENDIF.

" Call the abap2UI5 handler if authorized
z2ui5_cl_ui5_http_handler=>run( server ).
ENDMETHOD.

ENDCLASS.
```
Create multiple HTTP endpoints for different users or departments to fine-tune access.

The example uses a custom authorization object `Z_APP_AUTH` with an `APP` field — define the object in transaction `SU21` and assign it to the matching roles on your system. Create one node per user group or department, each with a handler like this, to separate who may reach which group of apps.

::: warning Do not decide on `app_start` in the handler
It is tempting to read the class from the request - ``server->request->get_form_field( `app_start` )`` - and refuse the ones a user may not run. That does not work. `app_start` is a URL parameter of the first page request only: the roundtrips are POSTs to the service URI, the class travels in the body of the first one (from the launchpad and from the [embed control](/advanced/fiori) it never is in the URL at all), every roundtrip after the start carries no class name, and `nav_app_call( )` changes the app without the client naming it. A handler that refuses on that parameter either breaks every roundtrip or checks nothing. Which app may run is decided in the app.
:::

## App-Level
Alternatively, handle authorization within individual app classes. This approach works well when you want each app to check user permissions before acting.
Whoever reaches the node can start any class that implements `z2ui5_if_app` - the class name comes from the browser, as the URL parameter, a launchpad parameter or the `app` property of the [embed control](/advanced/fiori). The framework checks the type, not the user: a class that does not implement the interface is refused before it is instantiated. So the decision which user may run which app belongs in the app class, the way a report checks before it selects. It holds however the app is started - its own page, the launchpad, a Fiori elements section, or `nav_app_call( )` from another app - and a transport carries the app and its guard together.

### Example: Authorization Check in an App Class
In this approach, each app checks the user's permissions, like traditional ABAP apps.
Expand Down Expand Up @@ -131,6 +106,5 @@ The refusal branch matters as much as the check. Returning without displaying
anything leaves the user on an empty frame with nothing to go on — indistinguishable
from an app that crashed. Say what happened, then return.

::: warning
If you don't add authorization checks at the app level, make sure users can't bypass service-level checks by navigating between apps.
:::
## Startup Parameters Are User Input
An app started with parameters - from the launchpad, or from a Fiori elements object page with the [embed control](/advanced/fiori) (`client->get( )-t_comp_params`) - can be started with any other value. An app that shows one sales order checks that the user may see *that* order, not only that they may run the app: the authorization for its sales organization, for example. Reading through a CDS view entity with an access control applies that access control, and EML on a RAP business object runs its authorization control; a plain `SELECT` on the database table checks nothing.
2 changes: 2 additions & 0 deletions docs/configuration/productive_usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Technically, abap2UI5 is just an HTTP handler implementation — use it like any
## Stable Version
The project evolves all the time, so there's no fixed "stable" version. But we keep changes to the public APIs minimal to avoid frequent app refactoring. Pin to a [release](https://github.com/abap2UI5/abap2UI5/releases/) instead of tracking the main branch, and update regularly to keep refactoring effort low.

Apps embedded in Fiori elements or UI5 freestyle apps with the embed control load their frontend from the backend, so an upgrade reaches all of them at once. What to test before the transport moves on, and how to keep them on a frozen installation, is on [Fiori Elements Integration](/advanced/fiori#upgrades).

## Quality Assurance
Every commit to the framework runs through CI before it reaches a release:

Expand Down
Loading