1- from flask import Flask , Response , request , jsonify , make_response
2- import os
3- import json
4- import requests
5- import re
6- import base64
7- import logging
8- from urllib .parse import unquote
9-
10- app = Flask ("Secured Signal Api" )
11-
12- app .logger .setLevel (logging .INFO )
13-
14- DEFAULT_BLOCKED_ENDPOINTS = [
15- "/v1/about" ,
16- "/v1/configuration" ,
17- "/v1/devices" ,
18- "/v1/register" ,
19- "/v1/unregister" ,
20- "/v1/qrcodelink" ,
21- "/v1/accounts" ,
22- "/v1/contacts"
23- ]
24-
25- SENDER = os .getenv ("SENDER" )
26- DEFAULT_RECIPIENTS = os .getenv ("DEFAULT_RECIPIENTS" )
27- SIGNAL_API_URL = os .getenv ("SIGNAL_API_URL" )
28- API_TOKEN = os .getenv ("API_TOKEN" )
29-
30- BLOCKED_ENDPOINTS = os .getenv ("BLOCKED_ENDPOINTS" )
31- VARIABLES = os .getenv ("VARIABLES" )
32-
33- secure = False
34-
1+ from flask import Flask , Response , request , jsonify , make_response
2+ import os
3+ import json
4+ import requests
5+ import re
6+ import base64
7+ import logging
8+ from urllib .parse import unquote
9+
10+ app = Flask ("Secured Signal Api" )
11+
12+ app .logger .setLevel (logging .INFO )
13+
14+ DEFAULT_BLOCKED_ENDPOINTS = [
15+ "/v1/about" ,
16+ "/v1/configuration" ,
17+ "/v1/devices" ,
18+ "/v1/register" ,
19+ "/v1/unregister" ,
20+ "/v1/qrcodelink" ,
21+ "/v1/accounts" ,
22+ "/v1/contacts"
23+ ]
24+
25+ SENDER = os .getenv ("SENDER" )
26+ DEFAULT_RECIPIENTS = os .getenv ("DEFAULT_RECIPIENTS" )
27+ SIGNAL_API_URL = os .getenv ("SIGNAL_API_URL" )
28+ API_TOKEN = os .getenv ("API_TOKEN" )
29+
30+ BLOCKED_ENDPOINTS = os .getenv ("BLOCKED_ENDPOINTS" )
31+ VARIABLES = os .getenv ("VARIABLES" )
32+
33+ secure = False
34+
3535def fillInVars (obj ):
36- if isinstance (obj , dict ):
36+ if isinstance (obj , dict ):
3737 for key , value in obj .items ():
3838 obj [key ] = fillInVars (value )
3939 elif isinstance (obj , list ):
4040 for i in range (len (obj )):
4141 obj [i ] = fillInVars (obj [i ])
4242 elif isinstance (obj , str ):
43- matches = re .findall (r"\${(.*?)}" , obj )
44- for match in matches :
43+ matches = re .findall (r"\${(.*?)}" , obj )
44+ for match in matches :
4545 if match in VARIABLES :
4646 value = VARIABLES [match ]
4747
@@ -50,123 +50,123 @@ def fillInVars(obj):
5050 return newValue
5151 else :
5252 return value
53- return obj
54-
55- def UnauthorizedResponse (prompt = None ):
56- headers = {}
57-
58- if prompt :
59- headers = {
60- "WWW-Authenticate" : 'Basic realm="Login Required", Bearer realm="Access Token Required"'
61- }
62-
63- return Response (
64- "Unauthorized" , 401 ,
65- headers
66- )
67-
68- @app .before_request
69- def middlewares ():
70- for blockedPath in BLOCKED_ENDPOINTS :
71- if request .path .startswith (blockedPath ):
72- infoLog (f"Client tried to access Blocked Endpoint [{ blockedPath } ]" )
73- return Response ("Forbidden" , 401 )
74-
75- if secure :
76- auth_header = request .headers .get ("Authorization" , "" )
77-
78- if auth_header .startswith ("Bearer " ):
79- token = auth_header .split (" " , 1 )[1 ]
80-
81- token = unquote (token )
82- if token != API_TOKEN :
83- infoLog (f"Client failed Bearer Auth [token: { token } ]" )
84- return UnauthorizedResponse ()
85- elif auth_header .startswith ("Basic " ):
86- try :
87- decoded = base64 .b64decode (auth_header .split (" " , 1 )[1 ]).decode ()
88- username , password = decoded .split (":" , 1 )
89-
90- username = unquote (username )
91- password = unquote (password )
92- if username != "api" or password != API_TOKEN :
93- infoLog (f"Client failed Basic Auth [user: { username } , pw:{ password } ]" )
94- return UnauthorizedResponse ()
95- except Exception as error :
96- errorLog (f"Unexpected Error during Basic Auth: { error } " )
97- return UnauthorizedResponse ()
98- else :
99- infoLog (f"Client did not provide any Auth Method" )
100- return UnauthorizedResponse (True )
101-
102-
103- @app .route ('/' , defaults = {'path' : '' }, methods = ['GET' , 'POST' , 'PUT' ])
104- @app .route ('/<path:path>' , methods = ['GET' , 'POST' , 'PUT' ])
105- def proxy (path ):
106- method = request .method
107- incomingJSON = request .get_json (force = True , silent = True )
108- jsonData = incomingJSON
109- headers = {k : v for k , v in request .headers if k .lower () != 'host' }
110-
111- if incomingJSON :
112- jsonData = fillInVars (incomingJSON )
113-
114- if "${NUMBER}" in path :
115- path = path .replace ("${NUMBER}" , SENDER )
116-
117- query_string = request .query_string .decode ()
118-
119- if request .query_string .decode ():
120- query_string = "?" + request .query_string .decode ()
121-
122- targetURL = f"{ SIGNAL_API_URL } /{ path } { query_string } "
123-
124- infoLog (json .dumps (jsonData ))
125-
126- resp = requests .request (
127- method = method ,
128- url = targetURL ,
129- headers = headers ,
130- json = jsonData
131- )
132-
133- infoLog (f"Forwarded { resp .text } to { targetURL } [{ method } ]" )
134-
135- # return Response(resp.content, status=resp.status_code, headers=dict(resp.headers))
136-
137- response = make_response (resp .json ())
138- response .status_code = resp .status_code
139-
140- return response
141-
142- def infoLog (msg ):
143- app .logger .info (msg )
144-
145- def errorLog (msg ):
146- app .logger .error (msg )
147-
148- if __name__ == '__main__' :
149- if SENDER and SIGNAL_API_URL :
150- if API_TOKEN == None or API_TOKEN == "" :
151- infoLog ("No API Token set (API_TOKEN), this is not recommended!" )
152- else :
153- secure = True
154- infoLog ("API Token set, use Bearer or Basic Auth (user: api) for authentication" )
155-
156- if DEFAULT_RECIPIENTS != None and DEFAULT_RECIPIENTS != "" :
157- DEFAULT_RECIPIENTS = json .loads (DEFAULT_RECIPIENTS )
158-
159- if BLOCKED_ENDPOINTS != None and BLOCKED_ENDPOINTS != "" :
160- BLOCKED_ENDPOINTS = json .loads (BLOCKED_ENDPOINTS )
161- else :
162- BLOCKED_ENDPOINTS = DEFAULT_BLOCKED_ENDPOINTS
163-
164- if VARIABLES != None and VARIABLES != "" :
165- VARIABLES = json .loads (VARIABLES )
166- else :
167- VARIABLES = {
168- "NUMBER" : SENDER ,
169- "RECIPIENTS" : DEFAULT_RECIPIENTS
170- }
171-
172- app .run (debug = False , port = 8880 , host = '0.0.0.0' )
53+ return obj
54+
55+ def UnauthorizedResponse (prompt = None ):
56+ headers = {}
57+
58+ if prompt :
59+ headers = {
60+ "WWW-Authenticate" : 'Basic realm="Login Required", Bearer realm="Access Token Required"'
61+ }
62+
63+ return Response (
64+ "Unauthorized" , 401 ,
65+ headers
66+ )
67+
68+ @app .before_request
69+ def middlewares ():
70+ for blockedPath in BLOCKED_ENDPOINTS :
71+ if request .path .startswith (blockedPath ):
72+ infoLog (f"Client tried to access Blocked Endpoint [{ blockedPath } ]" )
73+ return Response ("Forbidden" , 401 )
74+
75+ if secure :
76+ auth_header = request .headers .get ("Authorization" , "" )
77+
78+ if auth_header .startswith ("Bearer " ):
79+ token = auth_header .split (" " , 1 )[1 ]
80+
81+ token = unquote (token )
82+ if token != API_TOKEN :
83+ infoLog (f"Client failed Bearer Auth [token: { token } ]" )
84+ return UnauthorizedResponse ()
85+ elif auth_header .startswith ("Basic " ):
86+ try :
87+ decoded = base64 .b64decode (auth_header .split (" " , 1 )[1 ]).decode ()
88+ username , password = decoded .split (":" , 1 )
89+
90+ username = unquote (username )
91+ password = unquote (password )
92+ if username != "api" or password != API_TOKEN :
93+ infoLog (f"Client failed Basic Auth [user: { username } , pw:{ password } ]" )
94+ return UnauthorizedResponse ()
95+ except Exception as error :
96+ errorLog (f"Unexpected Error during Basic Auth: { error } " )
97+ return UnauthorizedResponse ()
98+ else :
99+ infoLog (f"Client did not provide any Auth Method" )
100+ return UnauthorizedResponse (True )
101+
102+
103+ @app .route ('/' , defaults = {'path' : '' }, methods = ['GET' , 'POST' , 'PUT' ])
104+ @app .route ('/<path:path>' , methods = ['GET' , 'POST' , 'PUT' ])
105+ def proxy (path ):
106+ method = request .method
107+ incomingJSON = request .get_json (force = True , silent = True )
108+ jsonData = incomingJSON
109+ headers = {k : v for k , v in request .headers if k .lower () != 'host' }
110+
111+ if incomingJSON :
112+ jsonData = fillInVars (incomingJSON )
113+
114+ if "${NUMBER}" in path :
115+ path = path .replace ("${NUMBER}" , SENDER )
116+
117+ query_string = request .query_string .decode ()
118+
119+ if request .query_string .decode ():
120+ query_string = "?" + request .query_string .decode ()
121+
122+ targetURL = f"{ SIGNAL_API_URL } /{ path } { query_string } "
123+
124+ infoLog (json .dumps (jsonData ))
125+
126+ resp = requests .request (
127+ method = method ,
128+ url = targetURL ,
129+ headers = headers ,
130+ json = jsonData
131+ )
132+
133+ infoLog (f"Forwarded { resp .text } to { targetURL } [{ method } ]" )
134+
135+ # return Response(resp.content, status=resp.status_code, headers=dict(resp.headers))
136+
137+ response = make_response (resp .json ())
138+ response .status_code = resp .status_code
139+
140+ return response
141+
142+ def infoLog (msg ):
143+ app .logger .info (msg )
144+
145+ def errorLog (msg ):
146+ app .logger .error (msg )
147+
148+ if __name__ == '__main__' :
149+ if SENDER and SIGNAL_API_URL :
150+ if API_TOKEN == None or API_TOKEN == "" :
151+ infoLog ("No API Token set (API_TOKEN), this is not recommended!" )
152+ else :
153+ secure = True
154+ infoLog ("API Token set, use Bearer or Basic Auth (user: api) for authentication" )
155+
156+ if DEFAULT_RECIPIENTS != None and DEFAULT_RECIPIENTS != "" :
157+ DEFAULT_RECIPIENTS = json .loads (DEFAULT_RECIPIENTS )
158+
159+ if BLOCKED_ENDPOINTS != None and BLOCKED_ENDPOINTS != "" :
160+ BLOCKED_ENDPOINTS = json .loads (BLOCKED_ENDPOINTS )
161+ else :
162+ BLOCKED_ENDPOINTS = DEFAULT_BLOCKED_ENDPOINTS
163+
164+ if VARIABLES != None and VARIABLES != "" :
165+ VARIABLES = json .loads (VARIABLES )
166+ else :
167+ VARIABLES = {
168+ "NUMBER" : SENDER ,
169+ "RECIPIENTS" : DEFAULT_RECIPIENTS
170+ }
171+
172+ app .run (debug = False , port = 8880 , host = '0.0.0.0' )
0 commit comments