Skip to Content
Event Information
Author's profile photo DJ Adams

Annotated links: Episode 52 of Hands-on SAP dev with qmacro

This is a searchable description of the content of a live stream recording, specifically “Ep.52 – Tidying up the JS script for Business Rules OAuth flow” in the “Hands-on SAP devwith qmacro” series. There are links directly to specific highlights in the video recording. For links to annotations of other episodes, please see the “Catch the replays” section of the series blog post.

This episode was streamed live on Fri 07 Feb 2020 and is approximately 60 minutes in length. The stream recording is available on YouTube.

Brief synopsis: In Ep.51 we finished on a cliffhanger – having retrieved a bearer token by authenticating using the resource owner password credentials grant flow, we made the call to the runtime API endpoint, but got a 401. Find out why in this episode, and work with me on making the script neater and more reusable.

00:01:55 Stream starts.

00:04:55 Highlighting a series of blog posts from Witalij Rudnicki on the subject of containers in general, and Docker in particular. I’m enjoying these posts, which Witalij has tagged with the user tag UnderstandContainers so you can find them easily. Thanks Witalij!

00:06:25 Highlighted this morning by Jakob Kjaer is a post on deploying an ABAP stack server on Kubernetes, by Richard TreuProof of Concept: Deploying ABAP in Kubernetes. A fascinating experiment, even more so because of my recent interest in containers and Kubernetes.

00:08:15 This post reminded me that I wanted to show a lovely example of a Terminal User Interface (TUI), in the form of a frontend for managing Kubernetes resources – k9s. It’s an implementation of a wonderful (and well established) idea – the ability to have a user interface that’s efficient, attractive to power users, with great key bindings, no requirement to use the mouse, and can run pretty much anywhere.

00:08:50 Showing off my recent “functor” tshirt, which I like very much 🙂

00:10:00 Connecting via ssh to my host ‘gargantubrain’ (an old MBP on the shelves behind me), to demonstrate k9s which I’d already installed and connected to the Kubernetes installation provided by Minikube.

I mentioned that I found it useful that while I was the user ‘qmacro’ on the local host ‘penguin’ I didn’t have to explicitly specify the user ‘dj’ that I needed to be over on ‘gargantubrain’ – normally it would have attempted to connect as ‘qmacro@gargantubrain’ which would have failed. And that’s because of the richness of ssh which I’ve been looking into as part of my learning list for 2020.

I didn’t actually show how I’d set that up, so I’ll explain now; I maintain ssh configuration in ~/.ssh/config and it looks like this:

Host pixel
     Port 8022

Host github.com
     ForwardX11 no

Host c02w4e8ff9zq
     User i347491

Host zino gargantubrain
     User dj

Host *
     ForwardX11 yes

So you can see that part of this says that when connecting to ‘gargantubrain’, connect as the user ‘dj’. Nice!

For those wondering, I like to forward X11, another one of my 2020 topics to dig into more, when I’m connecting to any host, apart from ‘github.com’ of course, which is just to use ssh to authenticate to interact with repositories with git.

(And yes, some of you guessed that the name ‘gargantubrain’ comes from The Hitch Hiker’s Guide To The Galaxy of course).

00:11:20 Talking of Kubernetes, I mention a great book that I’m reading, by Jeff Geerling, called Ansible for Kubernetes, which is teaching me not only about Kubernetes but about Ansible too, and also Docker. Recommended.

Moreover, talking of Docker, I bring up another TUI in the form of dry – a Docker manager for the terminal. We have a look at this too, having paused briefly to marvel at the power of running tools without having to install them – as Docker containers … which in turn reminds me of npx. We are in the future, right?

00:16:00 The final part of today’s preamble is about GitHub Packages which gives us citizen developers the ability to have our own Node.js modules (or Docker images, and other packages) hosted in a registry. The democratisation of NPM registries! (I couldn’t remember this word on the stream and “made up” a word “peopleisation” which of course is the same thing (δῆμος / demos = people).

00:19:30 Oh yes and I got to use my rules.bash script to recreate the Business Rules setup in my freshly minted (post-expired and destroyed) trial subaccount related Cloud Foundry environment. I wrote about this script in a recent blog post Scripting Cloud Foundry activities in trial.

00:24:30 Moving now to the task at hand for this episode, which was to fix and enhance the Node.js script that we wrote in the previous episode (<ahref=”https://bit.ly/handsonsapdev#ep51″>Ep.51) to call the business rule service we’d created.

I’d already modified the OAuth token request credentials to match the new Business Rules service setup, so we first ran the script to remind ourselves of what the problem was – a 401 ‘unauthorized’ error.

The problem had been that I hadn’t properly set the defaults on the request object I’d set up. I’d written this:

req = request.defaults({
  Authorization: 'Bearer ' + token.accessToken,
}),

but of course I wanted to set default headers (of which Authorization is one), so it should have been like this:

req = request.defaults({
  headers: {
    Authorization: 'Bearer ' + token.accessToken,
  }
}),

What a fool.

00:27:50 Explaining what parts of the script I was not particularly enamoured about – specifically the hard-coding of the OAuth credential information (and, worse, the repetition of it, seeing that it’s already stored in my ~/.netrc file), and the lazy way of specifying the URLs for the authorisation and runtime API endpoints.

00:29:55 To address the first of these issues, there’s a nice netrc package that we can use. Thisallows us to get rid of the hard coded section that looks like this:

client = new oauth2client({
  clientId: 'sb-clone-b9cdd759-e0ad-4f1d-b0be-b38f2da71f95!b34464|bpmrulebroker!b2466',
  clientSecret: 'xs+ExbQiieTZLBX857nQOGkbKj4=',
  accessTokenUri: 'https://qmacrosubdomain.authentication.eu10.hana.ondemand.com/oauth/token'
})

and replace it with this:

const
  netrc = require('netrc'),
  ...
  uaaHost = 'i347491trial.authentication.eu10.hana.ondemand.com',
  authInfo = new netrc()[uaaHost],

  client = new oauth2client({
    clientId: authInfo.login,
    clientSecret: authInfo.password,
    ...
  })

Much neater!

00:37:00 After a brief bit of confusion where I mistook ‘uri’ for ‘uri-js’, we light upon the uri-jspackage which will help us manage our URLs more cleanly, to address the second of the issues.

00:37:40 While riffing on the fact that the ‘http(s)’ part of a URL is called the ‘scheme’, I digress slightly into other schemes,and reflecting on the gopher protocol, which John Murray points out is still in use today. Who knew?! I also mention a couple of old browsers that I used to use, Cello and Viola. Gosh.

00:38:20 Using this ‘uri-js’ module, we can rewrite the opaque URL strings and construct them, instead, like this for the OAuth token request endpoint:

const
  uri = require('uri-js'),
  ...
  uaaHost = 'i347491trial.authentication.eu10.hana.ondemand.com',
  ...
  client = new oauth2client({
    ...
    accessTokenUri: uri.serialize({
      scheme: 'https',
      host: uaaHost,
      path: '/oauth/token'
    })
  })

and like this, for the Business Rules runtime API base endpoint:

runtimeApiEndpoint = uri.serialize({
  scheme: 'https',
  host: 'bpmruleruntime.cfapps.eu10.hana.ondemand.com',
  path: '/rules-service/rest'
})

(remember that all of the Cloud Foundry Rule Execution API resources have a base path of /rules-service/rest – we double-check this by looking into the environment configuration for my ‘EU10’ environment.)

00:51:10 Thinking about a further possible enhancement, specifically how we’d go about caching the OAuth token once received, instead of requesting a new one each time, which is what we’re doing right now. This leads into a small investigation as to whether requesting a new token returns a new one each time, which (after some confusion based on foolishness on my part) it appears it does, sending a fresh expiration (43199 seconds) each time (this is what Huseyin Dereli had already guessed).

Here’s what the entire contents of the retrieved token object look like – there’s plenty for us to be able to use to cache and calculate remaining lifetime:

ClientOAuth2Token {
  client:
   ClientOAuth2 {
     options:
      { clientId:
         'sb-clone-fe154511-fa4d-4b59-9865-92404f5ae664!b35963|bpmrulebroker!b2466',
        clientSecret: 'KreSs824GXFFPJy8WaAVWOPmI/w=',
        accessTokenUri:
         'https://i347491trial.authentication.eu10.hana.ondemand.com/oauth/token' },
     request: [Function: request],
     code: CodeFlow { client: [Circular] },
     token: TokenFlow { client: [Circular] },
     owner: OwnerFlow { client: [Circular] },
     credentials: CredentialsFlow { client: [Circular] },
     jwt: JwtBearerFlow { client: [Circular] } },
  data:
   { access_token:
      'eyJhbGciO...',
     token_type: 'bearer',
     id_token:
      'eyJhbGciO...',
     refresh_token: '9e0de6aec13a4788871b6b3ce33c5e32-r',
     expires_in: 43199,
     scope:
      'bpmrulecompiler!t2466.RuleCompiler.JavaCompile bpmrulecompiler!t2466.RuleCompiler.SqlCompile openid bpmrulesqlcompiler!t2466.RuleSqlCompiler.SqlCompile bpmrulerepository!t2466.RuleRepository.SuperUser bpmruleruntime!t2466.RuleRuntime.SuperUser',
     jti: '2acab2bd037a429dbe805d9f90155cdd' },
  tokenType: 'bearer',
  accessToken:
   'eyJhbGciO...',
  refreshToken: '9e0de6aec13a4788871b6b3ce33c5e32-r',
  expires: 2020-02-07T23:50:54.001Z }

(This, by the way, also explains why I was almost certain I’d seen an actual timestamp in the expiry information before now, whereas today I didn’t. It’s because the date – in this case 2020-02-07T23:50:54.001Z – is part of the object properties, and not returned in the access_token property itself.)

This is a nice place to bring the stream to an end, and in fact that is where it does end! I hope you enjoyed it, and that it inspires you to think a bit more about OAuth, tokens and related topics over the weekend. Happyruminating!

Assigned Tags

      2 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Huseyin Dereli
      Huseyin Dereli

      Thanks DJ for another instructive and fun episode 🙂 I wish I had time to join all the episodes 🙁

      Author's profile photo DJ Adams
      DJ Adams
      Blog Post Author

      Thanks Huseyin, it was great to see you online, and I hope to see you again in an episode soon! I'm really happy you liked it, and also had fun 🙂