在 .NET / F# 中将 OAuth 与 Jira REST 调用结合使用
Using OAuth with Jira REST calls in .NET / F#
我正在尝试在 F# 中创建一个 .NET 应用程序,它调用 Jira REST API 到 read/create 问题。
我使用 OpenSSL 生成了 .pub
和 .pem
,并使用 public 密钥在 Jira 中创建了一个应用程序 link。
这是我放在一起的签名函数(基于我在博客 post 中找到的一些 C# 代码),它让我得到初始令牌响应:
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)
这是我用来调用的函数(也是从在线片段中找到的)
/// 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")
这成功 returns 令牌信息,但我不确定一旦获得令牌后下一步该做什么。我基于此的原始 C# 代码使用 HMACSHA1 而不是 RSASHA1,并简单地将消费者机密与令牌机密连接起来,然后再次调用签名函数。
我无法以这种方式工作。我必须将 .pem
文件中的私钥转换为 XML 才能将其读入 RSACryptoServiceProvider。如果我尝试在转换前将令牌秘密连接到私钥上,我会收到错误消息。
我如何着手签署令牌秘密以便我可以对 Jira 进行后续 REST 调用?
这可能不是您想要的答案,但我做了一些接近您需要的事情。我没有使用其他任何东西然后 "ordinary" 身份验证(通过 JSON),但是当我被身份验证时我再次使用会话变量 JSESSIONID(cookie)。
如果这对你有用(你的身份验证,然后获取 JSESSIONID 然后重新使用它),下面的代码应该可以工作(作为原则)。
请注意,我在同一个 运行.
中使用 FSharp.Data <package id="FSharp.Data" version="2.3.1" targetFramework="net46" />
和一些 "friends"(其他一些包依赖项)
另请注意,我为查询的返回 JSON 引用了一个文件,因此如果您打算使用 JSON-provider 并进行搜索或类似的操作,您需要一些 JSON 来推断搜索结果。
希望代码能给大家一些指点。
是的,这段代码可能不是 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)
我们无法让 OAuth 工作,但受@Helge 的回答启发,我们能够以用户的方式伪造登录。
#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
我正在尝试在 F# 中创建一个 .NET 应用程序,它调用 Jira REST API 到 read/create 问题。
我使用 OpenSSL 生成了 .pub
和 .pem
,并使用 public 密钥在 Jira 中创建了一个应用程序 link。
这是我放在一起的签名函数(基于我在博客 post 中找到的一些 C# 代码),它让我得到初始令牌响应:
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)
这是我用来调用的函数(也是从在线片段中找到的)
/// 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")
这成功 returns 令牌信息,但我不确定一旦获得令牌后下一步该做什么。我基于此的原始 C# 代码使用 HMACSHA1 而不是 RSASHA1,并简单地将消费者机密与令牌机密连接起来,然后再次调用签名函数。
我无法以这种方式工作。我必须将 .pem
文件中的私钥转换为 XML 才能将其读入 RSACryptoServiceProvider。如果我尝试在转换前将令牌秘密连接到私钥上,我会收到错误消息。
我如何着手签署令牌秘密以便我可以对 Jira 进行后续 REST 调用?
这可能不是您想要的答案,但我做了一些接近您需要的事情。我没有使用其他任何东西然后 "ordinary" 身份验证(通过 JSON),但是当我被身份验证时我再次使用会话变量 JSESSIONID(cookie)。
如果这对你有用(你的身份验证,然后获取 JSESSIONID 然后重新使用它),下面的代码应该可以工作(作为原则)。
请注意,我在同一个 运行.
中使用 FSharp.Data<package id="FSharp.Data" version="2.3.1" targetFramework="net46" />
和一些 "friends"(其他一些包依赖项)
另请注意,我为查询的返回 JSON 引用了一个文件,因此如果您打算使用 JSON-provider 并进行搜索或类似的操作,您需要一些 JSON 来推断搜索结果。
希望代码能给大家一些指点。
是的,这段代码可能不是 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)
我们无法让 OAuth 工作,但受@Helge 的回答启发,我们能够以用户的方式伪造登录。
#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