Join 34,000+ subscribers and receive articles from our
blog about software quality, testing, QA and security.

Getting 401 programmatically for TestRail API v.2 integrated to AD/LADP


#1

We have a TestRail server integrated with AD/LADP. I am trying to use TestRail API v.2 programmatically from Scala/java code but am getting 401 after setting the session cookie on subsequent calls after authenticating through AD. The similar flow through the browser and curl work fine. Here is some details.

For the normal web use and similarly for API through the browser, we authenticate with the DA credentials at the base URL of the server, e.g.
https://[TestRailHost]/
and then, the session cooke kicks in and allows access to the subsequent API calls, e.g.
https://[TestRailHost]/testrail/api/v2/get_test/840
(in Chrome using the ModHeader extension to set the required header “Content-Type: application/json”)

Similarly, I am able to get the flow in curl by getting the session cookie first and setting in the subsequent calls

Authenticate and get the session cookie

curl --insecure -L -u “ad-user-testrail:********” “https://[TestRailHost]/testrail/index.php” -c - -v

Cookie output:
Connection #0 to host ***.com left intact

Netscape HTTP Cookie File

http://curl.haxx.se/docs/http-cookies.html

This file was generated by libcurl! Edit at your own risk.

#HttpOnly_***.com FALSE /testrail/ FALSE 0 tr_session 38552fc5-127e-46aa-93b8-a5ee1b7eed3f
***.com FALSE /testrail/ FALSE 1493132158 tr_rememberme deleted

Use the session cookie for the subsequent calls

curl --insecure -L -H “Content-Type: application/json” -H “Cookie: tr_session=38552fc5-127e-46aa-93b8-a5ee1b7eed3f” “https://[TestRailHost]/testrail/api/v2/get_test/840”

Output: HTTP/1.1 200 OK
JSON for id=840

Scala/java code:

val url="https://[TestRailHost]/testrail/index.php?/api/v2/get_test/840"

val method = "GET"
val url_for_cookie = "https://[TestRailHost]/testrail/index.php"

val cookieManager = new CookieManager(null, CookiePolicy.ACCEPT_ALL)
val cookieHandler = CookieHandler.setDefault(cookieManager)

val con1 = (new URL(url_for_cookie).openConnection).asInstanceOf[HttpURLConnection]
con1.setRequestMethod(method)
val basicAuth = "Basic " + DatatypeConverter.printBase64Binary((accessId + ":" + secretKey).getBytes(Charset.forName("UTF-8")))
con1.setRequestProperty("Authorization", basicAuth)

var responseCode1 = 0
try responseCode1 = con1.getResponseCode catch {
  case e: IOException => // swallow it since for 401 getResponseCode throws an IOException
    responseCode1 = con1.getResponseCode
}

val con2 = (new URL(url).openConnection).asInstanceOf[HttpURLConnection]
con2.setRequestMethod(method)
con2.setRequestProperty("Content-Type", "application/json")
// **** cookieManager should set this cookie implicitly
//con2.setRequestProperty("Cookie", cookie)

var responseCode2 = 0
try responseCode2 = con2.getResponseCode catch { // **** getting 401 here while expecting session cookie to auth ****
  case e: IOException => // swallow it since for 401 getResponseCode throws an IOException
    responseCode2 = con2.getResponseCode
}

#2

I was able to get HTTP 200 and result JSON for the second connection(s) if the “Authorization” header is added (while already have session cookie!). But after a few calls (7-10) the connection starts getting
{"error":"The maximum number of failed login attempts has been reached. Please try again in 10 minutes."}

Updated flow:

val hostUrl="my-testrail-mycompany.com"

val url=s"https://$hostUrl/testrail/index.php?/api/v2/get_test/840"

val url_for_cookie = s"https://$hostUrl/testrail/index.php"

val cookieManager = new CookieManager(null, CookiePolicy.ACCEPT_ALL)
val cookieHandler = CookieHandler.setDefault(cookieManager)

val con1 = (new URL(url_for_cookie).openConnection).asInstanceOf[HttpURLConnection]
con1.setRequestMethod("GET")
val basicAuth = "Basic " + DatatypeConverter.printBase64Binary((accessId + ":" + secretKey).getBytes(Charset.forName("UTF-8")))
con1.setRequestProperty("Authorization", basicAuth)

var responseCode1 = 0
try responseCode1 = con1.getResponseCode catch {
  case e: IOException => // swallow it since for 401 getResponseCode throws an IOException
    responseCode1 = con1.getResponseCode
}

val con2 = (new URL(url).openConnection).asInstanceOf[HttpURLConnection]
con2.setRequestMethod("GET")
con2.setRequestProperty("Content-Type", "application/json")
// **** cookieManager sets this cookie implicitly
//con2.setRequestProperty("Cookie", cookie)

// **** Somehow adding the Auth header makes the 401 error ago away
con2.setRequestProperty("Authorization", basicAuth)

var responseCode2 = 0
try responseCode2 = con2.getResponseCode catch { // **** After a few calls this hits {"error":"The maximum number of failed login attempts has been reached. Please try again in 10 minutes."}
  case e: IOException => // swallow it since for 401 getResponseCode throws an IOException
    responseCode2 = con2.getResponseCode
}

#3

Hi there,

I believe we are working on this via a support request. We can update this post as soon as a solution is found.