C# - Using REST API to get list of Processes - 401 Unauthorized

1
+1
-1

I am trying to use Bonita Web API. I My code is below. As you can see I call the loginservice before calling any other API service. It logs in OK 200. But when I make the subsequent call to get the list of processes I get a 401 error. You get a JSESSIONID from the first call and you are suppose to pass it to the subsequent calls to authenticate you. I'm using Bonita 6.3

var baseAddress = new Uri(); var cookieContainer = new CookieContainer(); using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer }) using (var client = new HttpClient(handler) { BaseAddress = baseAddress }) { HttpResponseMessage result = client.PostAsync("/bonita/loginservice", new StringContent("login=,password=,redirect=false")).Result; client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); HttpResponseMessage result2 = client.GetAsync("/bonita/API/bpm/process").Result; result2.EnsureSuccessStatusCode(); } }

3 answers

1
+1
-1

Hi guys,

thanks for the answers, but this actually works out of the box in .NET, there is absolutely no need to set cookies.

I am now using the new httpClient in .Net 4.5 I have created the service layer objects using XamaSofts JSOn to object convertor using cut and paste of the Bonita examples, are from a network monitor. Sure would be much nice if Bonita would provide decent schemas. Also, they really need to clean up their web api, the naming conventions (or more precisly complete lack thereof) are a mess.

Imports System.Net.Http
Imports System.Net.Http.Headers
Imports UZ.BPMN.Datatypes
Imports Newtonsoft.Json

Public Class BPMEngineService
  Inherits HttpClient
  Private username, password As String

  Public Sub New(username As String, password As String)
    DefaultRequestHeaders.Accept.Clear()
    DefaultRequestHeaders.Accept.Add(New MediaTypeWithQualityHeaderValue("application/json"))

    Me.BaseAddress = New Uri("http://localhost:9080")
    Me.username = username
    Me.password = password
  End Sub

  Public Function GetTasks(procs As BPMNProcess()) As BPMNTask()
    Dim result As BPMNTask() = Nothing
    Try
      Login()
      Dim url As String = "/bonita/API/bpm/humanTask?p=0&c=10&o=priority%20DESC&f=state%3dready&f=user_id%3d0"
      Dim response As HttpResponseMessage = GetAsync(url).Result
      Dim reponseData As String = response.Content.ReadAsStringAsync().Result
      HttpContext.Current.Trace.Write(response.ToString)
      HttpContext.Current.Trace.Write(reponseData)
      result = JsonConvert.DeserializeObject(reponseData, GetType(BPMNTask()))
    Catch ex As Exception
      HttpContext.Current.Trace.Write("Error contacting REST service: " & ex.ToString)
    Finally
      Logout()
    End Try
    Return result
  End Function

  Public Function GetProcess(processID As String) As BPMNProcess
    Dim result As BPMNProcess = Nothing
    Try
      Login()
      Dim url As String = "/bonita/API/bpm/process/" & processID
      Dim response As HttpResponseMessage = GetAsync(url).Result
      Dim reponseData As String = response.Content.ReadAsStringAsync().Result
      result = JsonConvert.DeserializeObject(reponseData, GetType(BPMNProcess))
    Catch ex As Exception
      HttpContext.Current.Trace.Write("Error contacting REST service: " & ex.ToString)
    Finally
      Logout()
    End Try
    Return result
  End Function

  Public Function StartCase(processID As String) As BPMNCase
    Dim result As BPMNCase = Nothing
    Try
      Login()
      Dim formContent As New List(Of KeyValuePair(Of String, String)) From {New KeyValuePair(Of String, String)("processDefinitionId", processID)}
      Dim url As String = "/bonita/bpm/case/"
      Dim response As HttpResponseMessage = MyBase.PostAsync(url, New FormUrlEncodedContent(formContent)).Result
      HttpContext.Current.Trace.Write(response.ToString)
      HttpContext.Current.Trace.Write(response.Headers.ToString())

      Dim reponseData As String = response.Content.ReadAsStringAsync().Result
      result = JsonConvert.DeserializeObject(reponseData, GetType(BPMNCase))
    Catch ex As Exception
      HttpContext.Current.Trace.Write("Error contacting REST service: " & ex.ToString)
    Finally
      Logout()
    End Try
    Return result
  End Function

  Public Function GetProcesses() As BPMNProcess()
    Dim result As BPMNProcess() = Nothing
    Try
      Login()
      Dim url As String = "/bonita/API/bpm/process?p=0&c=10"
      Dim response As HttpResponseMessage = GetAsync(url).Result
      Dim reponseData As String = response.Content.ReadAsStringAsync().Result

      HttpContext.Current.Trace.Write(response.ToString)
      HttpContext.Current.Trace.Write("Process data", reponseData)
      result = JsonConvert.DeserializeObject(reponseData, GetType(BPMNProcess()))
    Catch ex As Exception
      HttpContext.Current.Trace.Write("Error contacting REST service: " & ex.ToString)
    End Try
    Return result
  End Function

  Private Sub Login()
    Try
      'Add form data
      Dim formContent As New List(Of KeyValuePair(Of String, String)) From {New KeyValuePair(Of String, String)("username", username),
                                                                            New KeyValuePair(Of String, String)("password", password)}

      Dim response As HttpResponseMessage = MyBase.PostAsync("/bonita/loginservice", New FormUrlEncodedContent(formContent)).Result

      '
Display as string
      HttpContext.Current.Trace.Write(response.ToString)
      HttpContext.Current.Trace.Write(response.Headers.ToString())
    Catch ex As Exception
      HttpContext.Current.Trace.Write("Error contacting REST service: " & ex.ToString)
    End Try
  End Sub

  Private Sub Logout()
    Try
      'Add form data
      Dim response As HttpResponseMessage = MyBase.GetAsync("/bonita/logoutservice").Result

      '
Display as string
      HttpContext.Current.Trace.Write(response.ToString)
      HttpContext.Current.Trace.Write(response.Headers.ToString())
    Catch ex As Exception
      HttpContext.Current.Trace.Write("Error contacting REST service: " & ex.ToString)
    End Try
  End Sub

  Public Overrides Function SendAsync(request As HttpRequestMessage, cancellationToken As Threading.CancellationToken) As Threading.Tasks.Task(Of HttpResponseMessage)
    Dim sb As New StringBuilder
    For i As Integer = 0 To request.Properties.Count - 1
      sb.AppendLine(request.Properties.Keys(i) & ": " & request.Properties.Values(i).ToString)
    Next
    HttpContext.Current.Trace.Write("BPMNEngine Request", sb.ToString)
    Dim result As Threading.Tasks.Task(Of HttpResponseMessage) = MyBase.SendAsync(request, cancellationToken)
    Return result
  End Function
End Class

1
0
-1

Hello,

there is no need to read the Set-Cookie in .NET, theoretically you can't even read it as the TomCat server posts it as 'httpOnly' (tried this and I couldn't!). Turning httpOnly off on the TomCat server makes the site unsafe and doesn't help when reading the headers in client-side code , e.g. using AngularJS as the browser XHR does not read Set-Cookie or Set-Cookie2, ever!

.NET will honor the Set-Cookie automatically, see my test code (VB).

A good trick is to use Json2CSharp to create json data classes for deserialisation.

<TestClass()> Public Class BonitaRestServiceTests
  Private client As New HttpClient With {.BaseAddress = New Uri("http://localhost:9080")}

  <TestMethod()> Public Sub Login()
    'Setup for JSON
    client.DefaultRequestHeaders.Accept.Clear()
    client.DefaultRequestHeaders.Accept.Add(New MediaTypeWithQualityHeaderValue("application/json"))

    Try
      '
Add form data
      Dim formContent As New List(Of KeyValuePair(Of String, String)) From {New KeyValuePair(Of String, String)("username", "walter.bates"), New KeyValuePair(Of String, String)("password", "bpm")}

      Dim response As HttpResponseMessage = client.PostAsync("/bonita/loginservice", New FormUrlEncodedContent(formContent)).Result

      'Display as string
      Trace.WriteLine(response.ToString)
      Trace.WriteLine(response.Headers.ToString())
    Catch ex As Exception
      Assert.Fail("Error contacting REST service: " & ex.ToString)
    End Try
  End Sub

  <TestMethod()> Public Sub GetProcesses()
    '
Setup for JSON
    Login()

    Try
      Dim url As String = "/bonita/API/bpm/process?p=0&c=10"
      Dim response As HttpResponseMessage = client.GetAsync(url).Result
      Dim reponseData As String = response.Content.ReadAsStringAsync().Result

      'Display response
      Trace.WriteLine(response.ToString)

      '
Display response data
      Trace.WriteLine(reponseData)

      'Convert to object
      Dim procs As BPMNProcess() = JsonConvert.DeserializeObject(reponseData, GetType(BPMNProcess()))
      For Each proc As BPMNProcess In procs
        Trace.WriteLine("Found process " & proc.DisplayName)
      Next

    Catch ex As Exception
      Assert.Fail("Error contacting REST service: " & ex.ToString)
    End Try
  End Sub

  <TestMethod()> Public Sub GetTasks()
    '
Setup for JSON
    Login()

    Try
      Dim url As String = "/bonita/API/bpm/humanTask?p=0&c=10&o=priority%20DESC&f=state%3dready&f=user_id%3d0"

      Dim response As HttpResponseMessage = client.GetAsync(url).Result
      Dim reponseData As String = response.Content.ReadAsStringAsync().Result

      'Display response
      Trace.WriteLine(response.ToString)

      '
Display response data
      Trace.WriteLine(reponseData)

      'Convert to object
      Dim tasks As BPMNTask() = JsonConvert.DeserializeObject(reponseData, GetType(BPMNTask()))
      For Each t As BPMNTask In tasks
        Trace.WriteLine("Found task " & t.DisplayName)
      Next
    Catch ex As Exception
      Assert.Fail("Error contacting REST service: " & ex.ToString)
    End Try
  End Sub

End Class

1
0
-1

I was able to get this working in c#. I removed the cookie container and set the cookie directly in the header but you may be able to get the cookie container working. I didn't see you setting the authentication cookie in the cookie container, and I believe your login was failing (even though you received a 200 response and a jsessionid) because you were not form encoding the username, password, etc.

Here is the code which works on my end. This is a console project:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;

namespace BonitaTest
{
    class Program
    {
        static void Main()
        {
            var baseAddress = new Uri("http://localhost:8080");
            using (var handler = new HttpClientHandler { UseCookies =  false})
            {
                using (var client = new HttpClient(handler) { BaseAddress = baseAddress })
                {
                    // This needs to go over as a dict.
                    var formContent = new Dictionary<string,string>
                        {
                            {"username", "walter.bates"},
                            {"password", "bpm"},
                            {"redirect", "false"}
                        };

                    // Login to Bonita. Convert the dict to form encoded content.
                    var result = client.PostAsync("/bonita/loginservice", new FormUrlEncodedContent(formContent)).Result;
                   
                    // Variable to hold the authentication cookie.
                    var setCookie = string.Empty;

                    // Get the authentication cookie from the login request.
                    foreach (var header in result.Headers.Where(header => header.Key == "Set-Cookie"))
                    {
                        foreach (var value in header.Value)
                        {
                            setCookie = value;
                            break; // Stop after finding something..
                        }
                        break; // We only care about the first match.
                    }

                    // Uh oh, something bad happened...
                    if (setCookie.Trim().Equals(""))
                    {
                        throw new Exception("No authentication cookie from login call!");
                    }

                    // Make sure the accept headers are empty.
                    client.DefaultRequestHeaders.Accept.Clear();
                    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

                    var message = new HttpRequestMessage(HttpMethod.Get, "/bonita/API/bpm/process?p=0&c=10");
                    message.Headers.Add("Cookie", setCookie);

                    // Get a list of processes.
                    var result2 = client.SendAsync(message).Result;

                    // Make sure a success code was returned.
                    result2.EnsureSuccessStatusCode();
                }
            }

        }
    }
}

I hope this helps...

Notifications