Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consuming Services - How to use Service Binding based Destination access with multitenancy? #5157

Open
KarthikeyanLoganathan opened this issue Nov 8, 2024 · 1 comment
Labels
question Further information is requested

Comments

@KarthikeyanLoganathan
Copy link

Refer to SAP internal CAP issues/issues/17158

I managed to resolve service binding destination, handling xsuaa uaasubdomain of the consumer tenant, and client credentials flow for the consumer tenant

    async resolveDestinationByServiceInstanceName(cds, serviceInstanceName, tenantContext) {
        //TODO: This only supports client credentials flow
        if (cds.env?.production) {
            // need request context here.  we need tenant-id
            // console.log('cds.User: ', JSON.stringify(that.cds.User)); //id, roles, attr, tokenInfo
            // console.log('req: ', JSON.stringify(req));
            // console.log('req.authInfo: ', JSON.stringify(req.authInfo));
            //TODO: decide about caching?
            //TODO: What about time-bound caching?
            //TODO: What about user specific tokens?
            // https://sap.github.io/cloud-sdk/docs/js/features/connectivity/destinations#service-binding-environment-variables
            const destinationData = await destinationForServiceBinding(serviceInstanceName,
                {
                    /**
                     * @param {import('@sap-cloud-sdk/core').ServiceBinding} serviceBinding 
                     * @returns {import('@sap-cloud-sdk/core').Destination} Destination
                     */
                    serviceBindingTransformFn: async (serviceBinding) => {
                        //TODO: customer UAA subaccount sub-domain to be used as prefix to UAA URL, for 
                        //customer tenant specific JWT token
                        //Probably we are resolving the destination too early here
                        //At this point when server is loaded/up, you dont have end user context
                        //Only in customer tenant context, we should resolve the destination
                        //Then only it will work for all customer tenants.
                        console.log("serviceBindingTransformFn called now");
                        let uaaSubDomain = serviceBinding.credentials.uaa.identityzone;
                        const uaaCredentials = {};
                        Object.assign(uaaCredentials, serviceBinding.uaa);
                        if (tenantContext?.req) {
                            const inputToken = new xssec.XsuaaToken(tenantContext?.req?.headers?.authorization?.split(/^bearer /i)[1]);
                            uaaSubDomain = inputToken.payload.ext_attr.zdn;
                        } else if (tenantContext?.uaaSubDomain) {
                            uaaSubDomain = tenantContext?.uaaSubDomain;
                        }
                        uaaCredentials.url = `https://${uaaSubDomain}.${uaaCredentials.uaadomain}`;
                        const uaaService = new xssec.XsuaaService(uaaCredentials);
                        return {
                            name: serviceInstanceName,
                            authentication: 'OAuth2ClientCredentials',
                            clientId: uaaCredentials.clientid,
                            clientSecret: uaaCredentials.clientsecret,
                            url: serviceBinding.credentials.url,
                            tokenServiceUrl: uaaCredentials.url + '/oauth/token',
                            authTokens: [await uaaService.fetchClientCredentialsToken()].map(mapJWT)
                        }
                    }
                }
            )
            await registerDestination(destinationData, {
                useCache: false,
                IsolationStrategy: 'tenant'
            });
        }
    }

    async resolveDestinationByServiceName(cds, serviceLabel, tenantContext) {
        if (cds.env?.production) {
            const services = xsenv.filterServices({ label: serviceLabel });
            if (services.length === 0) {
                throw new Error(`Application has no service binding for label ${serviceLabel}`);
            }
            await this.resolveDestinationByServiceInstanceName(cds, services[0].name, tenantContext);
        }
    }
    this.connectToOdataService = async function (tenantContext) {
        if (rdaFacade.rdaConfig.applicationType === rdaGlobals.ApplicationTypes.consumer) {
            await rdaFacade.resolveDestinationByServiceInstanceName(cds, "rda-consumer-product-rating-rda-service-instance", tenantContext);
        }
        return await cds.connect.to(serviceInfo.serviceId);
    };

the jwt token also has expiry information for sure.

I naturally expected destination service to obtain token, cache the token till expiry etc. (which currently I have coded for token fetch)

Can this work out of the box from cloud sdk or destination service?

Can we have such tenant specific service binding destinations coded for dynamic fetch of jwt, automatic caching?

@KarthikeyanLoganathan KarthikeyanLoganathan added the question Further information is requested label Nov 8, 2024
@deekshas8
Copy link
Contributor

deekshas8 commented Nov 13, 2024

Hi @KarthikeyanLoganathan ,

You can take a look at our documentation of service bindings here. The SDK supports transformation of service bindings of certain types, for others you can write a custom transformation function. The jwt and useCache options make multi-tenancy and caching possible

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants