diff --git a/docs/advanced/fiori.md b/docs/advanced/fiori.md index b4563676..d53472d2 100644 --- a/docs/advanced/fiori.md +++ b/docs/advanced/fiori.md @@ -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: + ``. 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 diff --git a/docs/configuration/authorization.md b/docs/configuration/authorization.md index b33c642c..451b4a55 100644 --- a/docs/configuration/authorization.md +++ b/docs/configuration/authorization.md @@ -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. @@ -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. @@ -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. diff --git a/docs/configuration/productive_usage.md b/docs/configuration/productive_usage.md index 35d766ea..a39cab66 100644 --- a/docs/configuration/productive_usage.md +++ b/docs/configuration/productive_usage.md @@ -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: