2018-10-05 22:16:34 +00:00
|
|
|
#--require-config tls-api-key
|
|
|
|
#--require-config tls-api-certificate
|
|
|
|
|
|
|
|
import http.server, json, ssl, threading, uuid, urllib.parse
|
2018-10-12 17:07:23 +00:00
|
|
|
from src import ModuleManager, utils
|
2018-10-04 15:01:13 +00:00
|
|
|
|
|
|
|
_bot = None
|
|
|
|
_events = None
|
2018-11-06 17:22:50 +00:00
|
|
|
_log = None
|
2018-10-04 15:01:13 +00:00
|
|
|
class Handler(http.server.BaseHTTPRequestHandler):
|
2018-10-04 16:10:05 +00:00
|
|
|
timeout = 10
|
2019-02-08 21:52:24 +00:00
|
|
|
|
|
|
|
def _path_data(self):
|
|
|
|
path = urllib.parse.urlparse(self.path).path
|
2018-10-05 21:49:06 +00:00
|
|
|
_, _, endpoint = path[1:].partition("/")
|
|
|
|
endpoint, _, args = endpoint.partition("/")
|
|
|
|
args = list(filter(None, args.split("/")))
|
2019-02-08 21:56:58 +00:00
|
|
|
return path, endpoint, args
|
2019-02-08 21:52:24 +00:00
|
|
|
|
|
|
|
def _url_params(self):
|
|
|
|
parsed = urllib.parse.urlparse(self.path)
|
2019-02-08 21:55:42 +00:00
|
|
|
query = urllib.parse.parse_qs(parsed.query)
|
2019-02-08 21:52:24 +00:00
|
|
|
return dict([(k, v[0]) for k, v in query.items()])
|
|
|
|
|
|
|
|
def _body(self):
|
|
|
|
content_length = int(self.headers.get("content-length", 0))
|
|
|
|
return self.rfile.read(content_length)
|
|
|
|
|
2019-02-08 22:04:39 +00:00
|
|
|
def _respond(self, code, headers, data):
|
|
|
|
self.send_response(code)
|
2019-02-08 22:53:33 +00:00
|
|
|
for key, value in headers.items():
|
2019-02-08 22:04:39 +00:00
|
|
|
self.send_header(key, value)
|
|
|
|
self.end_headers()
|
|
|
|
self.wfile.write(data.encode("utf8"))
|
|
|
|
|
2019-02-08 21:54:33 +00:00
|
|
|
def _handle(self, method):
|
2019-02-08 21:56:58 +00:00
|
|
|
path, endpoint, args = self._path_data()
|
2018-12-08 09:00:12 +00:00
|
|
|
headers = utils.CaseInsensitiveDict(dict(self.headers.items()))
|
2019-02-08 21:52:24 +00:00
|
|
|
params = self._url_params()
|
|
|
|
data = self._body()
|
2018-10-04 15:01:13 +00:00
|
|
|
|
2018-10-05 21:49:06 +00:00
|
|
|
response = ""
|
|
|
|
code = 404
|
2019-02-08 22:04:39 +00:00
|
|
|
content_type = "text/plain"
|
2018-10-04 16:59:24 +00:00
|
|
|
|
2018-10-05 21:49:06 +00:00
|
|
|
hooks = _events.on("api").on(method).on(endpoint).get_hooks()
|
|
|
|
if hooks:
|
|
|
|
hook = hooks[0]
|
|
|
|
authenticated = hook.get_kwarg("authenticated", True)
|
|
|
|
key = params.get("key", None)
|
2018-11-12 18:16:55 +00:00
|
|
|
key_setting = _bot.get_setting("api-key-%s" % key, {})
|
2018-11-12 18:18:07 +00:00
|
|
|
permissions = key_setting.get("permissions", [])
|
2018-11-10 21:54:08 +00:00
|
|
|
|
2019-01-23 22:08:26 +00:00
|
|
|
if key_setting:
|
2019-01-23 22:23:21 +00:00
|
|
|
_log.debug("[HTTP] %s from API key %s (%s)",
|
2019-01-23 22:10:32 +00:00
|
|
|
[method, key, key_setting["comment"]])
|
2019-01-23 22:08:26 +00:00
|
|
|
|
2018-11-11 08:53:37 +00:00
|
|
|
if not authenticated or path in permissions or "*" in permissions:
|
2018-10-05 21:49:06 +00:00
|
|
|
if path.startswith("/api/"):
|
2018-10-06 08:24:43 +00:00
|
|
|
event_response = None
|
2018-10-06 08:22:11 +00:00
|
|
|
try:
|
2018-11-27 14:25:12 +00:00
|
|
|
event_response = _bot.trigger(lambda:
|
|
|
|
_events.on("api").on(method).on(
|
2018-11-06 13:02:04 +00:00
|
|
|
endpoint).call_unsafe_for_result(params=params,
|
2018-12-06 12:00:45 +00:00
|
|
|
path=args, data=data, headers=headers))
|
2018-11-06 17:22:50 +00:00
|
|
|
except Exception as e:
|
|
|
|
_log.error("failed to call API endpoint \"%s\"",
|
|
|
|
[path], exc_info=True)
|
2018-10-06 08:22:11 +00:00
|
|
|
code = 500
|
2018-10-04 15:01:13 +00:00
|
|
|
|
2018-11-06 14:09:13 +00:00
|
|
|
if not event_response == None:
|
2019-02-08 22:04:39 +00:00
|
|
|
content_type = "application/json"
|
2018-11-12 18:15:08 +00:00
|
|
|
if _bot.get_setting("rest-api-minify", False):
|
2018-11-11 08:55:49 +00:00
|
|
|
response = json.dumps(event_response,
|
|
|
|
sort_keys=True, separators=(",", ":"))
|
2018-11-11 08:51:50 +00:00
|
|
|
else:
|
|
|
|
response = json.dumps(event_response,
|
|
|
|
sort_keys=True, indent=4)
|
2018-10-05 21:49:06 +00:00
|
|
|
code = 200
|
2018-11-10 21:54:08 +00:00
|
|
|
else:
|
|
|
|
code = 401
|
2018-10-04 15:01:13 +00:00
|
|
|
|
2019-02-08 22:04:39 +00:00
|
|
|
headers = {
|
|
|
|
"Content-type": content_type
|
|
|
|
}
|
|
|
|
|
|
|
|
self._respond(code, headers, response)
|
|
|
|
|
2018-10-04 15:01:13 +00:00
|
|
|
|
2018-10-05 21:49:06 +00:00
|
|
|
def do_GET(self):
|
2019-02-08 21:52:24 +00:00
|
|
|
self._handle("GET")
|
2018-10-05 21:49:06 +00:00
|
|
|
|
|
|
|
def do_POST(self):
|
2019-02-08 21:52:24 +00:00
|
|
|
self._handle("POST")
|
2018-10-05 21:49:06 +00:00
|
|
|
|
2018-11-14 23:01:22 +00:00
|
|
|
def log_message(self, format, *args):
|
2018-11-14 23:02:32 +00:00
|
|
|
_log.info("[HTTP] " + format, args)
|
2018-11-14 23:01:22 +00:00
|
|
|
|
2018-10-04 16:09:52 +00:00
|
|
|
@utils.export("botset", {"setting": "rest-api",
|
|
|
|
"help": "Enable/disable REST API",
|
|
|
|
"validate": utils.bool_or_none})
|
2018-11-12 18:15:08 +00:00
|
|
|
@utils.export("botset", {"setting": "rest-api-minify",
|
|
|
|
"help": "Enable/disable REST API minifying",
|
|
|
|
"validate": utils.bool_or_none})
|
2018-10-12 17:07:23 +00:00
|
|
|
class Module(ModuleManager.BaseModule):
|
|
|
|
def on_load(self):
|
2018-10-04 15:01:13 +00:00
|
|
|
global _bot
|
2018-10-12 17:07:23 +00:00
|
|
|
_bot = self.bot
|
2018-10-04 15:01:13 +00:00
|
|
|
|
|
|
|
global _events
|
2018-10-12 17:07:23 +00:00
|
|
|
_events = self.events
|
2018-10-04 15:01:13 +00:00
|
|
|
|
2018-11-06 17:22:50 +00:00
|
|
|
global _log
|
|
|
|
_log = self.log
|
|
|
|
|
2018-12-08 08:56:47 +00:00
|
|
|
self.httpd = None
|
2018-10-12 17:07:23 +00:00
|
|
|
if self.bot.get_setting("rest-api", False):
|
2018-10-04 15:01:13 +00:00
|
|
|
self.httpd = http.server.HTTPServer(("", 5000), Handler)
|
2019-02-10 12:38:53 +00:00
|
|
|
|
2019-02-10 13:19:30 +00:00
|
|
|
self.httpd.socket = utils.security.ssl_wrap(self.httpd.socket,
|
2019-02-10 12:38:53 +00:00
|
|
|
cert=self.bot.config["tls-api-certificate"],
|
|
|
|
key=self.bot.config["tls-api-key"],
|
2019-02-10 13:26:35 +00:00
|
|
|
server_side=True, verify=False)
|
2019-02-10 12:38:53 +00:00
|
|
|
|
2018-10-04 15:01:13 +00:00
|
|
|
self.thread = threading.Thread(target=self.httpd.serve_forever)
|
|
|
|
self.thread.daemon = True
|
|
|
|
self.thread.start()
|
|
|
|
|
|
|
|
def unload(self):
|
2018-12-08 08:56:47 +00:00
|
|
|
if self.httpd:
|
|
|
|
self.httpd.shutdown()
|
2018-10-04 16:09:35 +00:00
|
|
|
|
2019-02-01 12:02:02 +00:00
|
|
|
@utils.hook("received.command.apikey", private_only=True, min_args=1)
|
2018-10-04 16:09:35 +00:00
|
|
|
def api_key(self, event):
|
|
|
|
"""
|
|
|
|
:help: Generate a new API key
|
2018-11-12 20:44:46 +00:00
|
|
|
:usage: <comment> [endpoint [endpoint ...]]
|
2018-10-04 16:09:35 +00:00
|
|
|
:permission: api-key
|
|
|
|
:prefix: APIKey
|
|
|
|
"""
|
2018-11-12 17:59:40 +00:00
|
|
|
api_key = uuid.uuid4().hex
|
|
|
|
comment = event["args_split"][0]
|
|
|
|
self.bot.set_setting("api-key-%s" % api_key, {
|
|
|
|
"comment": comment,
|
2018-11-12 22:20:46 +00:00
|
|
|
"permissions": event["args_split"][1:]
|
2018-11-12 17:59:40 +00:00
|
|
|
})
|
|
|
|
event["stdout"].write("New API key ('%s'): %s" % (comment, api_key))
|