1
0
Fork 0
mirror of https://github.com/Luzifer/nginx-sso.git synced 2024-12-20 21:01:17 +00:00
nginx-sso/vendor/github.com/duosecurity/duo_api_golang/duo_test.go
Knut Ahlers 9b3c895c04
Update dependencies
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2019-04-22 06:44:07 +02:00

330 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package duoapi
import (
"bytes"
"errors"
"io/ioutil"
"net/http"
"net/url"
"strings"
"testing"
"time"
)
func TestCanonicalize(t *testing.T) {
values := url.Values{}
values.Set("username", "H ell?o")
values.Set("password", "H-._~i")
values.Add("password", "A(!'*)")
params_str := canonicalize("post",
"API-XXX.duosecurity.COM",
"/auth/v2/ping",
values,
"5")
params := strings.Split(params_str, "\n")
if len(params) != 5 {
t.Error("Expected 5 parameters, but got " + string(len(params)))
}
if params[1] != string("POST") {
t.Error("Expected POST, but got " + params[1])
}
if params[2] != string("api-xxx.duosecurity.com") {
t.Error("Expected api-xxx.duosecurity.com, but got " + params[2])
}
if params[3] != string("/auth/v2/ping") {
t.Error("Expected /auth/v2/ping, but got " + params[3])
}
if params[4] != string("password=A%28%21%27%2A%29&password=H-._~i&username=H%20ell%3Fo") {
t.Error("Expected sorted escaped params, but got " + params[4])
}
}
func encodeAndValidate(t *testing.T, input url.Values, output string) {
values := url.Values{}
for key, val := range input {
values.Set(key, val[0])
}
params_str := canonicalize("post",
"API-XXX.duosecurity.com",
"/auth/v2/ping",
values,
"5")
params := strings.Split(params_str, "\n")
if params[4] != output {
t.Error("Mismatch\n" + output + "\n" + params[4])
}
}
func TestSimple(t *testing.T) {
values := url.Values{}
values.Set("realname", "First Last")
values.Set("username", "root")
encodeAndValidate(t, values, "realname=First%20Last&username=root")
}
func TestZero(t *testing.T) {
values := url.Values{}
encodeAndValidate(t, values, "")
}
func TestOne(t *testing.T) {
values := url.Values{}
values.Set("realname", "First Last")
encodeAndValidate(t, values, "realname=First%20Last")
}
func TestPrintableAsciiCharaceters(t *testing.T) {
values := url.Values{}
values.Set("digits", "0123456789")
values.Set("letters", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
values.Set("punctuation", "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~")
values.Set("whitespace", "\t\n\x0b\x0c\r ")
encodeAndValidate(t, values, "digits=0123456789&letters=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ&punctuation=%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F%3A%3B%3C%3D%3E%3F%40%5B%5C%5D%5E_%60%7B%7C%7D~&whitespace=%09%0A%0B%0C%0D%20")
}
func TestSortOrderWithCommonPrefix(t *testing.T) {
values := url.Values{}
values.Set("foo", "1")
values.Set("foo_bar", "2")
encodeAndValidate(t, values, "foo=1&foo_bar=2")
}
func TestUnicodeFuzzValues(t *testing.T) {
values := url.Values{}
values.Set("bar", "⠕ꪣ㟏䮷㛩찅暎腢슽ꇱ")
values.Set("baz", "ෳ蒽噩馅뢤갺篧潩鍊뤜")
values.Set("foo", "퓎훖礸僀訠輕ﴋ耤岳왕")
values.Set("qux", "讗졆-芎茚쳊ꋔ谾뢲馾")
encodeAndValidate(t, values, "bar=%E2%A0%95%EA%AA%A3%E3%9F%8F%E4%AE%B7%E3%9B%A9%EC%B0%85%E6%9A%8E%E8%85%A2%EC%8A%BD%EA%87%B1&baz=%E0%B7%B3%E8%92%BD%E5%99%A9%E9%A6%85%EB%A2%A4%EA%B0%BA%E7%AF%A7%E6%BD%A9%E9%8D%8A%EB%A4%9C&foo=%ED%93%8E%ED%9B%96%E7%A4%B8%E5%83%80%E8%A8%A0%E8%BC%95%EF%B4%8B%E8%80%A4%E5%B2%B3%EC%99%95&qux=%E8%AE%97%EC%A1%86-%E8%8A%8E%E8%8C%9A%EC%B3%8A%EA%8B%94%E8%B0%BE%EB%A2%B2%E9%A6%BE")
}
func TestUnicodeFuzzKeysAndValues(t *testing.T) {
values := url.Values{}
values.Set("䚚⡻㗐軳朧倪ࠐ킑È셰",
"ཅ᩶㐚敌숿鬉ꯢ荃ᬧ惐")
values.Set("瑉繋쳻姿﹟获귌逌쿑砓",
"趷倢鋓䋯⁽蜰곾嘗ॆ丰")
values.Set("瑰錔逜麮䃘䈁苘豰ᴱꁂ",
"៙ந鍘꫟ꐪ䢾ﮖ濩럿㋳")
values.Set("싅Ⱍ☠㘗隳F蘅⃨갡头",
"䆪붃萋☕㹮攭ꢵ핫U")
encodeAndValidate(t, values, "%E4%9A%9A%E2%A1%BB%E3%97%90%E8%BB%B3%E6%9C%A7%E5%80%AA%E0%A0%90%ED%82%91%C3%88%EC%85%B0=%E0%BD%85%E1%A9%B6%E3%90%9A%E6%95%8C%EC%88%BF%E9%AC%89%EA%AF%A2%E8%8D%83%E1%AC%A7%E6%83%90&%E7%91%89%E7%B9%8B%EC%B3%BB%E5%A7%BF%EF%B9%9F%E8%8E%B7%EA%B7%8C%E9%80%8C%EC%BF%91%E7%A0%93=%E8%B6%B7%E5%80%A2%E9%8B%93%E4%8B%AF%E2%81%BD%E8%9C%B0%EA%B3%BE%E5%98%97%E0%A5%86%E4%B8%B0&%E7%91%B0%E9%8C%94%E9%80%9C%E9%BA%AE%E4%83%98%E4%88%81%E8%8B%98%E8%B1%B0%E1%B4%B1%EA%81%82=%E1%9F%99%E0%AE%A8%E9%8D%98%EA%AB%9F%EA%90%AA%E4%A2%BE%EF%AE%96%E6%BF%A9%EB%9F%BF%E3%8B%B3&%EC%8B%85%E2%B0%9D%E2%98%A0%E3%98%97%E9%9A%B3F%E8%98%85%E2%83%A8%EA%B0%A1%E5%A4%B4=%EF%AE%A9%E4%86%AA%EB%B6%83%E8%90%8B%E2%98%95%E3%B9%AE%E6%94%AD%EA%A2%B5%ED%95%ABU")
}
func TestSign(t *testing.T) {
values := url.Values{}
values.Set("realname", "First Last")
values.Set("username", "root")
res := sign("DIWJ8X6AEYOR5OMC6TQ1",
"Zh5eGmUq9zpfQnyUIu5OL9iWoMMv5ZNmk3zLJ4Ep",
"POST",
"api-XXXXXXXX.duosecurity.com",
"/accounts/v1/account/list",
"Tue, 21 Aug 2012 17:29:18 -0000",
values)
if res != "Basic RElXSjhYNkFFWU9SNU9NQzZUUTE6MmQ5N2Q2MTY2MzE5Nzgx"+
"YjVhM2EwN2FmMzlkMzY2ZjQ5MTIzNGVkYw==" {
t.Error("Signature did not produce output documented at " +
"https://www.duosecurity.com/docs/authapi :(")
}
}
func TestV2Canonicalize(t *testing.T) {
values := url.Values{}
values.Set("䚚⡻㗐軳朧倪ࠐ킑È셰",
"ཅ᩶㐚敌숿鬉ꯢ荃ᬧ惐")
values.Set("瑉繋쳻姿﹟获귌逌쿑砓",
"趷倢鋓䋯⁽蜰곾嘗ॆ丰")
values.Set("瑰錔逜麮䃘䈁苘豰ᴱꁂ",
"៙ந鍘꫟ꐪ䢾ﮖ濩럿㋳")
values.Set("싅Ⱍ☠㘗隳F蘅⃨갡头",
"䆪붃萋☕㹮攭ꢵ핫U")
canon := canonicalize(
"PoSt",
"foO.BAr52.cOm",
"/Foo/BaR2/qux",
values,
"Fri, 07 Dec 2012 17:18:00 -0000")
expected := "Fri, 07 Dec 2012 17:18:00 -0000\nPOST\nfoo.bar52.com\n/Foo/BaR2/qux\n%E4%9A%9A%E2%A1%BB%E3%97%90%E8%BB%B3%E6%9C%A7%E5%80%AA%E0%A0%90%ED%82%91%C3%88%EC%85%B0=%E0%BD%85%E1%A9%B6%E3%90%9A%E6%95%8C%EC%88%BF%E9%AC%89%EA%AF%A2%E8%8D%83%E1%AC%A7%E6%83%90&%E7%91%89%E7%B9%8B%EC%B3%BB%E5%A7%BF%EF%B9%9F%E8%8E%B7%EA%B7%8C%E9%80%8C%EC%BF%91%E7%A0%93=%E8%B6%B7%E5%80%A2%E9%8B%93%E4%8B%AF%E2%81%BD%E8%9C%B0%EA%B3%BE%E5%98%97%E0%A5%86%E4%B8%B0&%E7%91%B0%E9%8C%94%E9%80%9C%E9%BA%AE%E4%83%98%E4%88%81%E8%8B%98%E8%B1%B0%E1%B4%B1%EA%81%82=%E1%9F%99%E0%AE%A8%E9%8D%98%EA%AB%9F%EA%90%AA%E4%A2%BE%EF%AE%96%E6%BF%A9%EB%9F%BF%E3%8B%B3&%EC%8B%85%E2%B0%9D%E2%98%A0%E3%98%97%E9%9A%B3F%E8%98%85%E2%83%A8%EA%B0%A1%E5%A4%B4=%EF%AE%A9%E4%86%AA%EB%B6%83%E8%90%8B%E2%98%95%E3%B9%AE%E6%94%AD%EA%A2%B5%ED%95%ABU"
if canon != expected {
t.Error("Mismatch!\n" + expected + "\n" + canon)
}
}
func TestNewDuo(t *testing.T) {
duo := NewDuoApi("ABC", "123", "api-XXXXXXX.duosecurity.com", "go-client")
if duo == nil {
t.Fatal("Failed to create a new Duo Api")
}
}
func TestDupApiCallHttpErr(t *testing.T) {
httpClient := &mockHttpClient{doError: true}
sleepSvc := &mockSleepService{}
duo := &DuoApi{
ikey: "ikey-foo",
skey: "skey-bar",
host: "host.baz",
userAgent: "ua-qux",
apiClient: httpClient,
authClient: httpClient,
sleepSvc: sleepSvc,
}
resp, body, err := duo.Call("GET", "/v9/hello/world", url.Values{})
if resp != nil {
t.Fatal("Non nil response returned")
}
if len(body) != 0 {
t.Fatal("Non empty body returned")
}
if err == nil {
t.Fatal("No error returned")
}
if len(httpClient.actualRequests) != 1 {
t.Fatal("We should not retry after an HTTP error")
}
}
func getMockClients(httpResponses []http.Response) (*DuoApi, *mockHttpClient, *mockSleepService) {
httpClient := &mockHttpClient{responses: httpResponses}
sleepSvc := &mockSleepService{}
return &DuoApi{
ikey: "ikey-foo",
skey: "skey-bar",
host: "host.baz",
userAgent: "ua-qux",
apiClient: httpClient,
authClient: httpClient,
sleepSvc: sleepSvc,
}, httpClient, sleepSvc
}
var okResp = http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(bytes.NewReader([]byte("hello world"))),
}
var rateLimitResp = http.Response{
StatusCode: 429,
Body: ioutil.NopCloser(bytes.NewReader([]byte("hello world"))),
}
var completeRateLimitSleepDurations = []time.Duration{
time.Millisecond * 1000,
time.Millisecond * 2000,
time.Millisecond * 4000,
time.Millisecond * 8000,
time.Millisecond * 16000,
time.Millisecond * 32000,
}
func assertRateLimitedCall(
t *testing.T,
actualResponse http.Response,
httpClient mockHttpClient,
sleepSvc mockSleepService,
expectedTotalCalls int,
expectedResponse http.Response,
expectedSleepDurations []time.Duration) {
if actualResponse.StatusCode != expectedResponse.StatusCode {
t.Fatal("returned response does not have correct status code")
}
if actualResponse.Body != expectedResponse.Body {
t.Fatal("returned response does not have correct body")
}
retriedRequestCount := expectedTotalCalls - 1
if len(httpClient.actualRequests) != expectedTotalCalls {
t.Fatal("Made " + string(len(httpClient.actualRequests)) +
" requests instead of " + string(expectedTotalCalls))
}
if len(sleepSvc.sleepCalls) != retriedRequestCount {
t.Fatal("Made " + string(len(sleepSvc.sleepCalls)) +
" sleep calls instead of " + string(retriedRequestCount))
}
for i := range expectedSleepDurations {
if sleepSvc.sleepCalls[i] != expectedSleepDurations[i] {
t.Fatal("Slept for " + string(sleepSvc.sleepCalls[i]) +
" instead of " + string(expectedSleepDurations[i]))
}
}
}
func TestCallRateLimitedOnce(t *testing.T) {
responses := []http.Response{rateLimitResp, okResp}
sleepDurations := []time.Duration{time.Millisecond * 1000}
duo, mockHttp, mockSleep := getMockClients(responses)
resp, _, _ := duo.Call("GET", "/v9/hello/world", url.Values{})
assertRateLimitedCall(t, *resp, *mockHttp, *mockSleep, 2, okResp, sleepDurations)
}
func TestCallCompletelyRateLimited(t *testing.T) {
responses := make([]http.Response, 7)
for i := range responses {
responses[i] = rateLimitResp
}
duo, mockHttp, mockSleep := getMockClients(responses)
resp, _, _ := duo.Call("GET", "/v9/hello/world", url.Values{})
assertRateLimitedCall(t, *resp, *mockHttp, *mockSleep,
7, rateLimitResp, completeRateLimitSleepDurations)
}
func TestSignedCallRateLimitedOnce(t *testing.T) {
responses := []http.Response{rateLimitResp, okResp}
sleepDurations := []time.Duration{time.Millisecond * 1000}
duo, mockHttp, mockSleep := getMockClients(responses)
resp, _, _ := duo.SignedCall("GET", "/v9/hello/world", url.Values{})
assertRateLimitedCall(t, *resp, *mockHttp, *mockSleep, 2, okResp, sleepDurations)
}
func TestSignedCallCompletelyRateLimited(t *testing.T) {
responses := make([]http.Response, 7)
for i := range responses {
responses[i] = rateLimitResp
}
duo, mockHttp, mockSleep := getMockClients(responses)
resp, _, _ := duo.SignedCall("GET", "/v9/hello/world", url.Values{})
assertRateLimitedCall(t, *resp, *mockHttp, *mockSleep,
7, rateLimitResp, completeRateLimitSleepDurations)
}
type mockHttpClient struct {
responses []http.Response
actualRequests []*http.Request
doError bool
}
func (c *mockHttpClient) Do(req *http.Request) (*http.Response, error) {
if c.actualRequests == nil {
c.actualRequests = []*http.Request{}
}
c.actualRequests = append(c.actualRequests, req)
if c.doError {
return nil, errors.New("Ouch")
}
resp := c.responses[0]
c.responses = c.responses[1:]
return &resp, nil
}
type mockSleepService struct {
sleepCalls []time.Duration
}
func (svc *mockSleepService) Sleep(duration time.Duration) {
if svc.sleepCalls == nil {
svc.sleepCalls = []time.Duration{}
}
svc.sleepCalls = append(svc.sleepCalls, duration)
}