fake-juniper-sso-server.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. #!/usr/bin/env python3
  2. #
  3. # Copyright © 2021 Joachim Kuebart <joachim.kuebart@gmail.com>
  4. #
  5. # This file is part of openconnect.
  6. #
  7. # This is free software; you can redistribute it and/or
  8. # modify it under the terms of the GNU Lesser General Public License
  9. # as published by the Free Software Foundation; either version 2.1 of
  10. # the License, or (at your option) any later version.
  11. #
  12. # This library is distributed in the hope that it will be useful, but
  13. # WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. # Lesser General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU Lesser General Public License
  18. # along with this program. If not, see <http://www.gnu.org/licenses/>
  19. from flask import (
  20. Flask, make_response, redirect, render_template_string, request,
  21. url_for
  22. )
  23. import re
  24. import ssl
  25. import sys
  26. host, port, *cert_and_maybe_keyfile = sys.argv[1:]
  27. context = ssl.SSLContext()
  28. context.load_cert_chain(*cert_and_maybe_keyfile)
  29. app = Flask(__name__)
  30. @app.route("/")
  31. def root():
  32. # Step 0. Step 11.
  33. return redirect(url_for("welcome"))
  34. @app.route("/dana-na/auth/url_default/welcome.cgi")
  35. def welcome():
  36. if request.args.get("p") != "preauth":
  37. # Step 1. Step 12.
  38. return redirect(url_for("login"))
  39. if request.cookies.get("DSPREAUTH") != "success":
  40. # Step 14: set DSPREAUTH.
  41. resp = make_response("Waiting for host checker…")
  42. resp.set_cookie("DSPREAUTH", "hostchecker")
  43. return resp
  44. # Step 15.
  45. return redirect(url_for("login", loginmode="mode_postAuth"))
  46. @app.route("/dana-na/auth/url_default/login.cgi")
  47. def login():
  48. if request.cookies.get("DSASSERTREF") != "assert_ref":
  49. # Step 2.
  50. return redirect(url_for("ls"))
  51. if request.args.get("loginmode") != "mode_postAuth":
  52. # Step 13.
  53. return redirect(url_for("welcome", p="preauth"))
  54. # Step 16: set DSID.
  55. resp = redirect(url_for("starter0"))
  56. resp.set_cookie("DSID", "dsid")
  57. return resp
  58. @app.route("/adfs/ls/", methods=["GET", "POST"])
  59. def ls():
  60. if request.cookies.get("MSISAuth") != "success":
  61. if (
  62. request.method == "GET" or
  63. request.form.get("UserName") != "test@example.com" or
  64. request.form.get("Password") != "test"
  65. ):
  66. # Step 3: user/password form.
  67. return render_template_string("""<!doctype html>
  68. <form action="{{ url_for("ls") }}"
  69. id="loginForm"
  70. method="post">
  71. <input id="userNameInput" name="UserName" type="email">
  72. <input id="passwordInput" name="Password" type="password">
  73. <input id="KmsiInput" name="Kmsi" type="checkbox">
  74. <input id="optionForms"
  75. name="AuthMethod"
  76. type="hidden"
  77. value="FormsAuthentication">
  78. </form>
  79. <form action="{{ url_for("ls") }}" id="options" method="post">
  80. <input id="optionSelection"
  81. name="AuthMethod"
  82. type="hidden">
  83. </form>""")
  84. # Step 4: username/password success.
  85. resp = redirect(url_for("ls"))
  86. resp.set_cookie("MSISAuth", "success")
  87. return resp
  88. if request.cookies.get("MSISAuth1") != "success":
  89. if (
  90. "VerificationCode" not in request.form or
  91. not re.match("^\\d{6}$", request.form["VerificationCode"])
  92. ):
  93. # Step 5. Step 6: TOTP form.
  94. return render_template_string("""<!doctype html>
  95. <form id="loginForm" method="post">
  96. <input id="autheMethod"
  97. name="AuthMethod"
  98. type="hidden"
  99. value="AzureMfaAuthentication">
  100. <input id="context" name="Context" type="hidden">
  101. <input id="__EVENTTARGET"
  102. name="__EVENTTARGET"
  103. type="hidden">
  104. {% if request.method == "POST" %}
  105. <input id="verificationCodeInput"
  106. name="VerificationCode"
  107. type="text">
  108. <input id="signInButton"
  109. name="SignIn"
  110. type="submit"
  111. value="Sign in">
  112. {% endif %}
  113. </form>
  114. <form action="{{ url_for("ls") }}" id="options" method="post">
  115. <input id="optionSelection"
  116. name="AuthMethod"
  117. type="hidden">
  118. </form>""")
  119. # Step 7: TOTP success.
  120. resp = redirect(url_for("ls"))
  121. resp.set_cookie("MSISAuth1", "success")
  122. return resp
  123. # Step 8.
  124. return render_template_string("""<!doctype html>
  125. <form action="{{ url_for("saml_consumer0") }}"
  126. method="post"
  127. name="hiddenform">
  128. <input name="SAMLResponse" type="hidden">
  129. <input name="RelayState" type="hidden">
  130. <input type="submit" value="Submit">
  131. </form>""")
  132. @app.route("/data-na/auth/saml-consumer0.cgi", methods=["POST"])
  133. def saml_consumer0():
  134. # Step 9: in reality, this is hosted on a different domain than the
  135. # next step.
  136. return render_template_string("""<!doctype html>
  137. <form action="{{ url_for("saml_consumer") }}"
  138. id="formSAMLSSO"
  139. method="post">
  140. <input id="RelayState" name="RelayState" type="hidden">
  141. <input id="SAMLResponse" name="SAMLResponse" type="hidden">
  142. <input id="input_saml-response-post_1"
  143. type="submit"
  144. value="Continue">
  145. </form>""")
  146. @app.route("/data-na/auth/saml-consumer.cgi", methods=["POST"])
  147. def saml_consumer():
  148. # Step 10.
  149. resp = redirect(url_for("root"))
  150. resp.set_cookie("DSASSERTREF", "assert_ref")
  151. return resp
  152. @app.route("/dana/home/starter0.cgi")
  153. def starter0():
  154. # Need to provide a form to make the parser happy.
  155. return "<form></form>The DSID is in your cookie jar."
  156. app.run(host=host, port=int(port), ssl_context=context)