While invoking 'patchCallsCallIdParticipantsParticipantId' api using OAuthClientRequest we are getting below error. Where as same is works for 'patchUserIdPresencesSourceId' api. could you please let me know how to execute hold api using client credentials?
Stack Trace: Exception when calling ConversationsApi#patchCallsCallIdParticipantsParticipantId com.mypurecloud.sdk.ApiException: {"status":403,"code":"forbidden","message":"Access to Conversation 3b55e190-f806-48cc-8b54-6beb52422fc5 is forbidden.","contextId":"60e39188-a2a5-4eb1-a308-e41cb3953041","details":[],"errors":[]} at com.mypurecloud.sdk.ApiClient.invokeAPI(ApiClient.java:657) at com.mypurecloud.sdk.api.ConversationsApi.patchCallsCallIdParticipantsParticipantId(ConversationsApi.java:1650) at com.asurion.pss.pathfinder.telephony.business.inin.InInTelephonyService.holdResumeCall(InInTelephonyService.java:94) at com.asurion.pss.pathfinder.telephony.controllers.TelephonyController.holdCall(TelephonyController.java:57) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:817) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:731) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:968) at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:870) at javax.servlet.http.HttpServlet.service(HttpServlet.java:648) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:844) at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:292) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207) at org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration$ApplicationContextHeaderFilter.doFilterInternal(EndpointWebMvcAutoConfiguration.java:237) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207) at org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:112) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207) at com.asurion.pss.pathfinder.base.infrastructure.http.CorsHttpFilter.doFilter(CorsHttpFilter.java:38) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207) at com.asurion.pss.pathfinder.base.infrastructure.http.HttpLogFilter.doFilter(HttpLogFilter.java:64) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207) at com.asurion.pss.pathfinder.base.infrastructure.http.CarrierNameExtractHttpFilter.doFilter(CarrierNameExtractHttpFilter.java:45) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207) at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207) at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:87) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207) at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:121) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207) at org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:103) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:522) at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1095) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:672) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1500) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1456) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:745)
There is a three part reason that this doesn't work:
The requesting user must own the conversation (it's on their queue)
All conversation resources require a user context to perform the operation
Client Credentials do not have a user context
There is a BUT:
There are permissions for conversation:participant:wrapup, conversation:communication:disconnect, and conversation:communication:transfer that can be granted to a role that will allow you to do those actions (and only those actions) to conversations. Because those permissions belong to a role, they can apply to client credentials.
Regarding the presence resource, that does not require a user context.
I work with Murugan at Asurion. We are interested in using client credentials for call control because we are building a call center application (our java/dart application). In fact - this application is already built and running - with another telephony vendor. We have been tasked with integrating our application with InIn as a telephony provider in the same way we integrate with another 'big name' vendor. Our issue is - to do this - we will need to make method calls like this:
Browser -> Our Java Service Layer -> InIn pure cloud API
For methods like presence and - very important to us - call controls. Hold, resume, transfer.
We don't want our telephony users to have to be redirected to InIn to authorize with Auth Code OAuth for a variety of reasons - mostly that we want to replicate the UX we have now and abstract away from the underlying telephony provider.
Can you suggest how we could meet this need? Is it possible to request an enhancement to get additional call controls allowed with client credentials - we could provide the userId and conversationId as parts of the method call.
Unfortunately, there's no way to perform user actions without authenticating as a user. You must use either the Implicit or Auth code grant types, which require the user to authenticate securely with the PureCloud login page.
If you do not wish to have the users authenticate with PureCloud credentials, you can implement one of the Single Sign On providers to allow the users to authenticate with 3rd party credentials.
It would be useful if you could post an explanation of your application and use case in the API Enhancement Requests category. This will help provide us with more information about why customers are asking for features that we can use when prioritizing new features.
Yes - the concept of auth code oauth makes sense - but we don't have that same issue with the other provider, and we are trying to replicate the UI. One of the key items we'd like is for our end users to not have to know that InIn exists. We'd have admins setup the technician's systems and they would never know their telephony user/password - we have our own user password management based on AD. We don't want the technicians to have to be aware of the telephony backend credentials.
If we were to use SSO, would we be able to obtain some sort of SSO based authentication - and then hand a token to our java services from the browser? Or is it possible for the server to manage the SSO?
We are several weeks into our design and prototyping assuming this would work.
Wondering - what is a realistic lead time to get this added as a feature if we request it?
It is possible to avoid the PureCloud login screen entirely, if the organization you wish to authenticate with is known and a third party identity provider is configured for that organization. Provided those conditions are met, the authorization server supports authentication directives at the authorization endpoint. Namely, the 'org' and 'provider' query parameters can be used to short circuit the identity provider selection process.
For example, defining the following:
your oauth client id => your_client_id
your client oauth callback => https://example.com/oauth/callback
target organization => acme
target identity provider => adfs
An authorization redirect would look like this (newlines added for clarity): https://login.mypurecloud.com/oauth/authorize?response_type=code& client_id=your_client_id& redirect_uri=https://example.com/oauth/callback& org=acme& provider=adfs
From a users perspective, they would go from your app, to the ADFS login page, back to your app. Unless they are already authenticated with ADFS or PureCloud, in which case they won't have to enter their credentials at all.
To elaborate more on what John is talking about here, you can use the SAML2Bearer OAuth grant type to get an access token from a SAML assertion.
This is a very simple diagram showing which services talk to each other. As you can see in this example the web browser never talks to PureCloud directly.
Here is little more detail in a sequence diagram showing how you might code this up. The first part is a SAML sequence and the bottom half is getting a PureCloud access token from the SAML assertion the user posted to the web service.
Just to further confirm - do we have to use one of the SAML providers that are configured in Admin -> Integrations -> Single Sign-on. Looks like OneLogin, Okta, ADFS and Salesforce. Is there a way to configure another SSO provider - or must we use one of those?
We have verified those SAML providers (plus CIC) and ADFS includes Azure. If you have an additional SAML provider you'd like to use let us know. They are usually straightforward to add. Since SAML is such a wide spec we chose to provide specific support for these and add additional providers as needed.
You can checkout the config options for them from the Admin UI or api docs.
I think we are using Ping Federate - which is one of the larger providers. As we already have that integrated - how difficult is it for you guys to add support for Ping?
Ping has been on our list of providers to tackle. While the effort is not significant, it is behind some work that is currently in flight. We pushed it out slightly, but not too far off. I am hopeful we can get to this feature sometime in Q4-2016.
Adding the org parameter to the authorization redirect seems to work fine (I have the org selected at login screen).
But the provider parameter seems to have no effect, the login screen is still presented with org & advanced login options.
I set the provider to adfs.
POST https://login.mypurecloud.ie/oauth/token authorization=basic <base64encode(clientid:clientsecret)> content-type=application/x-www-form-urlencoded grant_type=urn:ietf:params:oauth:grant-type:saml2-bearer assertion=<base64encode(saml_assertion)> orgName=purecloud-france
But I get 400 Bad Request "unauthorized_client". clientid & clientsecret are correct as I use them for OAuth with Postman.
I got the SAML assertion using the Chrome console while login into my PureCloud org with ADFS SSO.
I'm going to be publishing an example implementation of this today. There is one missing step in the instructions. You must first create a new oauth client to use with this. You need to use POST /api/v2/oauth/clients and set "authorizedGrantType":"SAML2BEARER". This grant type can't currently be created via the UI (I'll be logging an issue to get it added).
FWIW, I got this working with Authorization and Content-Type as headers instead of form body parameters.
I successfully created the SAML 2 Bearer token.
Using Authorization & Content-type in headers and grant_type, assertion and orgName in body gives me a 400 Bad Request (Invalid Request).
The assertion has a short lifetime. Make sure you're always using one that's brand new each time.
You can validate the encoding of your assertion using this site (no endorsement of that site, it's just the top google result and it works). The decoded value should be the SAML assertion XML document
I'm not sure if the request you posted above is literally what you're sending (aside from removing your client creds and the assertion). If it is, your form body is invalid. Make sure the values are properly URI encoded and concatenated with an ampersand.