Amnon_Gibly | 2019-11-22 15:20:04 UTC | #1
This must be done server side on behalf of a call user.
Description: We would like when a user uses our application gets to the Credit Card entry screen for the system to automatically stop recording. On our old platform the system does it.
So Far: We have been instructed to do this by issuing a series of calls: Authenticate using our Application clientid and secret Get user (using bearer token) by their EMail (data point the system has for the user) Get the UserID (using bearer token) and then see if there is a conversation in progress. If so get the conversation id.
This is where we are stuck: To issue the stop recording we need the client bearer token and we are not able to get it to the server. The documentation we got is client side with a call back which will not work for us.
Questions?
- If the UserID is persistent, Can we as we add new users record their client ID in our database and thus save a step?
- Is the same true for their Authorization to be also persistent and also be saved in our database?
This is a partial code we currently use. Can you please chart the steps needed to stop and start recording server side?
Region "GENESYS"
Public Sub do_GENESYSGET(ByRef strIn As String, ByRef sbResponse As StringBuilder, ByRef sbError As StringBuilder) Dim stbRequest As New StringBuilder Dim colHeaders As New System.Collections.Specialized.NameValueCollection Dim strToken = Utils.GetConfig(BID, "GENToken") Dim strAction = Utils.GetItem(strIn, "Action").ToUpper
Dim stbProcessed As New StringBuilder Dim strResult As String = "" Dim strURL As String = Utils.GetConfig(BID, "GENUrl") Dim strClientID As String = Utils.GetConfig(BID, "GENClientID") Dim strSecret As String = Utils.GetConfig(BID, "GENSecret") Dim xDoc As New XmlDocument Dim iPos As Integer = 0, iPos1 As Integer = 0 Dim strOutbox As String = 0, strSentPhone As String = "", strSentMessage As String = "", strStatus As String = ""
'<?xml version="1.0"?> '<Request transtype="misc.GENESYSGET"> ' <BID>AVA</BID> ' <Action>TOKEN</Action> '</Request>
'<Request transtype="misc.GENESYSGET"> ' <BID>AVA</BID> ' <Action>USERBYID</Action> ' <UserID>8d42a969-c00d-41dc-8af6-c66ae96b89b1</UserID> '</Request> '<Request transtype="misc.GENESYSGET"> ' <BID>AVA</BID> ' <Action>USERBYEMAIL</Action> ' <Email>tpipano@gate1travel.com</Email> '</Request> '<Request transtype="misc.GENESYSGET"> ' <BID>AVA</BID> ' <Action>USERLIST</Action> '</Request>
'<Request transtype="misc.GENESYSGET"> ' <BID>AVA</BID> ' <Action>RECORDSTOP</Action> ' <Email>tpipano@gate1travel.com</Email> '</Request> '<Request transtype="misc.GENESYSGET"> ' <BID>AVA</BID> ' <Action>RECORDSTART</Action> ' <Email>tpipano@gate1travel.com</Email> '</Request>
If strURL = "" Or strClientID = "" Or strSecret = "" Then sbError.Append("do_GENESYSGET not configured for this server") Exit Sub End If
Try 'Log the strIn Log.Append("GENESYSGET strIn======================================================================") Log.Append(strIn)
If strToken = "" Or strAction = "TOKEN" Then 'get token Try Dim bAuth = Encoding.UTF8.GetBytes(strClientID & ":" & strSecret) colHeaders.Add("Content-Type", "application/x-www-form-urlencoded") colHeaders.Add("Accept", "application/json") colHeaders.Add("Authorization", "Basic " & System.Convert.ToBase64String(bAuth))
stbRequest.Append("granttype=clientcredentials")
Log.Append("Sending the Request =======================================================") Log.Append(stbRequest.ToString()) strResult = Utils.SendHTTPRequest(Utils.GetConfig(BID, "GENLoginUrl"), stbRequest.ToString(), "POST", 10000, colHeaders,,,, "TLS12") Log.Append("Raw API Response =======================================================") Log.Append(strResult) '{"accesstoken":"PNGNcotScnSN4RlU939tM77POhbKhVzo2y49Xfg6jWGOQTbSuEoHdSKzwfu3z5xdpg6gUmHEBF7YrQJZIz4A","tokentype":"bearer","expiresin":86399} If strResult.StartsWith("Error") Then sbError.Append("doGENESYSGET " & strResult) Exit Sub End If
'Convert from JSON to XML so we can loop through correctly xDoc = JsonConvert.DeserializeXmlNode(strResult.Replace("/", "_"), "Result") strResult = xDoc.InnerXml.Replace("_", "/")
Log.Append("Raw to XML Convert =======================================================") Log.Append(strResult)
strToken = Utils.GetItem(strResult, "accesstoken") If strToken = "" Then sbError.Append("doGENESYSGET could not get Token") Exit Sub End If
sbResponse.Append("<Success>Y</Success><Token>" & strToken & "</Token>")
Catch ex As Exception Log.Append("-------------GENESYSGET fail expression-------------") Log.Append(ControlChars.CrLf & ex.ToString & ControlChars.CrLf) Log.Append("-------------END GENESYSGET fail expression-------------") Dim this As String this = ex.ToString() Throw New Exception("do_GENESYSGET() Failed." & ControlChars.CrLf & ex.ToString) End Try
End If '================================================================================================= If (strAction & " ").Substring(0, 4) = "USER" Or (strAction & " ").Substring(0, 6) = "RECORD" Then Try stbRequest = New StringBuilder colHeaders = New System.Collections.Specialized.NameValueCollection
colHeaders.Add("Content-Type", "application/json") colHeaders.Add("Accept", "application/json") colHeaders.Add("Authorization", "Bearer " & strToken)
Log.Append("Sending the Request =======================================================") Select Case strAction Case "USERLIST" Log.Append(stbRequest.ToString()) strResult = Utils.SendHTTPRequest(strURL & "api/v2/users", stbRequest.ToString(), "GET", 10000, colHeaders,,,, "TLS12") Case "USERBYID" Dim strUser As String = Utils.GetItem(strIn, "UserID") If strUser = "" Then sbError.Append("doGENESYSGET UserID is required") Exit Sub End If Log.Append(stbRequest.ToString()) strResult = Utils.SendHTTPRequest(strURL & "api/v2/users/" & strUser, stbRequest.ToString(), "GET", 10000, colHeaders,,,, "TLS12") Case "USERBYEMAIL", "RECORDSTOP", "RECORDSTART" Dim strEmail As String = Utils.GetItem(strIn, "Email") If strEmail = "" Then sbError.Append("doGENESYSGET User email is required") Exit Sub End If stbRequest.Append("{ ""pageSize"": 25, ""pageNumber"": 1, ""types"": [ ""users"" ], ""sortOrder"": ""SCORE"", ""query"": [ { ""type"": ""EXACT"", ""fields"": [ ""email"" ], ""operator"": ""AND"", ""value"": """ & strEmail & """ } ]}") Log.Append(stbRequest.ToString()) strResult = Utils.SendHTTPRequest(strURL & "api/v2/search", stbRequest.ToString(), "POST", 10000, colHeaders,,,, "TLS12") '{"total":1,"pageCount":1,"pageSize":25,"pageNumber":1,"currentPage":"/api/v2/search?q64=H4sIAAAAAAAAACWOTQuCQBRF8tbS2DQxlViE66UdIIiQiZ92cDY2HwIKv73ZnR5uedw7wxm7FFD9ACrUWl4BqClMrlqUEEEZZIXBAL4WVSjo-aVdwW5xQl1xZujaFYfO8aF9wcmrEfYxLXg3bYMoOhUWxAsatl5yzZo2JG-oU4O8HirJ61WPLJifvDljLbvfyJ0F1CpuoP3aYvV1LcK5qSrDoTmqSwAFEyCNxgAAAA%3D%3D","types":["users"],"results":[{"guid":"8d42a969-c00d-41dc-8af6-c66ae96b89b1","type":"users"}]} Dim strUser = Utils.GetJSONString(strResult, "guid") If strUser = "" Then sbError.Append("doGENESYSGET User not found") Exit Sub End If If strAction = "USERBYEMAIL" Then Log.Append(stbRequest.ToString()) strResult = Utils.SendHTTPRequest(strURL & "api/v2/users/" & strUser, stbRequest.ToString(), "GET", 10000, colHeaders,,,, "TLS12") Else 'stop start recording... we needed the UserID 'now get conversationid if there is one in progress Dim strFDate As String = Format(Now, "yyyy-MM-ddThh:mm:ss.000Z") Dim strTDate As String = Format(Now, "yyyy-MM-ddThh:mm:ss.000Z") strFDate = "2019-11-12T01:00:04.000Z" strTDate = "2019-11-12T23:00:04.000Z" stbRequest = New StringBuilder stbRequest.Append("{ ""interval"": """ & strFDate & "/" & strTDate & """, ""order"": ""asc"", ""orderBy"": ""conversationStart"", ""paging"": { ""pageSize"": 25, ""pageNumber"": 1 }, ""segmentFilters"": [ { ""type"": ""and"", ""predicates"": [ { ""type"": ""dimension"", ""dimension"": ""userId"", ""operator"": ""matches"", ""value"": """ & strUser & """ }, { ""type"": ""dimension"", ""dimension"": ""segmentEnd"", ""operator"": ""notExists"", ""value"": null } ] } ]}") Log.Append(stbRequest.ToString()) strResult = Utils.SendHTTPRequest(strURL & "api/v2/analytics/conversations/details/query", stbRequest.ToString(), "POST", 10000, colHeaders,,,, "TLS12") Dim strConv = Utils.GetJSONString(strResult, "conversationId") If strConv = "" Then sbError.Append("doGENESYSGET No active conversation found") Exit Sub End If stbRequest = New StringBuilder If strAction = "RECORDSTOP" Then stbRequest.Append("{ ""recordingState"": ""paused"" }") Log.Append(stbRequest.ToString()) strResult = Utils.SendHTTPRequest(strURL & "api/v2/conversations/calls/" & strConv, stbRequest.ToString(), "POST", 10000, colHeaders,,,, "TLS12") Else stbRequest.Append("{ ""recordingState"": ""active"" }") Log.Append(stbRequest.ToString()) strResult = Utils.SendHTTPRequest(strURL & "api/v2/conversations/calls/" & strConv, stbRequest.ToString(), "POST", 10000, colHeaders,,,, "TLS12") End If If strResult.StartsWith("Error") Then sbError.Append("doGENESYSGET " & strResult) Exit Sub End If
End If End Select
Log.Append("Raw API Response =======================================================") Log.Append(strResult) '{"accesstoken":"PNGNcotScnSN4RlU939tM77POhbKhVzo2y49Xfg6jWGOQTbSuEoHdSKzwfu3z5xdpg6gUmHEBF7YrQJZIz4A","tokentype":"bearer","expiresin":86399} If strResult.StartsWith("Error") Then sbError.Append("doGENESYSGET " & strResult) Exit Sub End If
'Convert from JSON to XML so we can loop through correctly xDoc = JsonConvert.DeserializeXmlNode(strResult.Replace("/", "_"), "Result") strResult = xDoc.InnerXml.Replace("_", "/")
Log.Append("Raw to XML Convert =======================================================") Log.Append(strResult)
sbResponse.Append("<Success>Y</Success>" & strResult)
Catch ex As Exception Log.Append("-------------GENESYSGET fail expression-------------") Log.Append(ControlChars.CrLf & ex.ToString & ControlChars.CrLf) Log.Append("-------------END GENESYSGET fail expression-------------") Dim this As String this = ex.ToString() Throw New Exception("do_GENESYSGET() Failed." & ControlChars.CrLf & ex.ToString) End Try
End If
Catch ex As Exception 'Something failed!! 'Clear the response sbResponse.Length = 0 'Append the error to the sbError stringbuilder sbError.Append(ex.ToString()) 'Log the error Log.Append("do_GENESYSGET() failed: ======================================") Log.Append(ex.ToString()) Finally
If sbError.Length > 0 Then sbError.Append("<Success>N</Success>") End If
'Dump if opted to Log.Dump("do_GENESYSGET", "GENESYSGET", System.Guid.NewGuid.ToString()) End Try End Sub
End Region
tim.smith | 2019-11-21 16:09:39 UTC | #2
Amnon_Gibly, post:1, topic:6531
Authenticate using our Application clientid and secret Get user (using bearer token) by their EMail (data point the system has for the user) Get the UserID (using bearer token) and then see if there is a conversation in progress. If so get the conversation id.
This is incorrect. For a server-side app that requires a user context, you need to implement the Authorization Code OAuth Flow. The flow, as described on that page, is for the user to be directed to the PureCloud authorization page to authenticate, then they will be redirected to the callback url, which will include an auth code. Your web application should then pass the auth code code to the backend service where it can exchange the auth code for an auth token that can be used to make requests on behalf of the user.
Amnon_Gibly, post:1, topic:6531
If the UserID is persistent, Can we as we add new users record their client ID in our database and thus save a step?
You don't need to know the user's ID to complete the OAuth flow. Once your service has obtained an auth token, you can retrieve information about the user that authorized your service by making a request to GET /api/v2/users/me.
Amnon_Gibly, post:1, topic:6531
Is the same true for their Authorization to be also persistent and also be saved in our database?
Storing auth tokens in a database generally isn't a good idea for security reasons; they should generally only be stored in memory. But if your architecture requires it, you can. But keep in mind that tokens expire, so your app will need to be able to handle reauthorizing the user when necessary.
Amnon_Gibly, post:1, topic:6531
This is a partial code we currently use. Can you please chart the steps needed to stop and start recording server side?
Looks like you're using visual basic. You may find the ASP.NET auth code grant tutorial useful, as well as the .NET SDK. The tutorials and SDK examples are in C#, but the concepts are the same in VB.
Amnon_Gibly | 2019-11-22 02:58:49 UTC | #3
Thank you. This process however forces our users to login twice, once to our application and then again to authenticate against PureCloud. That is not optimal. Is there a server side API that will allow us to sign in on behalf of the user and receive the authorization token in the response? Could we do it with screen scraping if needed? the problem is the call back piece. There is no relationship between the user session and some url receiving the response.
tim.smith | 2019-11-22 15:19:41 UTC | #4
Amnon_Gibly, post:3, topic:6531
This process however forces our users to login twice, once to our application and then again to authenticate against PureCloud.
This is correct and intentional. Your app cannot act on behalf of a user until the user authorizes it. If they are already authenticated in the same browser (e.g. logged in to the PureCloud UI) and an auth cookie is present, your auth flow will be authenticated automatically and the auth flow will complete without the user being prompted for credentials.
Alternatively, if you're using a single sign on provider, that provider may make it possible to use the SAML2 Bearer grant without prompting the user to authenticate. But that's entirely dependent on the auth provider's process and is outside of PureCloud's control.
Amnon_Gibly, post:3, topic:6531
Is there a server side API that will allow us to sign in on behalf of the user and receive the authorization token in the response?
No. The user must personally authenticate and authorize the app. This is the intended use case for the Auth Code grant.
Amnon_Gibly, post:3, topic:6531
Could we do it with screen scraping if needed?
Attempting to bypass user authentication constitutes abuse of the platform. I'd strongly recommend against violating security rules as it could result in suspension of your PureCloud account.
Amnon_Gibly, post:3, topic:6531
the problem is the call back piece. There is no relationship between the user session and some url receiving the response.
I'd suggest making the redirect page one that is aware of your app's user's session. That way your app can receive the auth code on the front end and pass it to the back end in the context of the user session.
You can also use the state parameter to pass an arbitrary string with the initial login redirect that will be returned to you at the callback URL. You can provide some identifier in this data that would be useful to you to relate the initial request to the response.
Amnon_Gibly | 2019-11-22 16:02:19 UTC | #5
Thank you. A follow-up question related to your comment about the cookie. You mentions: If they are already authenticated in the same browser (e.g. logged in to the PureCloud UI) and an auth cookie is present, your auth flow will be authenticated automatically and the auth flow will complete without the user being prompted for credentials.
Is it possible for our application to read this cookie and extract the user bearer token from it? If the entering of user purecloud credentials may be bypassed in subsequent sessions, we will not have an opportunity to use the call back feature for new sessions?
Amnon
tim.smith | 2019-11-22 16:05:20 UTC | #6
Amnon_Gibly, post:5, topic:6531
Is it possible for our application to read this cookie and extract the user bearer token from it?
No. The cookie does not allow cross-domain access, nor does it contain the bearer token anyway.
Amnon_Gibly, post:5, topic:6531
If the entering of user purecloud credentials may be bypassed in subsequent sessions, we will not have an opportunity to use the call back feature for new sessions?
The login process isn't bypassed, it's completed automatically. Your app cannot distinguish between when a user entered credentials vs. when the auth cookie was used to automatically authenticate the user. The flow is literally exactly the same in both cases.
Amnon_Gibly | 2019-11-23 06:22:30 UTC | #7
I am trying:
https://login.mypurecloud.com/oauth/authorize?client_id=2562f350-1af3-4492-83d3-209f05f886c8&response_type=token&redirect_uri=http://www.jerom.com:8080/rafael/token.jsp&state=12345678901234567890
and I keep getting an error:
Sorry, PureCloud cannot authenticate you at this time.
We did not recognize your redirect url.
Please try again and notify your system administrator if the problem persists
What is wrong?
Amnon
Amnon_Gibly | 2019-11-25 16:09:42 UTC | #8
Any answer on this. Keep getting "we did not recognize your redirect url"
This is a valid URL: http://www.jerom.com:8080/rafael/token.jsp
tim.smith | 2019-11-25 17:10:51 UTC | #9
Amnon_Gibly, post:7, topic:6531
We did not recognize your redirect url.
This means that the value you sent for redirect_uri doesn't match the values configured for the specified client_id. The value sent must match one of the configured values exactly, character for character.
Amnon_Gibly | 2019-11-26 20:39:27 UTC | #10
I am not sure where to check this. The OAuth setup does not have a field for it.
SCREENSHOT REDACTED - Don't post client secrets!
tim.smith | 2019-11-26 20:37:58 UTC | #11
Wrong grant type.
quote="tim.smith, post:2, topic:6531"] For a server-side app that requires a user context, you need to implement the [Authorization Code OAuth Flow. [/quote]
tim.smith | 2019-11-26 20:38:56 UTC | #12
Also, please regenerate your secret immediately. By posting your client secret, literally anyone on the internet can now authenticate using that client and has access to your org.
Amnon_Gibly | 2019-11-27 07:13:47 UTC | #13
I can now successfully use the user token. However, using the api/v2/conversations/calls/ with the conversation number does not seem to affect the red dot on the client. It looks like the recording is continuous.
This is the payload: { ""recordingState"": ""paused"" }
I do get a result which indicates "recordingState":"PAUSED"
But the recording continues on the client.
tim.smith | 2019-11-27 14:51:47 UTC | #14
I believe it's still going to show as recording. Have you checked the recording to see if it was actually paused or not?
If you're getting a successful response to the API and the recording is not being paused, please open a case with PureCloud Care to investigate why the pause command is successful but not working. This information you've provided appears correct per the documentation.
Amnon_Gibly | 2019-12-03 20:56:02 UTC | #15
The good news I go it to work when placing a call through the browser. The user is redirected to issue an authorization which the server converts to a token and then able to start and stop the recording.
HOWEVER, our users are using the Pure Cloud client on desktop as well as the app on their cell phones. How do we get an authorization to be able to stop their recording. This is urgent and important.
tim.smith | 2019-12-03 21:01:38 UTC | #16
The user must authorize in whatever context (browser, cellphone, etc) your app sends the redirect request. If they're already authenticated in that context, the auth process will complete immediately. If they are not already authenticated in that context, they will need to enter their credentials.
You could embed your app in the desktop client using a Custom Client Application so it will be displayed within PureCloud and the user will always be able automatically authenticate.
system | 2020-01-03 21:12:15 UTC | #17
This topic was automatically closed 31 days after the last reply. New replies are no longer allowed.
This post was migrated from the old Developer Forum.
ref: 6531