btp-cap-multitenant-saas icon indicating copy to clipboard operation
btp-cap-multitenant-saas copied to clipboard

How to run background jobs which require Principal Propagation to SAP Backend?

Open codeyogi911 opened this issue 1 year ago • 10 comments

I have a use case where the background job needs to access the backend system via destinations in a multi tenant app. How can i achieve this? Basically i want to run a background job with a technical user

codeyogi911 avatar Sep 09 '24 05:09 codeyogi911

Hi @codeyogi911,

cds.spawn should help you with your use case as described here.

cds.spawn ({ tenant:'t0', every: 1000 /* ms */ }, async (tx) => {
  const mails = await SELECT.from('Outbox')
  await MailServer.send(mails)
  await DELETE.from('Outbox').where (`ID in ${mails.map(m => m.ID)}`)
})

alperdedeoglu avatar Sep 09 '24 07:09 alperdedeoglu

@alperdedeoglu I am aware about how to use cds.spawn, what I want to find out is how to run with a jwt in the background?

codeyogi911 avatar Sep 09 '24 10:09 codeyogi911

@codeyogi911 What kind of SAP Backend are we talking about? Can you explain the exact use case you have? If that is S/4HANA Private Cloud or on-premise, you need to get the token for destination service on behalf of your tenant. You can do this each time when your job gets executed. I am having hard times understanding what is the actual problem you are having.

alperdedeoglu avatar Sep 09 '24 12:09 alperdedeoglu

I have an SAP Onprem system connected to our multi-tenant app. We are running in the background a Auto Sales Order job. When connecting to the backend API it fails because there is no jwt. Is there a way to configure and use technical user for each tenant? What i did currently is get the jwt using password_grant (which is not correct) and created a cds.user using this token and @sap/xssec. I am then using this user in the cds.spawn. I feel like its a work around and there should be a way to configure and use background users.

codeyogi911 avatar Sep 09 '24 15:09 codeyogi911

Where exactly do you get your JWT from? Do you mean the XSUAA? By Backend API do you mean CAP API or S/4 API?

alperdedeoglu avatar Sep 09 '24 15:09 alperdedeoglu

Right now as a workaround i get the jwt from the bound XSUAA using a password_grant. Backend API means S4 API using Destinations and Cloud Connector.

codeyogi911 avatar Sep 10 '24 08:09 codeyogi911

Right now as a workaround i get the jwt from the bound XSUAA using a password_grant. Backend API means S4 API using Destinations and Cloud Connector.

And are you using Cloud SDK or something to connect to the backend? Or have you defined a destination? How do you call the on-premise backend? Which library do you use?

alperdedeoglu avatar Sep 10 '24 11:09 alperdedeoglu

I have my function to create sales order defined like:

async function createSalesOrder(orderData) {
  LOGGER.info("Creating sales order: ", cds.context.user)
  const salesSrv = await cds.connect.to("API_SALES_ORDER_SRV", {
    destinationOptions: {
      jwt: cds.context.user.tokenInfo.getTokenValue(),
    }
  })
  const result = await salesSrv.create("A_SalesOrder").entries(orderData)
  LOGGER.info("Sales order created", result.SalesOrder)
  return result
}

earlier i had it defined like:

async function createSalesOrder(orderData) {
  let salesSrv
  if (cds.context.isPrivilegedSpawn) {
    const subDomain = await getTenantSubdomain(cds.context.tenant)
    LOGGER.info("Subdomain for tenant", subDomain)
    LOGGER.info("Creating sales order for tenant using basic authentication", cds.context.tenant)
    salesSrv = await cds.connect.to("API_SALES_ORDER_SRV", {
      credentials: {
        destination: "onpremise-basic",
        path: "/sap/opu/odata/sap/API_SALES_ORDER_SRV",
      },
      destinationOptions: {
        selectionStrategy: "alwaysSubscriber",
        iss: `https://${subDomain}.authentication.eu10.hana.ondemand.com/oauth/token`,
        useCache: true,
      },
    })
  } else {
    salesSrv = await cds.connect.to("API_SALES_ORDER_SRV")
  }
  const result = await salesSrv.create("A_SalesOrder").entries(orderData)
  LOGGER.info("Sales order created", result.SalesOrder)
  return result
}

so this stopped working after the upgrading to CDS8, it somehow now gives me 401 when using onpremise-basic and even if i try in the foreground then also it gives me 401, even though it should use the onpremise in foreground. Somehow the destination is not getting cached even after setting

"API_SALES_ORDER_SRV": {
        "kind": "odata-v2",
        "model": "srv/external/API_SALES_ORDER_SRV",
        "csrf": true,
        "csrfInBatch": true,
        "[production]": {
          "credentials": {
            "destination": "onpremise",
            "path": "/sap/opu/odata/sap/API_SALES_ORDER_SRV"
          },
          "destinationOptions": {
            "selectionStrategy": "alwaysSubscriber",
            "useCache": false
          }
        }
      },

codeyogi911 avatar Sep 11 '24 01:09 codeyogi911

Ok got it, can you just give the jwt with retrieving a token via client_credentials and let me know how it goes?

alperdedeoglu avatar Sep 16 '24 07:09 alperdedeoglu

Hi @codeyogi911, did that work for you?

alperdedeoglu avatar Sep 24 '24 09:09 alperdedeoglu