|
@@ -0,0 +1,165 @@
|
|
|
|
+Some explanation on components used for the authentication proxy...
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+### Show simple page
|
|
|
|
+Creates an object of the class [HTTPServer](https://docs.python.org/3/library/http.server.html) which takes as parameter:
|
|
|
|
+ - a tuple of (host, port) to bind. Empty host binds to all interfaces.
|
|
|
|
+ - an object of the class BaseHTTPRequestHandlerto to answer http requests, e.g. GET, POST, ...
|
|
|
|
+
|
|
|
|
+```
|
|
|
|
+from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
|
|
+httpd = HTTPServer(('', 8000), BaseHTTPRequestHandler)
|
|
|
|
+httpd.serve_forever()
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+To actually answer http requests, a new class has to be defined
|
|
|
|
+which inherits BaseHTTPRequestHandler an overwrites the functions
|
|
|
|
+to handle different http request types such a GET, POST, ...
|
|
|
|
+```
|
|
|
|
+from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
|
|
+
|
|
|
|
+HOST='localhost'
|
|
|
|
+PORT=8000
|
|
|
|
+
|
|
|
|
+s = """<html>
|
|
|
|
+<head><title>hello, world</title></head>
|
|
|
|
+<body>hello, world</body>
|
|
|
|
+</html>"""
|
|
|
|
+
|
|
|
|
+class OtpRequestHandler(BaseHTTPRequestHandler):
|
|
|
|
+ def do_GET(self):
|
|
|
|
+ self.send_response(200) #OK
|
|
|
|
+ self.send_header("Content-type", "text/html")
|
|
|
|
+ self.end_headers()
|
|
|
|
+ self.wfile.write(bytes(s, "utf-8"))
|
|
|
|
+
|
|
|
|
+httpd = HTTPServer((HOST, PORT), OtpRequestHandler)
|
|
|
|
+httpd.serve_forever()
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+To test: `curl http://localhost:8000/`
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+### Print POST data
|
|
|
|
+- To read data received via post we need to obtain the length
|
|
|
|
+ of the post data from the http-header first.
|
|
|
|
+- Form data is sent with the content-type application/x-www-form-urlencoded
|
|
|
|
+
|
|
|
|
+```
|
|
|
|
+from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
|
|
+
|
|
|
|
+class OtpRequestHandler(BaseHTTPRequestHandler):
|
|
|
|
+ def do_POST(self):
|
|
|
|
+ length = int(self.headers['Content-Length'])
|
|
|
|
+ print(self.headers['content-type'])
|
|
|
|
+ print(self.rfile.read(length))
|
|
|
|
+
|
|
|
|
+httpd = HTTPServer(('localhost', 8000), OtpRequestHandler)
|
|
|
|
+httpd.serve_forever()
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+To Test: `curl -d "hello, world" -X POST http://localhost:8000/`
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+### Parse HTML form data
|
|
|
|
+- parse_qs parses the form data into a dictionary.
|
|
|
|
+- Form values are stored as list in the dicionary,
|
|
|
|
+ which has to be converted to a string
|
|
|
|
+
|
|
|
|
+```
|
|
|
|
+from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
|
|
+from urllib.parse import parse_qs
|
|
|
|
+
|
|
|
|
+class OtpRequestHandler(BaseHTTPRequestHandler):
|
|
|
|
+ def do_POST(self):
|
|
|
|
+ length = int(self.headers['Content-Length'])
|
|
|
|
+ parms = parse_qs(self.rfile.read(length).decode("utf-8"))
|
|
|
|
+ print("User: " + ''.join(parms["user"]) )
|
|
|
|
+ print("OTP: " + ''.join(parms["otp"]) )
|
|
|
|
+
|
|
|
|
+httpd = HTTPServer(('localhost', 8000), OtpRequestHandler)
|
|
|
|
+httpd.serve_forever()
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+To Test: `curl -d "user=jdoe&otp=204177" -X POST http://localhost:8000/`
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+### Set Cookie
|
|
|
|
+```
|
|
|
|
+from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
|
|
+from http.cookies import SimpleCookie
|
|
|
|
+import secrets
|
|
|
|
+
|
|
|
|
+cookie = SimpleCookie()
|
|
|
|
+cookie['token'] = secrets.token_urlsafe(16)
|
|
|
|
+#cookie["token"]["domain"] = "s-up.net"
|
|
|
|
+cookie["token"]["path"] = "/"
|
|
|
|
+#cookie["token"]["secure"] = True
|
|
|
|
+cookie["token"]["httponly"] = True
|
|
|
|
+cookies.Morsel._reserved.setdefault('samesite', 'SameSite')
|
|
|
|
+cookie["token"]["samesite"] = "strict"
|
|
|
|
+cookie["token"]["expires"] = 60 * 60 * 6 # 6h
|
|
|
|
+
|
|
|
|
+class OtpRequestHandler(BaseHTTPRequestHandler):
|
|
|
|
+ def do_GET(self):
|
|
|
|
+ self.send_response(200) #OK
|
|
|
|
+ self.send_header("Content-type", "text/html")
|
|
|
|
+ self.send_header('Set-Cookie', cookie.output(header=''))
|
|
|
|
+ self.end_headers()
|
|
|
|
+ self.wfile.write(bytes("Hello", "utf-8"))
|
|
|
|
+
|
|
|
|
+httpd = HTTPServer(('localhost', 8000), OtpRequestHandler)
|
|
|
|
+httpd.serve_forever()
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+### Get Cookie
|
|
|
|
+```
|
|
|
|
+from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
|
|
+from http.cookies import SimpleCookie
|
|
|
|
+import secrets
|
|
|
|
+
|
|
|
|
+class OtpRequestHandler(BaseHTTPRequestHandler):
|
|
|
|
+ def do_GET(self):
|
|
|
|
+ self.send_response(200) #OK
|
|
|
|
+ self.send_header("Content-type", "text/html")
|
|
|
|
+ self.end_headers()
|
|
|
|
+ cookies = SimpleCookie(self.headers.get('Cookie'))
|
|
|
|
+ token = cookies['token'].value
|
|
|
|
+ self.wfile.write(bytes(token, "utf-8"))
|
|
|
|
+ print(token)
|
|
|
|
+
|
|
|
|
+httpd = HTTPServer(('localhost', 8000), OtpRequestHandler)
|
|
|
|
+httpd.serve_forever()
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+### TOTP
|
|
|
|
+```
|
|
|
|
+import pyotp
|
|
|
|
+import time
|
|
|
|
+
|
|
|
|
+# Get a random secret
|
|
|
|
+secret=pyotp.random_base32()
|
|
|
|
+print(secret)
|
|
|
|
+
|
|
|
|
+# Calculate OTP
|
|
|
|
+otp=pyotp.TOTP(secret).now()
|
|
|
|
+print(otp)
|
|
|
|
+
|
|
|
|
+# Verify OTP, should be true
|
|
|
|
+pyotp.TOTP(secret).verify(otp)
|
|
|
|
+
|
|
|
|
+# Verify OTP again after 30s, should be false
|
|
|
|
+time.sleep(30)
|
|
|
|
+pyotp.TOTP(secret).verify(otp)
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+## References
|
|
|
|
+
|
|
|
|
+- [Online TOTP Generator](https://totp.danhersam.com/)
|
|
|
|
+- [simpleotp on Github](https://github.com/newhouseb/simpleotp)
|