feat: direct token auth & webhook path auth support

This commit is contained in:
2024-02-19 18:19:54 +01:00
parent 1ebd9db897
commit 85f0179d80
3 changed files with 41 additions and 12 deletions

View File

@@ -12,7 +12,7 @@ import json
HTTP_NOT_FOUND = 404 HTTP_NOT_FOUND = 404
DISPATCH_SERVER = None DISPATCH_SERVER = None
AUTH = None DISPATCH_ACCESS_TOKEN = None
def debug_send(uuid, data, fail_it=False): def debug_send(uuid, data, fail_it=False):
'''Dummy function to print and ack a dispatch for debugging''' '''Dummy function to print and ack a dispatch for debugging'''
@@ -92,7 +92,7 @@ def report_failed_dispatch(uuid, error):
'''Inform the server that the dispatch has failed''' '''Inform the server that the dispatch has failed'''
payload = [{ "uuid" : uuid, "error" : error }] payload = [{ "uuid" : uuid, "error" : error }]
response = requests.post(DISPATCH_SERVER + "/report-dispatch-failed", json=payload, auth=AUTH) response = requests.post(DISPATCH_SERVER + "/report-dispatch-failed", json=payload)
if response.status_code not in [200, 204]: if response.status_code not in [200, 204]:
print("Failed to report back failed dispatch for {} ({})".format( print("Failed to report back failed dispatch for {} ({})".format(
@@ -102,7 +102,7 @@ def confirm_dispatch(uuid):
'''Confirm to server that message has been dispatched and can be removed''' '''Confirm to server that message has been dispatched and can be removed'''
payload = [{ "uuid" : uuid }] payload = [{ "uuid" : uuid }]
response = requests.post(DISPATCH_SERVER + "/confirm-dispatch", json=payload, auth=AUTH) response = requests.post(DISPATCH_SERVER + "/confirm-dispatch", json=payload)
if response.status_code not in [200, 204]: if response.status_code not in [200, 204]:
print("Failed to confirm dispatch with server for {} ({})".format( print("Failed to confirm dispatch with server for {} ({})".format(
@@ -115,8 +115,7 @@ if __name__ == "__main__":
formatter_class=argparse.ArgumentDefaultsHelpFormatter) formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('--dispatch-server') parser.add_argument('--dispatch-server')
parser.add_argument('--dispatch-user') parser.add_argument('--dispatch-access-token')
parser.add_argument('--dispatch-password')
parser.add_argument('--ntfy-api-server') parser.add_argument('--ntfy-api-server')
parser.add_argument('--ntfy-api-token') parser.add_argument('--ntfy-api-token')
@@ -136,12 +135,11 @@ if __name__ == "__main__":
dispatch_server = args.dispatch_server or os.environ.get("DISPATCH_SERVER") dispatch_server = args.dispatch_server or os.environ.get("DISPATCH_SERVER")
dispatch_user = args.dispatch_user or os.environ.get("DISPATCH_USER") dispatch_access_token = args.dispatch_access_token or os.environ.get("DISPATCH_ACCESS_TOKEN")
dispatch_password = args.dispatch_password or os.environ.get("DISPATCH_PASSWORD")
# set dispatch server & authentication global # # set dispatch server & authentication global #
DISPATCH_SERVER = dispatch_server DISPATCH_SERVER = dispatch_server
AUTH = (dispatch_user, dispatch_password) DISPATCH_ACCESS_TOKEN = dispatch_access_token
ntfy_api_server = args.ntfy_api_server or os.environ.get("NTFY_API_SERVER") ntfy_api_server = args.ntfy_api_server or os.environ.get("NTFY_API_SERVER")
ntfy_api_token = args.ntfy_api_token or os.environ.get("NTFY_API_TOKEN") ntfy_api_token = args.ntfy_api_token or os.environ.get("NTFY_API_TOKEN")
@@ -159,7 +157,8 @@ if __name__ == "__main__":
while args.loop or first_run: while args.loop or first_run:
# request dispatches # # request dispatches #
response = requests.get(dispatch_server + "/get-dispatch?method=all&timeout=0", auth=AUTH) response = requests.get(dispatch_server +
"/get-dispatch?method=all&timeout=0&dispatch-access-token={}".format(DISPATCH_ACCESS_TOKEN))
# check status # # check status #
if response.status_code == HTTP_NOT_FOUND: if response.status_code == HTTP_NOT_FOUND:

View File

@@ -25,6 +25,15 @@ app = flask.Flask("Signal Notification Gateway")
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///sqlite.db" app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///sqlite.db"
db = SQLAlchemy(app) db = SQLAlchemy(app)
BAD_DISPATCH_ACCESS_TOKEN = "Invalid or missing dispatch-access-token parameter in URL"
class WebHookPaths(db.Model):
__tablename__ = "webhook_paths"
username = Column(String, primary_key=True)
path = Column(String, primary_key=True)
class UserSettings(db.Model): class UserSettings(db.Model):
__tablename__ = "user_settings" __tablename__ = "user_settings"
@@ -153,6 +162,10 @@ def get_dispatch():
timeout = flask.request.args.get("timeout") or 5 # timeout in seconds timeout = flask.request.args.get("timeout") or 5 # timeout in seconds
timeout = int(timeout) timeout = int(timeout)
dispatch_acces_token = flask.request.args.get("dispatch-access-token") or ""
if dispatch_acces_token != app.config["DISPATCH_ACCESS_TOKEN"]:
return (BAD_DISPATCH_ACCESS_TOKEN, 401)
if not method: if not method:
return (500, "Missing Dispatch Target (signal|email|phone|ntfy|all|any)") return (500, "Missing Dispatch Target (signal|email|phone|ntfy|all|any)")
@@ -259,8 +272,9 @@ def confirm_dispatch():
return ("", 204) return ("", 204)
@app.route('/smart-send/<path:path>', methods=["POST"])
@app.route('/smart-send', methods=["POST"]) @app.route('/smart-send', methods=["POST"])
def smart_send_to_clients(): def smart_send_to_clients(path=None):
'''Send to clients based on querying the LDAP '''Send to clients based on querying the LDAP
requests MAY include: requests MAY include:
- list of usernames under key "users" - list of usernames under key "users"
@@ -280,6 +294,19 @@ def smart_send_to_clients():
title = instructions.get("title") title = instructions.get("title")
method = instructions.get("method") method = instructions.get("method")
# authenticated by access token or webhook path #
dispatch_acces_token = flask.request.args.get("dispatch-access-token") or ""
print(path)
if path:
webhook_path = db.session.query(WebHookPaths).filter(WebHookPaths.path==path).first()
if webhook_path:
users = webhook_path.username
groups = None
else:
return ("Invalid Webhook path", 401)
elif dispatch_acces_token != app.config["DISPATCH_ACCESS_TOKEN"]:
return (BAD_DISPATCH_ACCESS_TOKEN, 401)
# allow single use string instead of array # # allow single use string instead of array #
if type(users) == str: if type(users) == str:
users = [users] users = [users]
@@ -346,6 +373,7 @@ def create_app():
} }
app.config["LDAP_ARGS"] = ldap_args app.config["LDAP_ARGS"] = ldap_args
app.config["SETTINGS_ACCESS_TOKEN"] = os.environ["SETTINGS_ACCESS_TOKEN"] app.config["SETTINGS_ACCESS_TOKEN"] = os.environ["SETTINGS_ACCESS_TOKEN"]
app.config["DISPATCH_ACCESS_TOKEN"] = os.environ["DISPATCH_ACCESS_TOKEN"]
if __name__ == "__main__": if __name__ == "__main__":
@@ -363,6 +391,7 @@ if __name__ == "__main__":
parser.add_argument('--ldap-manager-password') parser.add_argument('--ldap-manager-password')
parser.add_argument('--settings-access-token') parser.add_argument('--settings-access-token')
parser.add_argument('--dispatch-access-token')
args = parser.parse_args() args = parser.parse_args()
@@ -376,6 +405,7 @@ if __name__ == "__main__":
app.config["LDAP_NO_READ_ENV"] = True app.config["LDAP_NO_READ_ENV"] = True
app.config["SETTINGS_ACCESS_TOKEN"] = args.settings_access_token app.config["SETTINGS_ACCESS_TOKEN"] = args.settings_access_token
app.config["DISPATCH_ACCESS_TOKEN"] = args.dispatch_access_token
if not any([value is None for value in ldap_args.values()]): if not any([value is None for value in ldap_args.values()]):
app.config["LDAP_ARGS"] = ldap_args app.config["LDAP_ARGS"] = ldap_args

View File

@@ -51,8 +51,8 @@ if __name__ == "__main__":
signal_cli_bin = args.signal_cli_bin signal_cli_bin = args.signal_cli_bin
# request dispatches # # request dispatches #
response = requests.get(args.target + "/get-dispatch?method={}".format(args.method), response = requests.get(args.target +
auth=(args.user, args.password)) "/get-dispatch?method={}&dispatch-access-token={}".format(args.method), args.password)
# check status # # check status #
if response.status_code == HTTP_NOT_FOUND: if response.status_code == HTTP_NOT_FOUND: