From bd7d14d909afcedf10272af55febe98f389c4cde Mon Sep 17 00:00:00 2001 From: Zhihao Ma Date: Thu, 6 Feb 2025 01:38:40 -0500 Subject: [PATCH 1/2] adds an example HTTP authentication source service implemented using python --- .../external_HTTP_API.py | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 addons/example_external_auth/external_HTTP_API.py diff --git a/addons/example_external_auth/external_HTTP_API.py b/addons/example_external_auth/external_HTTP_API.py new file mode 100644 index 000000000000..6170d09368b0 --- /dev/null +++ b/addons/example_external_auth/external_HTTP_API.py @@ -0,0 +1,136 @@ +# If you are running your external HTTP auth server (for example, this script) on a PacketFence server, +# please make sure that you are configuring your authentication source using the appropriate hostname or IP. +# typically you need to use "containers-gateway.internal" or "100.64.0.1" instead of "127.0.0.1" +# because PacketFence services are now running inside containers using docker, and "127.0.0.1" refers to the +# loopback of the container, not the PacketFence server. + +# When using external HTTP authentication source, PacketFence will take parameters such as username and password +# from Form Values. If the programming language you are using does not have a built-in method to extract POST fields, +# you might need to test and debug by specifying this header: "Content-Type: application/x-www-form-urlencoded" + +import json +from http.server import BaseHTTPRequestHandler, HTTPServer +import urllib.parse + + +class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): + res_code_SUCCESS = 1 + res_code_FAILURE = 0 + + def do_POST(self): + if self.path == '/authenticate': + self.handler_authenticate() + elif self.path == '/authorize': + self.handler_authorize() + else: + self.send_response(404) + self.send_header('Content-type', 'application/json') + self.end_headers() + r = { + "result": self.res_code_FAILURE, + "message": "Not Found" + } + self.wfile.write(json.dumps(r).encode()) + + def extract_credential(self): + if 'Content-Length' not in self.headers: + r = { + "result": self.res_code_FAILURE, + "message": "Invalid POST payload, missing header Content-Length" + } + return r, None, None + + try: + content_length = int(self.headers['Content-Length']) + post_data = self.rfile.read(content_length) + except ValueError: + r = { + "result": self.res_code_FAILURE, + "message": "Invalid Content-Length" + } + return r, None, None + except Exception as e: + r = { + "result": self.res_code_FAILURE, + "message": str(e) + } + return r, None, None + + try: + data = urllib.parse.parse_qs(post_data.decode()) + + if 'username' not in data or 'password' not in data: + r = { + "result": self.res_code_FAILURE, + "message": "Invalid json payload, missing username or password" + } + return r, None, None + else: + username = data.get("username", [''])[0] + password = data.get("password", [''])[0] + + return None, username, password + + except json.JSONDecodeError as e: + r = { + "result": self.res_code_FAILURE, + "message": f"JSON decode error: {str(e)}" + } + return r, None, None + except Exception as e: + r = { + "result": self.res_code_FAILURE, + "message": f"JSON decode error: {str(e)}" + } + return r, None, None + + def handler_authenticate(self): + err, username, password = self.extract_credential() + + if err is not None: + self.send_response(400) + self.send_header('Content-type', 'application/json') + self.end_headers() + self.wfile.write(json.dumps(err).encode()) + + if username == 'test' and password == 'testing123': + r = { + "result": self.res_code_SUCCESS, + "message": "ok" + } + self.send_response(200) + else: + r = { + "result": self.res_code_FAILURE, + "message": "Unauthorized: bad username or password" + } + self.send_response(401) + + self.send_header('Content-type', 'application/json') + self.end_headers() + self.wfile.write(json.dumps(r).encode()) + + def handler_authorize(self): + r = { + "access_duration": "1D", + "access_level": "ALL", + "sponsor": 1, + "unregdate": "2030-01-01 00:00:00", + "category": "default", + } + self.send_response(200) + + self.send_header('Content-type', 'application/json') + self.end_headers() + self.wfile.write(json.dumps(r).encode()) + + +def run(server_class=HTTPServer, handler_class=SimpleHTTPRequestHandler, port=10000): + server_address = ('', port) + httpd = server_class(server_address, handler_class) + print(f'Starting server on port {port}...') + httpd.serve_forever() + + +if __name__ == '__main__': + run() From 22b394fc97f6d4a3600939bd63145cbfe35171e0 Mon Sep 17 00:00:00 2001 From: Zhihao Ma Date: Thu, 6 Feb 2025 16:55:48 -0500 Subject: [PATCH 2/2] update documentation --- addons/example_external_auth/README.asciidoc | 12 ++++++++++++ .../mock_servers}/external_HTTP_API.py | 0 2 files changed, 12 insertions(+) rename {addons/example_external_auth => t/mock_servers}/external_HTTP_API.py (100%) diff --git a/addons/example_external_auth/README.asciidoc b/addons/example_external_auth/README.asciidoc index d846099dac05..480e6e46f2b9 100644 --- a/addons/example_external_auth/README.asciidoc +++ b/addons/example_external_auth/README.asciidoc @@ -1,3 +1,15 @@ +== Mock HTTP API server using python (recommended) + +We provide a python implementation of this HTTP API server. It is not designed for production use. +but all the necessary steps, arguments extracting, basic authentication and authorization mechanism are provided in this script. +You can take this python code snippet to test and build your own API server using any language you'd prefer. + +The python script is ready to use as python is a package pre-installed on most of the linux distros. +Please kindly check out the script in `/t/mock_servers/external_HTTP_API.py` from PacketFence repository, make any changes +necessary for debugging and troubleshooting. + + +== Mock HTTP API server by PHP First install php on your server Then add the following in /usr/local/pf/conf/httpd.conf.d/httpd.aaa.tt diff --git a/addons/example_external_auth/external_HTTP_API.py b/t/mock_servers/external_HTTP_API.py similarity index 100% rename from addons/example_external_auth/external_HTTP_API.py rename to t/mock_servers/external_HTTP_API.py