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

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

This is a searchable description of the content of a live stream recording, specifically “Ep.51 – More fun with Business Rules API and OAuth” in the “Hands-on SAP dev with 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 31 Jan 2020 and is approximately 60 minutes in length. The stream recording is available on YouTube.

Brief synopsis: In the previous episode we got as far as staring at the OAuth Bearer token that we could use to authenticate calls to the API. In this episode we see how far we can get in using one of those tokens to do so. Perhaps even a bit of JavaScript with Axios might be nice to look at too – promises promises! (geddit)

00:01:55 Stream starts.

00:03:00 Talking briefly about “behind the scenes” of the HandsOnSAPDev “studio” (which I changed around a little bit recently so the video is showing me in a different angle).

We see my setup on the main desk, which includes the Blue Yeti microphone and my single 4K monitor which, when streaming, I switch to run at twice the resolution of 720p (1280×720), i.e. 2560×1440, so that I can use my version of the Tiling Window Manager for Chrome OS extension (which I’ve modified to give me the ability to adjust the gaps between windows) to run four 720p sized windows, one of which (the top left one) I capture and stream. This minimises scaling and the untidy visual artifacts that go with that sort of thing. That also means I’ve got three more windows to manage the streaming and other stuff.

00:05:40 A small warning that my typing may be louder than usual as I’ve switched back from my Anne Pro 2 (with Gateron Brown switches) to my Vortex Race 3 which has Cherry MX Blue switches – tactile AND clicky, nice!

00:06:00 Reminding folks about the very first conference on the SAP Cloud Application Programming Model, reCAP, which is taking place in Heidelberg, Germany, on Fri 15 May 2020. I’m looking forward to this very much and hope to be able to make it. The call for papers is open, so head over and submit a session proposal!

00:07:50 Talking about the recent Coffee Corner Radio podcast episode which has an interview with the one and only Tom Jung our new head of Developer Advocacy at SAP.

00:08:55 Looking at what we’re going to cover in the main part of this episode, which is to continue with the SAP Business Rules API. We hadn’t got as far as calling the runtime API last time, so this time that’s what we’ll aim to do, starting with some Bash script goodness in the terminal to get us warmed up.

00:11:30 Reminding ourselves of the different APIs that we have for the SAP Business Rules on the API Hub, which can be divided up into APIs for Neo and Cloud Foundry, for designtime (‘authoring’) and runtime (‘execution’), and for different versions.

Last time, we used the designtime API endpoint /v1/projects to get a brief summary of our Business Rules project (airline discounts).

00:13:00 To get back into the swing of things, we switch to a terminal running tmux and open a couple of panes to run simple scripts that will monitor the apps and services in my Cloud Foundry space. I wrote about these scripts, and related subjects, in a couple of recent blog posts: Scripting Cloud Foundry activities in trial and Mini adventures with MTAs and the Cloud Foundry CLI.

00:16:20 Having a look at the gettoken.bash script, and where we get some of the information that it requires. Here’s the entirety of the script:

#!/bin/bash

# Run this script in the context of an eval $(...) to get a fresh
# access token set in TOKEN

curl \
  --silent \
  --netrc \
  --header "Content-Type: application/x-www-form-urlencoded" \
  --data "grant_type=password&response_type=token&username=$USERNAME&password=$(pass SAP/trial)" \
  https://qmacrosubdomain.authentication.eu10.hana.ondemand.com/oauth/token \
| jq -r '"export TOKEN=\(.access_token)"'

The purpose of this script is to retrieve a fresh access token and present it in a way that can be eval‘d into the environment. It does this by calling the authentication endpoint (qmacrosubdomain.authentication.eu10.hana.ondemand.com/oauth/token in this case) and passing the appropriate credentials and request information.

Note that the authentication for this endpoint is Basic Authentication with the username and password provided by the ‘netrc’ facility which we’ve seen on recent previous live stream episodes – basically, credentials are managed on a per-host basis in a private .netrc file and curl will use those when told to with the --netrc option.

00:17:10 On the subject of token requests in OAuth flows, there’s a really nice post by Carlos RogganSAP Cloud Platform Backend service: Tutorial [14]: about OAuth mechanism – which explains lots of things and is, along with its companion blog posts, well worth reading, not least to understand more about the different OAuth based authentication mechanisms, or ‘grant types’.

00:17:50 The grant type we’re going to use is the ‘resource owner password credentials grant type’, and is also described in the documentation for the NPM module client-oauth2 that we’re about to use in our JavaScript code.

This grant type, or flow, is when the client is trusted by the user, because the user gives the client their credentials to send to the OAuth endpoint. This is different to other flows where the client is not trusted, and in these cases the user is temporarily redirected to be asked for their credentials, and a short-lived token is returned to the client with which an access token can then be requested.

00:18:55 For managing my credentials securely in my terminal, I’m using the classic pass password manager which you can see in action in the $(pass SAP/trial) part of the script.

00:20:25 Having retrieved a token, we look briefly at the other data that is returned with it, including the value of the expires_in property, which is 43199. If you weren’t on the live stream at the time, can you make a guess as to why that is a significant number? Put your thoughts in the comments to this post.

00:21:00 Once we have a token in the TOKEN environment variable, we can make the actual API call to /v1/projects (this is still warming ourselves up from last week) to retrieve information from the designtime API, about the projects we have. This is in the getprojectinfo.bash script which looks like this:

#!/bin/bash

curl \
  --silent \
  --header "Authorization: Bearer ${TOKEN}" \
  https://bpmrulerepository.cfapps.eu10.hana.ondemand.com/rules-service/rest/v1/projects

That all works fine. So far so good.

00:23:00 Thinking now about calls not to the designtime API but to the runtime API, we pause briefly to think about the version management that has been introduced to the Business Rules service, which is explained by Archana Shukla in the blog post Version Management in SAP Cloud Platform Business Rules, and means that our new airline discounts rule project that we’re currently authoring is accessible to call as a ‘working set’ rule, for which we need the /v2/workingset-rule-services endpoint.

00:23:50 We try out this call in the SAP API Hub using my ‘EU10’ environment that I’ve already got set up, using a payload that I’d stored in payload.json, that looks like this:

{
  "RuleServiceId": "8ebbf08e24f84790a99f1e8f32be59a9",
  "Vocabulary": [
    {
      "flightinfo": {
        "carrier": "LH"
      }
    }
  ]
}

I think that’s a nice way of calling rules in runtime, nicer than the previous version (v1).

By the way, the value for the RuleServiceId property is available in the URL when we’re looking at the business rule service within the project in the Business Rules Editor.

00:26:55 We copy the getprojectinfo.bash script to a new one, callrule.bash which we modify to look like this:

#!/bin/bash

curl \
  --silent \
  --header "Authorization: Bearer ${TOKEN}" \
  -d@payload.json \
  --header "Content-Type: application/json" \
  https://bpmruleruntime.cfapps.eu10.hana.ondemand.com/rules-service/rest/v2/workingset-rule-services

As you can see, we’re calling the runtime API (bpmruleruntime.cfapps.eu10.hana.ondemand.com in my case) with the /v2/workingset-rule-services endpoint, passing the contents of payload.json using the -d@ option to curl. Of course, we pass the token we already have as a bearer token in the Authorization header of the request.

00:30:00 Starting to move across to doing this now in JavaScript, and we initialise a new Node.js project callrule … and while I do that Christian Drumm gently challenges me on my continued use of the Bash shell, rather than, say, the Z shell.

00:32:10 At this stage we go back and take a closer look at the client-oauth2 that we glanced at earlier, as this is what we’re going to use for the OAuth flow.

We also look at the request-promise package for making the actual HTTP requests, in a promise context (this package is essentially a wrapper around the request package). This is a deprecated package but serves our purpose for now.

00:33:50 First, we install the nodemon package as a development dependency – everyone likes a bit of nodemon, right? We start running it in a new tmux pane and create the simplest thing that could possibly work to satisfy nodemon, in a sort of “nodemon driven development” style.

00:55:30 After lots of thinking and typing, we end up with something that looks like this in index.js:

const
  oauth2client = require('client-oauth2'),
  request = require('request-promise'),

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

async function main() {
  const
    token = await client.owner.getToken(process.env.USERNAME, process.env.PASSWORD)

  console.log(token.accessToken)

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


  const result = await req.post('https://bpmruleruntime.cfapps.eu10.hana.ondemand.com/rules-service/rest/v2/workingset-rule-services', {
    json: {
      RuleServiceId: "8ebbf08e24f84790a99f1e8f32be59a9",
      Vocabulary: [
        {
          flightinfo: {
            carrier: "LH"
          }
        }
      ]
    }
  })

  console.log(result)


}

main()

You can get a good idea of what each part of this does by watching this section of the recording, but here’s a brief rundown too:

  • We use a set of const declarations to bring in the two modules we want to use (client-oauth2 and request-promise), and to initialise a new client-oauth2 object that has the client ID and secret set, along with the authorisation endpoint, ready for calls to be made (this set of ID and secret is now no longer in existence, in case you’re wondering).
  • As we’re using a promise-based request mechanism, we can wrap the actual calls inside a function main that’s declared as asynchronous, and then call it right at the bottom of the script (with main()) when everything is ready. This reminds me a little of the Python pattern involving __name__ == "__main__", but that’s a story for another time I guess. Anyway, that allows us to make the actual calls in a synchronous fashion, using await.
  • Inside the main function we pass the user credentials (remember this is the ‘resource owner password credentials’ grant type flow) in the call to actually request a token (getToken). We get the credential values from environment variables so you don’t see them in the script.
  • Assuming that a token is returned successfully (remember, this is just demo code and not robust!) we make an HTTP POST request with the request-promise module, having first set some defaults for requests with the request.defaults call, setting the bearer token in an Authorization header.
  • The request-promise’s post mechanism is very convenient, allowing us to send JSON directly via the json parameter, which we do, supplying the same payload as we’ve seen already to invoke the business rule.

Unfortunately (or fortunately, if you like live streams that end with some suspense!) I forget something small but fundamental in the code, which we don’t manage to catch before the hour is up. For those of you following this in the future, I’ll give you a clue – it’s in the call to request.defaults, and I will cover it in Ep.52 which at the time of writing this post is tomorrow! 🙂

That wrapped it up for this episode. Tune in for the next one for some more (hopefully exciting) hands-on stuff with SAP development!

Assigned Tags

      Be the first to leave a comment
      You must be Logged on to comment or reply to a post.