4

I am trying to create a .NET application in F# that calls out to the Jira REST API to read/create issues.

I generated a .pub and .pem with OpenSSL, and created an application link in Jira with the public key.

Here is the signing function that I put together (based off of some C# code I found in a blog post) that gets me the initial token response:

let rsasha1 (signingKey : string) str =
    let rsa = new RSACryptoServiceProvider()
    rsa.FromXmlString(signingKey)
    let shaHashObject = new SHA1Managed()
    let data = Encoding.ASCII.GetBytes(str : string)
    let hash = shaHashObject.ComputeHash(data)
    let signedValue = rsa.SignHash(hash, CryptoConfig.MapNameToOID("SHA1"))
    Convert.ToBase64String(signedValue, Base64FormattingOptions.None)

And this is the function that I'm using to do the call (also found from a snippet online)

/// Request a token and return:
///  oauth_token, oauth_token_secret, oauth_callback_confirmed
let requestToken() = 

    let queryParameters = 
        ["oauth_callback", "oob"
         "oauth_consumer_key", consumerKey
         "oauth_nonce", System.Guid.NewGuid().ToString().Substring(24)
         "oauth_signature_method", "RSA-SHA1"
         "oauth_timestamp", currentUnixTime().ToString()
         "oauth_version", "1.0"]

    let signingString = baseString "POST" requestTokenURI queryParameters

    let rsaSignature = rsasha1 consumerSecretXML signingString

    let realQueryParameters = ("oauth_signature", rsaSignature)::queryParameters

    let req = WebRequest.Create(requestTokenURI, Method="POST")
    let headerValue = createAuthorizeHeader realQueryParameters
    req.Headers.Add(HttpRequestHeader.Authorization, headerValue)

    let resp = req.GetResponse()
    let stream = resp.GetResponseStream()
    let txt = (new StreamReader(stream)).ReadToEnd()

    let parts = txt.Split('&')
    (parts.[0].Split('=').[1],
     parts.[1].Split('=').[1],
     parts.[2].Split('=').[1] = "true")

This successfully returns token information, but I am unsure what to do next once I have the token. The original C# code that I based this off of was using HMACSHA1 instead of RSASHA1 and simply concatenated the consumer secret with the token secret and then called the signing function again.

I am unable to get this working that way. I had to convert my private key in the .pem file into XML in order to read it into the RSACryptoServiceProvider. If I try to concatenate the token secret onto the private key before converting, I get an error.

How do I go about signing the token secret so that I can do subsequent REST calls to Jira?

2
  • Use session cookie in a request, Now that you've created a session, it's just a matter of setting the cookie in all subsequent requests to the server. Store the session object on the client. The way that you do this will depend on how your client is implemented. When you want to make a request, take cookie name and value from the session and use them to set the 'cookie' field in the header of your request. You can see an example of this below: headers: {cookie: JSESSIONID=6E3487971234567896704A9EB4AE501F} Commented Oct 17, 2016 at 21:22
  • Recommended by Sergey Tihon on Twitter: Atlassian.SDK. Commented Oct 18, 2016 at 13:12

2 Answers 2

2

This is probably not the answer you want, but I did something close to what you need. I am not using anything else then "ordinary" authentication (by JSON), but then again I am reusing the Session variable JSESSIONID (cookie) when I am authenticated.

If this works for you (your authentication, and then grabbing the JSESSIONID and then reusing it), the following code should kind of work (as a principle).

Mind that I am using FSharp.Data <package id="FSharp.Data" version="2.3.1" targetFramework="net46" /> and maybe some "friends" (some other package dependencies) in the same run.

Please also note that I am referencing a file for the returned JSON of the query, so if you you are going to use the JSON-provider and doing a search or something like that, you need some JSON to infer the search result from.

Hopefully the code will give you some pointers.

And yes, this code is probably not the finest moment of F# ;-)

#r "System.Servicemodel"
#r "System.Xml"
#r @"..\packages\FSharp.Data.2.3.1\lib\net40\fsharp.data.dll"

open FSharp.Data
[<Literal>]
let baseUrl = "https://jira"

[<Literal>]
let jiraUrlQuery = baseUrl + "/rest/api/2/search?jql=text%20~%20%22some text%22%20ORDER%20BY%20created%20DESC"

[<Literal>]
let loginUrl = "/rest/auth/1/session"

//[<Literal>]
let creds = sprintf "{\"username\": \"%s\", \"password\": \"%s\" }"

[<Literal>]
let loginInfoExample = "{\"session\":{\"name\":\"JSESSIONID\",\"value\":\"åæø8F95074F7398C68961708066EC6342A8\"},\"loginInfo\":{\"failedLoginCount\":7,\"loginCount\":434,\"lastFailedLoginTime\":\"2016-08-02T13:15:27.392+0200\",\"previousLoginTime\":\"2016-08-05T08:46:45.168+0200\"}}"

type LoginInfo = JsonProvider< loginInfoExample >

let login = Http.Request(baseUrl+loginUrl, httpMethod="POST", body=HttpRequestBody.TextRequest( creds "SomeUserName" "SomePassword" ), headers= [ "Content-Type", "application/json" ])
//let login = JsonValue.Request(baseUrl+loginUrl, httpMethod="POST",  headers= [ "Content-Type", "application/json" ])

let getText =
    function
    | Text s -> s
    | _ -> "" 

let loggedIn = LoginInfo.Parse(getText login.Body).Session.Value.ToString()

type JiraIssues = JsonProvider< ".\JIRA\query.json" > //jiraUrlQuery>

let listOfCases = JiraIssues.Parse(Http.Request(jiraUrlQuery, cookies=["JSESSIONID", loggedIn] ).Body |> getText)

listOfCases.Issues |> Seq.map (fun x -> x.Fields.Summary, x.Fields.Created) |> Seq.sortByDescending (fun (s,c) -> c) |> Seq.iter (fun (s,c) -> printfn "%A: %s" c s)  
Sign up to request clarification or add additional context in comments.

2 Comments

Does this account for the recent versions of JIRA, that don't support Basic Auth for apps?
Probably not. But it still is "same principle" with sending back some oauth token somewhere, and that somewhere is then usually a cookie of some sort.
1

We couldn't get OAuth working, but inspired by @Helge's answer, we were able to fake logging in the way a user does.

#r "System.Net.Http"

open System
open System.Collections.Generic
open System.Net
open System.Net.Http
open System.Net.Http.Headers

let toKVPairSeq l =
    Seq.map (fun (k, v) -> KeyValuePair(k, v)) l

let handler = new HttpClientHandler()
let client = new HttpClient(handler)
client.BaseAddress <- Uri "http://jira/"
let authRequestContent = new FormUrlEncodedContent([
                                                        "os_username", "jira-user"
                                                        "os_password", "jira-password"
                                                        "os_cookie", "true"
                                                        "os_destination", ""
                                                        "user_role", ""
                                                        "atl_token", ""
                                                        "login", "Log+In"
                                                    ] |> toKVPairSeq)
let request = new HttpRequestMessage()
let result1 = client.PostAsync("login.jsp", authRequestContent).Result
result1.EnsureSuccessStatusCode()
printf "%A %s" result1.StatusCode <| result1.Content.ReadAsStringAsync().Result
let result2 = (client.GetAsync "rest/api/latest/issue/ISSUE-1").Result
printf "%A %s" result2.StatusCode <| result2.Content.ReadAsStringAsync().Result

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.