redirect uri verification

This commit is contained in:
Niko Abeler 2022-11-06 14:36:37 +01:00
parent 3975f08441
commit ae387f3d7d
5 changed files with 164 additions and 1 deletions

48
auth_test.go Normal file
View File

@ -0,0 +1,48 @@
package owl_test
import (
"h4kor/owl-blogs"
"h4kor/owl-blogs/test/assertions"
"net/http"
"testing"
)
func TestGetRedirctUrisLink(t *testing.T) {
html := []byte("<link rel=\"redirect_uri\" href=\"http://example.com/redirect\" />")
parser := &owl.OwlHtmlParser{}
uris, err := parser.GetRedirctUris(constructResponse(html))
assertions.AssertNoError(t, err, "Unable to parse feed")
assertions.AssertArrayContains(t, uris, "http://example.com/redirect")
}
func TestGetRedirctUrisLinkMultiple(t *testing.T) {
html := []byte(`
<link rel="redirect_uri" href="http://example.com/redirect1" />
<link rel="redirect_uri" href="http://example.com/redirect2" />
<link rel="redirect_uri" href="http://example.com/redirect3" />
<link rel="foo" href="http://example.com/redirect4" />
<link href="http://example.com/redirect5" />
`)
parser := &owl.OwlHtmlParser{}
uris, err := parser.GetRedirctUris(constructResponse(html))
assertions.AssertNoError(t, err, "Unable to parse feed")
assertions.AssertArrayContains(t, uris, "http://example.com/redirect1")
assertions.AssertArrayContains(t, uris, "http://example.com/redirect2")
assertions.AssertArrayContains(t, uris, "http://example.com/redirect3")
assertions.AssertLen(t, uris, 3)
}
func TestGetRedirectUrisLinkHeader(t *testing.T) {
html := []byte("")
parser := &owl.OwlHtmlParser{}
resp := constructResponse(html)
resp.Header = http.Header{"Link": []string{"<http://example.com/redirect>; rel=\"redirect_uri\""}}
uris, err := parser.GetRedirctUris(resp)
assertions.AssertNoError(t, err, "Unable to parse feed")
assertions.AssertArrayContains(t, uris, "http://example.com/redirect")
}

View File

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
main "h4kor/owl-blogs/cmd/owl/web" main "h4kor/owl-blogs/cmd/owl/web"
"h4kor/owl-blogs/test/assertions" "h4kor/owl-blogs/test/assertions"
"h4kor/owl-blogs/test/mocks"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
@ -121,3 +122,65 @@ func TestAuthPostWithCorrectCode(t *testing.T) {
assertions.AssertEqual(t, response.Me, user.FullUrl()) assertions.AssertEqual(t, response.Me, user.FullUrl())
} }
func TestAuthRedirectUriNotSet(t *testing.T) {
repo, user := getSingleUserTestRepo()
repo.HttpClient = &mocks.MockHttpClient{}
repo.Parser = &mocks.MockParseLinksHtmlParser{
Links: []string{"http://example.com/response"},
}
user.ResetPassword("testpassword")
csrfToken := "test_csrf_token"
// Create Request and Response
form := url.Values{}
form.Add("password", "wrongpassword")
form.Add("client_id", "http://example.com")
form.Add("redirect_uri", "http://example.com/response_not_set")
form.Add("response_type", "code")
form.Add("state", "test_state")
form.Add("csrf_token", csrfToken)
req, err := http.NewRequest("GET", user.AuthUrl()+"?"+form.Encode(), nil)
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Content-Length", strconv.Itoa(len(form.Encode())))
req.AddCookie(&http.Cookie{Name: "csrf_token", Value: csrfToken})
assertions.AssertNoError(t, err, "Error creating request")
rr := httptest.NewRecorder()
router := main.SingleUserRouter(&repo)
router.ServeHTTP(rr, req)
assertions.AssertStatus(t, rr, http.StatusBadRequest)
}
func TestAuthRedirectUriSet(t *testing.T) {
repo, user := getSingleUserTestRepo()
repo.HttpClient = &mocks.MockHttpClient{}
repo.Parser = &mocks.MockParseLinksHtmlParser{
Links: []string{"http://example.com/response"},
}
user.ResetPassword("testpassword")
csrfToken := "test_csrf_token"
// Create Request and Response
form := url.Values{}
form.Add("password", "wrongpassword")
form.Add("client_id", "http://example.com")
form.Add("redirect_uri", "http://example.com/response")
form.Add("response_type", "code")
form.Add("state", "test_state")
form.Add("csrf_token", csrfToken)
req, err := http.NewRequest("GET", user.AuthUrl()+"?"+form.Encode(), nil)
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Content-Length", strconv.Itoa(len(form.Encode())))
req.AddCookie(&http.Cookie{Name: "csrf_token", Value: csrfToken})
assertions.AssertNoError(t, err, "Error creating request")
rr := httptest.NewRecorder()
router := main.SingleUserRouter(&repo)
router.ServeHTTP(rr, req)
assertions.AssertStatus(t, rr, http.StatusOK)
}

View File

@ -100,6 +100,23 @@ func userAuthHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Reque
return return
} }
// check if redirect_uri is registered
resp, _ := repo.HttpClient.Get(clientId)
registered_redirects, _ := repo.Parser.GetRedirctUris(resp)
is_registered := false
for _, registered_redirect := range registered_redirects {
if registered_redirect == redirectUri {
// redirect_uri is registered
is_registered = true
break
}
}
if !is_registered {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Invalid redirect_uri. Must be registered with client_id."))
return
}
// Double Submit Cookie Pattern // Double Submit Cookie Pattern
// https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie // https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie
csrfToken := owl.GenerateRandomString(32) csrfToken := owl.GenerateRandomString(32)

View File

@ -27,6 +27,16 @@ func AssertContains(t *testing.T, containing string, search string) {
} }
} }
func AssertArrayContains[T comparable](t *testing.T, list []T, search T) {
t.Helper()
for _, item := range list {
if item == search {
return
}
}
t.Errorf("Expected '%v' to be in '%v'", search, list)
}
func AssertNotContains(t *testing.T, containing string, search string) { func AssertNotContains(t *testing.T, containing string, search string) {
t.Helper() t.Helper()
if strings.Contains(containing, search) { if strings.Contains(containing, search) {

View File

@ -259,6 +259,30 @@ func (OwlHtmlParser) GetRedirctUris(resp *http.Response) ([]string, error) {
} }
var findLinks func(*html.Node) ([]string, error) var findLinks func(*html.Node) ([]string, error)
// Check link headers
header_links := make([]string, 0)
for _, linkHeader := range resp.Header["Link"] {
linkHeaderParts := strings.Split(linkHeader, ",")
for _, linkHeaderPart := range linkHeaderParts {
linkHeaderPart = strings.TrimSpace(linkHeaderPart)
params := strings.Split(linkHeaderPart, ";")
if len(params) != 2 {
continue
}
for _, param := range params[1:] {
param = strings.TrimSpace(param)
if strings.Contains(param, "redirect_uri") {
link := strings.Split(params[0], ";")[0]
link = strings.Trim(link, "<>")
linkUrl, err := url.Parse(link)
if err == nil {
header_links = append(header_links, requestUrl.ResolveReference(linkUrl).String())
}
}
}
}
}
findLinks = func(n *html.Node) ([]string, error) { findLinks = func(n *html.Node) ([]string, error) {
links := make([]string, 0) links := make([]string, 0)
if n.Type == html.ElementNode && n.Data == "link" { if n.Type == html.ElementNode && n.Data == "link" {
@ -287,5 +311,6 @@ func (OwlHtmlParser) GetRedirctUris(resp *http.Response) ([]string, error) {
} }
return links, nil return links, nil
} }
return findLinks(doc) body_links, err := findLinks(doc)
return append(body_links, header_links...), err
} }