Skip to Content
Technical Articles

How to patch a CAPM Node.js library

tl;dr

If you find you that need to patch a CAPM library, here is what to do:

  1. Use ‘npm install’ to obtain a copy of the CAPM library to patch.
  2. Push the library into a fresh, public GitHub repository.
  3. Patch the library, increase the patch version, commit the patch and push the commits.
  4. In the CAPM project’s ‘package.json’, add the patched library as a dependency, with the URL of the patched commit, e.g.
    "dependencies": {
            ...
            "@sap/cds-runtime": "git+https://github.com/laszloKajan/-sap-cds-runtime.git#c1c45241ec737d5bdd7e659dd2e50771818b0314",​
  5. Use ‘npm ls <@sap/library>’ to verify that all dependencies use the patched version.
  6. Rebuild the MTA and deploy.

Goal

Many of you probably remember the well-received OpenSAP course ‘Building Applications with SAP Cloud Application Programming Model‘. Week 3 unit 5 showed us how to do ‘Instance-Based Authorization through where: Clauses‘. It was this feature that I wanted to test in my SCP trial account.

The problem

The day happened to be 20-Aug-2020, a few days after the release of @sap/cds-runtime version 2.1.7. After enabling the JWT strategy (‘npm install @sap/xssec @sap/xsenv’),  deploying the unit to the Cloud Foundry, instantiating the ‘userattributes’ role template with the static currency value of ‘USD’, creating a role collection with the new role instance and the ‘admin’ role and assigning it to my user, I tested the services ‘/Admin/Orders’ and ‘/browse/Orders’.

‘/Admin/Orders’ is supposed to give results, but I got ‘403 – Forbidden’. ‘/browse/Orders’ is supposed to return an empty list (because none of the orders are of currency ‘USD’), but I got all the ‘EUR’ orders. Something is wrong.

Because I suspected outdated libraries in the project, I ran ‘npx npm-check-updates -u && npm install’ at the root of the ‘bookshop’, as well as in ‘db’. Apart of some changes to minor versions, this upgraded ‘@sap/cds’ from ‘3’ to the new major version ‘4’. After another round of ‘mbt build && cf deploy’, ‘/Admin/Orders’ gave me the expected list of orders depending on the assignment of the ‘admin’ role. But ‘/browse/Orders’ now gave ‘403 – Forbidden’. Not quite there yet.

With everything at its latest version, I suspected that I’m the victim of some bug. A bug that’s most likely not in the code of the tutorial – there is very little code, after all – but in a library. There’s nothing left to do: I must debug, and fix the library.

Solution

  1. In order to debug the ‘srv’ service module of the app as it runs on the CF, it has to be started with the ‘–inspect’ flag. So I changed the ‘start’ script of the main ‘package.json’ to
    "start": "npx --node-arg=--inspect cds run --in-memory?"​

    and added a new debug configuration for ‘Node.js – Attach to Remote Program’ with

          {
              "type": "node",
              "request": "attach",
              "name": "Attach to Remote",
              "address": "127.0.0.1",
              "port": 9229,
              "localRoot": "${workspaceFolder}",
              "remoteRoot": "/home/vcap/app"
          }
  2. Then I rebuilt and re-deployed the apps, enabled ssh to the ‘srv’ app and opened a tunnel for the debugger:
    1. mbt build && cf deploy mta_archives/sap.capire-bookshop_1.0.0.mtar
    2. cf enable-ssh capire-bookshop-srv && cf restart capire-bookshop-srv
    3. cf ssh capire-bookshop-srv -L 9229:127.0.0.1:9229
  3. I then attached the debugger, set a breakpoint on ‘All Exceptions, and reloaded ‘/browse/Orders’.
  4. The exception in ‘Request.js’ was not very revealing, but the next one in ‘Service.js’ came with a stack, with ‘ApplicationService.handler (/home/vcap/app/node_modules/@sap/cds-runtime/lib/common/generic/auth.js:307:14)’ in it. Examining the code and setting a few more breakpoints led me to this section of code:
        let val
        let attrs = Object.assign({ id: req.user.id }, req.user.attr || {})
        let attr = parts.shift()
        while (attr) {​
  5. The intention is to merge the user’s attribute object ‘req.user.attr’ into ‘attrs’. The problem is that with ‘@sap/xssec’ version ‘3’ in the project, ‘req.user.attr’ is not an object, but a Proxy with only a ‘get’ handler (see ‘@sap/cds-runtime/lib/common/auth/passport.js:119’). Even though the ‘currency’ attribute is present, it doesn’t make it into ‘attrs’.
  6. A little modification of the above does the trick though:
        let val
        let attr = parts.shift()
        let attrs = Object.assign({ id: req.user.id }, req.user.attr ? { [attr]: req.user.attr[attr] } : {})​
        while (attr) {

    Now the question is:

    • How to make the ‘srv’ module use the patched version of ‘@sap/cds-runtime/lib/common/generic/auth.js’ instead of the official one,
    • even if it’s rebuilt as part of the CF deployment process?
  7. As shown on npmjs.com, git sources from a GitHub repository can be used to load an ‘npm’ module. I created a public GitHub repository out of the original version of the ‘@sap/cds-runtime’, simply committing version ‘2.1.7’. Then I committed my fix, and increased the patch version to ‘2.1.8’. The latter is important, because it causes ‘npm’ to ‘prefer’ the newer, patched version, also for the dependencies of dependencies (such as ‘@sap/cds’).
  8. I added the patched ‘@sap/cds-runtime’ version to the ‘package.json’ of the ‘bookshop’ app:
        "dependencies": {
            ...
            "@sap/cds-runtime": "git+https://github.com/laszloKajan/-sap-cds-runtime.git#c1c45241ec737d5bdd7e659dd2e50771818b0314",​

    and ran ‘npm install’.

  9. ‘npm ls @sap/cds-runtime’ shows that ‘@sap/cds’ has picked up the patched version:
    user: bookshop $ npm ls @sap/cds-runtime
    @sap/capire-bookshop@1.0.0 /home/user/projects/cloud-cap-samples/packages/bookshop
    ├─┬ @sap/cds@4.1.7
    │ └── @sap/cds-runtime@2.1.8  deduped (git+https://github.com/laszloKajan/-sap-cds-runtime.git#c1c45241ec737d5bdd7e659dd2e50771818b0314)
    └── @sap/cds-runtime@2.1.8  (git+https://github.com/laszloKajan/-sap-cds-runtime.git#c1c45241ec737d5bdd7e659dd2e50771818b0314)
  10. I rebuilt and re-deployed the app again, and this time got the expected results for ‘/browse/Orders’:
    • For role instance attribute ‘USD’:
      {"@odata.context":"$metadata#Orders","value":[]}​
    • For role instance attribute ‘EUR’:
      {
      	"@odata.context": "$metadata#Orders",
      	"value": [
      		{
      			"ID": "64e718c9-ff99-47f1-8ca3-950c850777d4",
      			"createdAt": "2019-01-30T00:00:00.000Z",
      			"createdBy": "jane.doe@test.com",
      			"modifiedAt": null,
      			"modifiedBy": null,
      			"OrderNo": "2",
      			"total": null,
      			"currency_code": "EUR"
      		},
      		{
      			"ID": "7e2f2640-6866-4dcf-8f4d-3027aa831cad",
      			"createdAt": "2019-01-31T00:00:00.000Z",
      			"createdBy": "john.doe@test.com",
      			"modifiedAt": null,
      			"modifiedBy": null,
      			"OrderNo": "1",
      			"total": null,
      			"currency_code": "EUR"
      		}
      	]
      }​

Summary

Even though SAP does not release the source code of the CAPM libraries, it is possible to patch them, and use the patched version in a deployed CAPM application.

Disclaimer: for the bug examined in this blog post, an SAP incident was created.

Author and motivation

Laszlo Kajan is a full stack Fiori/SAPUI5 expert, present on the SAPUI5 field since 2015, diversifying into the area of SCP development.

The motivation behind this blog post is to provide a solution for patching an SAP-provided CAPM library such as ‘@sap/cds-runtime’.

Further reading

1 Comment
You must be Logged on to comment or reply to a post.
  • 20200825 Update from Sebastian (SAP):

    Thanks for reporting. The fix will be released with @sap/cds-runtime^2.2.0 (transient dependency of @sap/cds).