Repository: Versent/saml2aws
Pull request #193 (2018-07-09 20:59:24)

account for absolute URLs in login pages
fixes https://github.com/Versent/saml2aws/issues/186


Patch:
diff --git a/pkg/provider/pingfed/example/loginpage.html b/pkg/provider/pingfed/example/loginpage.html
new file mode 100644
index 0000000..d78f8a5
--- /dev/null
+++ b/pkg/provider/pingfed/example/loginpage.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>Example Login Page</title>
+</head>
+<body>
+  <form action="/relative/login">
+    <input type="submit" value="submit"/>
+  </form>
+</body>
+</html>
diff --git a/pkg/provider/pingfed/example/loginpage_absolute.html b/pkg/provider/pingfed/example/loginpage_absolute.html
new file mode 100644
index 0000000..6c00b8e
--- /dev/null
+++ b/pkg/provider/pingfed/example/loginpage_absolute.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>Example Login Page</title>
+</head>
+<body>
+  <form action="https://other.example.com/login">
+    <input type="submit" value="submit"/>
+  </form>
+</body>
+</html>
diff --git a/pkg/provider/pingfed/pingfed.go b/pkg/provider/pingfed/pingfed.go
index bae7682..69a3edb 100644
--- a/pkg/provider/pingfed/pingfed.go
+++ b/pkg/provider/pingfed/pingfed.go
@@ -283,21 +283,12 @@ func (ac *Client) getLoginForm(loginDetails *creds.LoginDetails) (string, url.Va
 		updateLoginFormData(authForm, s, loginDetails)
 	})
 
-	authSubmitURL := ""
-
-	doc.Find("form").Each(func(i int, s *goquery.Selection) {
-		action, ok := s.Attr("action")
-		if !ok {
-			return
-		}
-		authSubmitURL = action
-	})
-
-	if authSubmitURL == "" {
+	authSubmitURL, err := extractAuthSubmitURL(loginDetails.URL, doc)
+	if err != nil {
 		return "", nil, fmt.Errorf("unable to locate IDP authentication form submit URL")
 	}
 
-	return fmt.Sprintf("%s%s", loginDetails.URL, authSubmitURL), authForm, nil
+	return authSubmitURL, authForm, nil
 }
 
 func updateLoginFormData(authForm url.Values, s *goquery.Selection, user *creds.LoginDetails) {
@@ -321,6 +312,28 @@ func updateLoginFormData(authForm url.Values, s *goquery.Selection, user *creds.
 	}
 }
 
+func extractAuthSubmitURL(baseURL string, doc *goquery.Document) (authSubmitURL string, err error){
+	doc.Find("form").Each(func(i int, s *goquery.Selection) {
+		action, ok := s.Attr("action")
+		if !ok {
+			return
+		}
+		authSubmitURL = action
+	})
+
+	if authSubmitURL == "" {
+		err = fmt.Errorf("unable to locate IDP authentication form submit URL")
+		return
+	}
+
+	// account for relative action URI
+	if url, urlErr := url.ParseRequestURI(authSubmitURL); urlErr == nil && !url.IsAbs() {
+		authSubmitURL = fmt.Sprintf("%s%s", baseURL, authSubmitURL)
+	}
+
+	return
+}
+
 func extractFormData(res *http.Response) (url.Values, string, error) {
 	formData := url.Values{}
 	var actionURL string


Test patch:
diff --git a/pkg/provider/pingfed/pingfed_test.go b/pkg/provider/pingfed/pingfed_test.go
index 4591c12..4c8a7ae 100644
--- a/pkg/provider/pingfed/pingfed_test.go
+++ b/pkg/provider/pingfed/pingfed_test.go
@@ -22,3 +22,25 @@ func TestExtractMfaFormData(t *testing.T) {
 	require.Equal(t, "https://authenticator.pingone.com/pingid/ppm/auth/poll", actionURL)
 	require.Equal(t, url.Values{"csrfToken": []string{"fc80998c-34d8-4dd2-925c-3b3be8a0dee8"}}, mfaForm)
 }
+
+var extractAuthSubmitURLTests = []struct {
+        f        string // input html file
+        expected string // expected url
+}{
+	{"example/loginpage.html", "https://example.com/relative/login"},
+	{"example/loginpage_absolute.html", "https://other.example.com/login"},
+}
+
+func TestExtractAuthSubmitURL(t *testing.T) {
+	for _, tt := range extractAuthSubmitURLTests {
+		data, err := ioutil.ReadFile(tt.f)
+		require.Nil(t, err)
+
+		doc, err := goquery.NewDocumentFromReader(bytes.NewReader(data))
+		require.Nil(t, err)
+
+		url, err := extractAuthSubmitURL("https://example.com", doc)
+		require.Nil(t, err)
+		require.Equal(t, tt.expected, url)
+	}
+}