Skip to content

How to establish connection to SAP DataSphere OData API? #779

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

Open
MikeSchernbeck opened this issue Apr 16, 2025 · 10 comments
Open

How to establish connection to SAP DataSphere OData API? #779

MikeSchernbeck opened this issue Apr 16, 2025 · 10 comments
Labels
question Further information is requested

Comments

@MikeSchernbeck
Copy link

Ask the Question

I am trying to connect to the OData API of SAP DataSphere. A destination has been created accordingly in the sub-account, the spring-boot application is running in. Checking the destination is successful. When retrieving an HttpClient, a DestinationAccessException is thrown

Failed to read authentication token of destination 'datasphere'. The destination service responded with an error: 'Unauthorized grant type'.
In case only the properties of a destination should be accessed, without performing authorization flows, please use the 'getDestinationProperties' method on 'DestinationService' instead.

What is missing here to establish a connection?

  public String getDatasphere() {
    try {
      final HttpDestination destination = DestinationAccessor.getDestination("datasphere").asHttp();
      final HttpClient client = HttpClientAccessor.getHttpClient(destination);

      try {
        final HttpResponse response = client.execute(new HttpGet());

        if (response.getStatusLine().getStatusCode() == HttpStatus.OK.value()) {
          final BufferedReader br = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
          final StringBuilder sb = new StringBuilder();

          String line;
          while ((line = br.readLine()) != null) {
            sb.append(line);
          }
          br.close();
          return sb.toString();
        }
      } catch (final IOException e) {
        LOGGER.error(e.getMessage(), e);
        return "IOException: " + e.getMessage() + ": " + Arrays.toString(e.getStackTrace());
      }
    } catch (final DestinationNotFoundException e) {
      LOGGER.error(e.getMessage(), e);
      return "DestinationNotFoundException: " + e.getMessage() + ": " + Arrays.toString(e.getStackTrace());
    } catch (final DestinationAccessException e) {
      LOGGER.error(e.getMessage(), e);
      return "DestinationAccessException: " + e.getMessage() + ": " + Arrays.toString(e.getStackTrace());
    }

    return null;
  }
@MikeSchernbeck MikeSchernbeck added the question Further information is requested label Apr 16, 2025
@rpanackal
Copy link
Member

Hey @MikeSchernbeck,

You seem to be accessing the destination correctly.

May I ask you to double-check your destination configuration, especially the fields related to authentication? The error message clearly indicates something is wrong with the grant type.

If you’d like, feel free to provide us with a screenshot of your destination configuration so we can take a look. Just remember to blur or obscure any sensitive information, as this is a public repo.

Best regards,
Roshin

@MikeSchernbeck
Copy link
Author

This is the requested screenshot of the destination:

Image

@rpanackal
Copy link
Member

Thanks for the prompt response. Here the configuration of destination looks fine.

I would like to ask you to try out an authentication request via Bruno/Postman and see if it succeeds.

@MatKuhr
Copy link
Member

MatKuhr commented Apr 16, 2025

The destination service responded with an error: 'Unauthorized grant type'

Indicates that the OAuth server you are using in the destination declined the request. But assuming this OAuth server issued the credentials you are using here, the OAuth server should accept the client credentials grant type.

Try checking the Use Basic credentials for Token Service box. Some OAuth servers are picky about the format in which they receive it.

If that doesn't help, contact the BDC team or whoever is responsible for this OAuth server.

@MikeSchernbeck
Copy link
Author

Thanks for the prompt response. Here the configuration of destination looks fine.

I would like to ask you to try out an authentication request via Bruno/Postman and see if it succeeds.

@rpanackal with bruno, everything seems to work fine:

Image

@MatKuhr
Copy link
Member

MatKuhr commented Apr 17, 2025

Here you are using the authorization code grant type which is meant for business users and a browser-based login. This is typically not suitable for technical communication. And you configured your destination to use the client credentials grant type instead.

I assume with Bruno it fails when changing to client credentials grant type? If so, that would indicate that the client credentials grant type was explicitly disabled by DataSphere. So please reach out to them directly on what grant type to use / how to configure a destination.

@MikeSchernbeck
Copy link
Author

Here you are using the authorization code grant type which is meant for business users and a browser-based login. This is typically not suitable for technical communication. And you configured your destination to use the client credentials grant type instead.

I assume with Bruno it fails when changing to client credentials grant type? If so, that would indicate that the client credentials grant type was explicitly disabled by DataSphere. So please reach out to them directly on what grant type to use / how to configure a destination.

@MatKuhr adding (my) basic credentials to the destination results in a DestinationAccessException (again) having a different message:

DestinationAccessException: Failed to read authentication token of destination 'datasphere'. The destination service responded with an error: 'Bad credentials'.
In case only the properties of a destination should be accessed, without performing authorization flows, please use the 'getDestinationProperties' method on 'DestinationService' instead.

@MikeSchernbeck
Copy link
Author

Here you are using the authorization code grant type which is meant for business users and a browser-based login. This is typically not suitable for technical communication. And you configured your destination to use the client credentials grant type instead.

I assume with Bruno it fails when changing to client credentials grant type? If so, that would indicate that the client credentials grant type was explicitly disabled by DataSphere. So please reach out to them directly on what grant type to use / how to configure a destination.

@MatKuhr
true. sorry i missed that. after changing to client credentials, it fails in bruno. i will reach out to the boys and gils from datasphere

@MikeSchernbeck
Copy link
Author

MikeSchernbeck commented Apr 22, 2025

@MatKuhr @rpanackal

The guys from Datasphere told me, that OAuth 2 Client Credentials are not supported yet. The suggested to switch to OAuth 2 Authorization Code, but after deleting and recreating the destination in BTP, I still receive a DestinationAccessException exception:

DestinationAccessException: Failed to get destination with name 'datasphere'.: 
[com.sap.cloud.sdk.cloudplatform.connectivity.DestinationAccessor.lambda$getDestination$1(DestinationAccessor.java:104), 
io.vavr.control.Try.getOrElseThrow(Try.java:747), 
com.sap.cloud.sdk.cloudplatform.connectivity.DestinationAccessor.getDestination(DestinationAccessor.java:97), 
com.sap.factory_x.sustainability.prototype.data_transfer.controller.Endpoint.getDatasphere(Endpoint.java:119), 
java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103), 
java.base/java.lang.reflect.Method.invoke(Method.java:580), 
org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:257), 
org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:190), 
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118), 
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986), 
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891), 
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87), 
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1088), 
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:978), 
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014), 
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903), 
jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564), 
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885), 
jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658), 
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195), 
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140), 
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51), 
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164), 
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140), 
org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100), 
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116), 
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164), 
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140), 
org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93), 
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116), 
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164), 
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140), 
org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201), 
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116), 
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164), 
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140), 
org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167), 
org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90), 
org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483), 
org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115), 
org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93), 
org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74), 
org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:731), 
org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344), 
org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:397), 
org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63), 
org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:905), 
org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1743), 
org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52), 
java.base/java.lang.VirtualThread.run(VirtualThread.java:329)]

Screenshot of the new destination:

Image

@Jonas-Isr
Copy link
Member

Hi @MikeSchernbeck,

the Cloud SDK does not support authentication via Authorisation Code. We thus recommend to wait until DataSphere provides support for Client Credentials.

If you cannot wait that long, there is probably a way to make Authorisation Codes work through our SDK, but that would include significant custom coding from your side. The idea would basically be to manually change the headers for calls the DestinationAccessor makes to exactly that destination that needs to receive your authorisation code (you would need to get the authorisation code before that on your own). We do not recommend going that way and have not tested it, but it might work. If you still want to try it out, we can provide you with some information on how to change specific headers in the Cloud SDK. But that is basically all we could help you with.

Best,
Jonas

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

4 participants