Repository: Versent/saml2aws
Pull request #202 (2018-07-25 21:08:36)

fix(Pingfed) Adjusted selectors to be more lenient.
Turns out our implementation of ping doesn't have the same form identifiers as others, to overcome this I have made the code use `pf.pass` to match the login form.


Patch:
diff --git a/pkg/provider/pingfed/example/login2.html b/pkg/provider/pingfed/example/login2.html
new file mode 100644
index 0000000..8f9643b
--- /dev/null
+++ b/pkg/provider/pingfed/example/login2.html
@@ -0,0 +1,158 @@
+<!DOCTYPE html>
+<html lang="en" dir="ltr">
+
+<head>
+    <title>Sign On</title>
+    <base href="https://id.example.com/">
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <meta name="viewport" content="initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
+    <meta http-equiv="x-ua-compatible" content="IE=edge">
+    <link rel="stylesheet" type="text/css" href="assets/css/main.css">
+</head>
+
+<body onload="setFocus()">
+
+    <div class="ping-container ping-signin">
+
+        <!-- 
+    if there is a logo present in the 'company-logo' container,
+    then 'has-logo' class should be added to 'ping-header' container.
+    -->
+        <div class="ping-header">
+            <span class="company-logo">
+                <!-- client company logo here -->
+            </span>
+            Sign On
+        </div>
+        <!-- .ping-header -->
+
+        <div class="ping-body-container">
+
+            <div>
+                <form method="POST" action="/idp/9ABjD/resumeSAML20/idp/startSSO.ping" autocomplete="off">
+                    <div class="ping-messages">
+
+
+
+                    </div>
+
+                    <div class="ping-input-label">
+                        Username
+                    </div>
+                    <div class="ping-input-container">
+                        <input id="username" type="text" size="36" name="pf.username" value="" autocorrect="off" autocapitalize="off" onkeypress="return postOnReturn(event)">
+                        <!---->
+                    </div>
+
+                    <div class="ping-input-label">
+                        Password
+                    </div>
+                    <div class="ping-input-container">
+                        <input id="password" type="password" size="36" name="pf.pass" onkeypress="return postOnReturn(event)">
+                    </div>
+
+
+                    <div class="ping-buttons">
+                        <input type="hidden" name="pf.ok" value="">
+                        <input type="hidden" name="pf.cancel" value="">
+
+                        <a onclick="postOk();" class="ping-button normal allow" title="Sign On">
+                            Sign On
+                        </a>
+                    </div>
+                    <!-- .ping-buttons -->
+
+                    <div class="ping-input-link ping-pass-change">
+                        <a href="/idp/9ABjD/resumeSAML20/idp/startSSO.ping?ChangePassword=true" class="password-change">Change Password?</a>
+                    </div>
+
+                    <input type="hidden" name="pf.adapterId" id="pf.adapterId" value="VersentForm">
+                </form>
+            </div>
+            <!-- .ping-body -->
+        </div>
+        <!-- .ping-body-container -->
+
+        <div class="ping-footer-container">
+            <div class="ping-footer">
+                <div class="ping-credits"></div>
+                <div class="ping-copyright">Copyright © 2003-2017. Ping Identity Corporation. All rights reserved.</div>
+            </div>
+            <!-- .ping-footer -->
+        </div>
+        <!-- .ping-footer-container -->
+
+    </div>
+    <!-- .ping-container -->
+
+    <script type="text/javascript">
+
+        function getForgotPasswordUrl() {
+            var base = "https://id.example.com/ext/pwdreset/Identify?referrer=https%3A%2F%2Fid.example.com%2Fidp%2F9ABjD%2FresumeSAML20%2Fidp%2FstartSSO.ping&adapterId=ExampleForm";
+            window.location.href = base;
+        }
+
+        function postOk() {
+            document.forms[0]['pf.ok'].value = 'clicked';
+            document.forms[0].submit();
+        }
+        function postCancel() {
+            document.forms[0]['pf.cancel'].value = 'clicked';
+            document.forms[0].submit();
+        }
+        function postOnReturn(e) {
+            var keycode;
+            if (window.event) keycode = window.event.keyCode;
+            else if (e) keycode = e.which;
+            else return true;
+
+            if (keycode == 13) {
+                document.forms[0].submit();
+                return false;
+            } else {
+                return true;
+            }
+        }
+        function setFocus() {
+            var platform = navigator.platform;
+            if (platform != null && platform.indexOf("iPhone") == -1) {
+                document.getElementById('username').focus();
+            }
+        }
+        function setMobile(mobile) {
+            var className = ' mobile',
+                hasClass = (bodyTag.className.indexOf(className) !== -1);
+
+            if (mobile && !hasClass) {
+                bodyTag.className += className;
+
+            } else if (!mobile && hasClass) {
+                bodyTag.className = bodyTag.className.replace(className, '');
+            }
+
+        }
+        function getScreenWidth() {
+            return (window.outerHeight) ? window.outerWidth : document.body.clientWidth;
+        }
+
+        var bodyTag = document.getElementsByTagName('body')[0],
+            width = getScreenWidth(),
+            remember = false && false;
+
+
+        if (/Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
+            setMobile(true);
+        } else {
+            setMobile((width <= 480));
+            window.onresize = function () {
+                width = getScreenWidth();
+                setMobile((width <= 480));
+            }
+        }
+    </script>
+
+
+
+</body>
+
+</html>
\ No newline at end of file
diff --git a/pkg/provider/pingfed/pingfed.go b/pkg/provider/pingfed/pingfed.go
index 19f7578..f9fd629 100644
--- a/pkg/provider/pingfed/pingfed.go
+++ b/pkg/provider/pingfed/pingfed.go
@@ -106,7 +106,7 @@ func (ac *Client) handleLogin(ctx context.Context, doc *goquery.Document) (conte
 		return ctx, nil, fmt.Errorf("no context value for 'login'")
 	}
 
-	form, err := page.NewFormFromDocument(doc, "#login-form")
+	form, err := page.NewFormFromDocument(doc, "form")
 	if err != nil {
 		return ctx, nil, errors.Wrap(err, "error extracting login form")
 	}
@@ -190,7 +190,7 @@ func (ac *Client) handleFormRedirect(ctx context.Context, doc *goquery.Document)
 }
 
 func docIsLogin(doc *goquery.Document) bool {
-	return doc.Has("form#login-form").Size() == 1
+	return doc.Has("input[name=\"pf.pass\"]").Size() == 1
 }
 
 func docIsOTP(doc *goquery.Document) bool {


Test patch:
diff --git a/pkg/provider/pingfed/pingfed_test.go b/pkg/provider/pingfed/pingfed_test.go
index 09beb93..14b125f 100644
--- a/pkg/provider/pingfed/pingfed_test.go
+++ b/pkg/provider/pingfed/pingfed_test.go
@@ -24,6 +24,7 @@ var docTests = []struct {
 	expected bool
 }{
 	{docIsLogin, "example/login.html", true},
+	{docIsLogin, "example/login2.html", true},
 	{docIsLogin, "example/otp.html", false},
 	{docIsLogin, "example/swipe.html", false},
 	{docIsLogin, "example/form-redirect.html", false},