Authentication with Active Directory/Kerberos in Julia
When accessing REST APIs from Julia I prefer to use the HTTP package.
However, some of the APIs I use rely on authentication using the dark magic known as Active Directory and HTTP does not support this.
The {httr} package for R supports this by using the {curl} package with
httr::authenticate(":", ":", "gssnegotiate")
The HTTP package does not use cURL directly, but the LibCURL package offers an interface to libcurl.
I am no expert on libcurl/cURL, so this is based on a discourse post and a few tips on relevant cURL options.
In order to make cURL write the request into Julia we need a callback function, that I copied directly from the Discourse post:
function curl_write_cb(curlbuf::Ptr{Cvoid}, s::Csize_t, n::Csize_t, p_ctxt::Ptr{Cvoid})::Csize_t
sz = s * n
data = Array{UInt8}(undef, sz)
ccall(:memcpy, Ptr{Cvoid}, (Ptr{Cvoid}, Ptr{Cvoid}, UInt64), data, curlbuf, sz)
if p_ctxt == C_NULL
j_ctxt = nothing
else
j_ctxt = unsafe_pointer_to_objref(p_ctxt)
end
append!(j_ctxt, data)
sz
end
There is an ancient blog post about callback functions in Julia.
With this callback function we now want to access the URL <URL>
.
I comment most of the options, but they are collected in a function in my actual use case.
First the setup of the cURL call:
curl = LibCURL.curl_easy_init()
Then the callback:
c_curl_write_cb = @cfunction(curl_write_cb, Csize_t, (Ptr{Cvoid}, Csize_t, Csize_t, Ptr{Cvoid}))
LibCURL.curl_easy_setopt(curl, LibCURL.CURLOPT_WRITEFUNCTION, c_curl_write_cb)
The URL is set:
LibCURL.curl_easy_setopt(curl, LibCURL.CURLOPT_FOLLOWLOCATION, 1)
LibCURL.curl_easy_setopt(curl, LibCURL.CURLOPT_URL, "<URL>")
If it is relevant, we can set a header:
accept = "Accept: application/json"
header = LibCURL.curl_slist(pointer(accept), Ptr{Nothing}())
LibCURL.curl_easy_setopt(curl, LibCURL.CURLOPT_HTTPHEADER, header)
We now arrive at the Kerberos magic 🦄:
LibCURL.curl_easy_setopt(curl, LibCURL.CURLOPT_USERNAME, "")
LibCURL.curl_easy_setopt(curl, LibCURL.CURLOPT_USERNAME, "")
LibCURL.curl_easy_setopt(curl, LibCURL.CURLOPT_HTTPAUTH, LibCURL.CURLAUTH_GSSNEGOTIATE)
We construct the array that is going to hold the response:
buffer = UInt8[]
LibCURL.curl_easy_setopt(curl, LibCURL.CURLOPT_WRITEDATA, pointer_from_objref(buffer))
Finally, we make the call and inform Julia to preserve this array:
GC.@preserve buffer begin
LibCURL.curl_easy_perform(curl)
body = deepcopy(buffer)
end
I am not certain that it is necessary to copy buffer
, but when using pointers I prefer to be careful.
Besides the actual body of the response we might be interested in the status code:
http_code = Array{Clong}(undef, 1)
LibCURL.curl_easy_getinfo(curl, LibCURL.CURLINFO_RESPONSE_CODE, http_code)
status = Int64(http_code[1])
When we are all done treating the response we ask cURL to clean up:
LibCURL.curl_easy_cleanup(curl)