authorization: convert module to component
CONFIG CHANGE: This deprecates the "enabled" option in the [authorization] section. Authorization will be enabled if the section is included in moonraker.conf, otherwise it will be disabled. Signed-off-by: Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
parent
57836047f6
commit
14991ac3b9
|
@ -18,8 +18,6 @@ from tornado.routing import Rule, PathMatches, AnyMatches
|
||||||
from tornado.log import access_log
|
from tornado.log import access_log
|
||||||
from utils import ServerError
|
from utils import ServerError
|
||||||
from websockets import WebRequest, WebsocketManager, WebSocket
|
from websockets import WebRequest, WebsocketManager, WebSocket
|
||||||
from authorization import AuthorizedRequestHandler, AuthorizedFileHandler
|
|
||||||
from authorization import Authorization
|
|
||||||
from streaming_form_data import StreamingFormDataParser
|
from streaming_form_data import StreamingFormDataParser
|
||||||
from streaming_form_data.targets import FileTarget, ValueTarget
|
from streaming_form_data.targets import FileTarget, ValueTarget
|
||||||
|
|
||||||
|
@ -89,7 +87,6 @@ class MoonrakerApp:
|
||||||
|
|
||||||
# Set Up Websocket and Authorization Managers
|
# Set Up Websocket and Authorization Managers
|
||||||
self.wsm = WebsocketManager(self.server)
|
self.wsm = WebsocketManager(self.server)
|
||||||
self.auth = Authorization(config['authorization'])
|
|
||||||
|
|
||||||
mimetypes.add_type('text/plain', '.log')
|
mimetypes.add_type('text/plain', '.log')
|
||||||
mimetypes.add_type('text/plain', '.gcode')
|
mimetypes.add_type('text/plain', '.gcode')
|
||||||
|
@ -123,7 +120,6 @@ class MoonrakerApp:
|
||||||
"moonraker.log", logfile, force=True)
|
"moonraker.log", logfile, force=True)
|
||||||
self.register_static_file_handler(
|
self.register_static_file_handler(
|
||||||
"klippy.log", DEFAULT_KLIPPY_LOG_PATH, force=True)
|
"klippy.log", DEFAULT_KLIPPY_LOG_PATH, force=True)
|
||||||
self.auth.register_handlers(self)
|
|
||||||
|
|
||||||
def listen(self, host, port):
|
def listen(self, host, port):
|
||||||
self.tornado_server = self.app.listen(
|
self.tornado_server = self.app.listen(
|
||||||
|
@ -148,9 +144,6 @@ class MoonrakerApp:
|
||||||
def get_server(self):
|
def get_server(self):
|
||||||
return self.server
|
return self.server
|
||||||
|
|
||||||
def get_auth(self):
|
|
||||||
return self.auth
|
|
||||||
|
|
||||||
def get_websocket_manager(self):
|
def get_websocket_manager(self):
|
||||||
return self.wsm
|
return self.wsm
|
||||||
|
|
||||||
|
@ -159,7 +152,6 @@ class MoonrakerApp:
|
||||||
self.tornado_server.stop()
|
self.tornado_server.stop()
|
||||||
await self.tornado_server.close_all_connections()
|
await self.tornado_server.close_all_connections()
|
||||||
await self.wsm.close()
|
await self.wsm.close()
|
||||||
self.auth.close()
|
|
||||||
|
|
||||||
def register_remote_handler(self, endpoint):
|
def register_remote_handler(self, endpoint):
|
||||||
if endpoint in RESERVED_ENDPOINTS:
|
if endpoint in RESERVED_ENDPOINTS:
|
||||||
|
@ -265,6 +257,78 @@ class MoonrakerApp:
|
||||||
self.api_cache[endpoint] = api_def
|
self.api_cache[endpoint] = api_def
|
||||||
return api_def
|
return api_def
|
||||||
|
|
||||||
|
class AuthorizedRequestHandler(tornado.web.RequestHandler):
|
||||||
|
def initialize(self):
|
||||||
|
self.server = self.settings['parent'].get_server()
|
||||||
|
|
||||||
|
def set_default_headers(self):
|
||||||
|
origin = self.request.headers.get("Origin")
|
||||||
|
# it is necessary to look up the parent app here,
|
||||||
|
# as initialize() may not yet be called
|
||||||
|
server = self.settings['parent'].get_server()
|
||||||
|
auth = server.lookup_component('authorization', None)
|
||||||
|
self.cors_enabled = False
|
||||||
|
if auth is not None:
|
||||||
|
self.cors_enabled = auth.check_cors(origin, self)
|
||||||
|
|
||||||
|
def prepare(self):
|
||||||
|
auth = self.server.lookup_component('authorization', None)
|
||||||
|
if auth is not None and not auth.check_authorized(self.request):
|
||||||
|
raise tornado.web.HTTPError(401, "Unauthorized")
|
||||||
|
|
||||||
|
def options(self, *args, **kwargs):
|
||||||
|
# Enable CORS if configured
|
||||||
|
if self.cors_enabled:
|
||||||
|
self.set_status(204)
|
||||||
|
self.finish()
|
||||||
|
else:
|
||||||
|
super(AuthorizedRequestHandler, self).options()
|
||||||
|
|
||||||
|
def get_associated_websocket(self):
|
||||||
|
# Return associated websocket connection if an id
|
||||||
|
# was provided by the request
|
||||||
|
conn = None
|
||||||
|
conn_id = self.get_argument('connection_id', None)
|
||||||
|
if conn_id is not None:
|
||||||
|
try:
|
||||||
|
conn_id = int(conn_id)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
wsm = self.settings['parent'].get_websocket_manager()
|
||||||
|
conn = wsm.get_websocket(conn_id)
|
||||||
|
return conn
|
||||||
|
|
||||||
|
# Due to the way Python treats multiple inheritance its best
|
||||||
|
# to create a separate authorized handler for serving files
|
||||||
|
class AuthorizedFileHandler(tornado.web.StaticFileHandler):
|
||||||
|
def initialize(self, path, default_filename=None):
|
||||||
|
super(AuthorizedFileHandler, self).initialize(path, default_filename)
|
||||||
|
self.server = self.settings['parent'].get_server()
|
||||||
|
|
||||||
|
def set_default_headers(self):
|
||||||
|
origin = self.request.headers.get("Origin")
|
||||||
|
# it is necessary to look up the parent app here,
|
||||||
|
# as initialize() may not yet be called
|
||||||
|
server = self.settings['parent'].get_server()
|
||||||
|
auth = server.lookup_component('authorization', None)
|
||||||
|
self.cors_enabled = False
|
||||||
|
if auth is not None:
|
||||||
|
self.cors_enabled = auth.check_cors(origin, self)
|
||||||
|
|
||||||
|
def prepare(self):
|
||||||
|
auth = self.server.lookup_component('authorization', None)
|
||||||
|
if auth is not None and not auth.check_authorized(self.request):
|
||||||
|
raise tornado.web.HTTPError(401, "Unauthorized")
|
||||||
|
|
||||||
|
def options(self, *args, **kwargs):
|
||||||
|
# Enable CORS if configured
|
||||||
|
if self.cors_enabled:
|
||||||
|
self.set_status(204)
|
||||||
|
self.finish()
|
||||||
|
else:
|
||||||
|
super(AuthorizedFileHandler, self).options()
|
||||||
|
|
||||||
class DynamicRequestHandler(AuthorizedRequestHandler):
|
class DynamicRequestHandler(AuthorizedRequestHandler):
|
||||||
def initialize(self, callback, methods, need_object_parser=False,
|
def initialize(self, callback, methods, need_object_parser=False,
|
||||||
is_remote=True, wrap_result=True):
|
is_remote=True, wrap_result=True):
|
||||||
|
|
|
@ -10,7 +10,6 @@ import time
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import re
|
import re
|
||||||
import logging
|
import logging
|
||||||
import tornado
|
|
||||||
from tornado.ioloop import IOLoop, PeriodicCallback
|
from tornado.ioloop import IOLoop, PeriodicCallback
|
||||||
from utils import ServerError
|
from utils import ServerError
|
||||||
|
|
||||||
|
@ -20,10 +19,10 @@ PRUNE_CHECK_TIME = 300 * 1000
|
||||||
|
|
||||||
class Authorization:
|
class Authorization:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
|
self.server = config.get_server()
|
||||||
api_key_file = config.get('api_key_file', "~/.moonraker_api_key")
|
api_key_file = config.get('api_key_file', "~/.moonraker_api_key")
|
||||||
self.api_key_file = os.path.expanduser(api_key_file)
|
self.api_key_file = os.path.expanduser(api_key_file)
|
||||||
self.api_key = self._read_api_key()
|
self.api_key = self._read_api_key()
|
||||||
self.auth_enabled = config.getboolean('enabled', True)
|
|
||||||
self.trusted_connections = {}
|
self.trusted_connections = {}
|
||||||
self.access_tokens = {}
|
self.access_tokens = {}
|
||||||
|
|
||||||
|
@ -70,7 +69,6 @@ class Authorization:
|
||||||
|
|
||||||
logging.info(
|
logging.info(
|
||||||
f"Authorization Configuration Loaded\n"
|
f"Authorization Configuration Loaded\n"
|
||||||
f"Auth Enabled: {self.auth_enabled}\n"
|
|
||||||
f"Trusted Clients:\n{t_clients}\n"
|
f"Trusted Clients:\n{t_clients}\n"
|
||||||
f"CORS Domains:\n{c_domains}")
|
f"CORS Domains:\n{c_domains}")
|
||||||
|
|
||||||
|
@ -78,12 +76,11 @@ class Authorization:
|
||||||
self._prune_conn_handler, PRUNE_CHECK_TIME)
|
self._prune_conn_handler, PRUNE_CHECK_TIME)
|
||||||
self.prune_handler.start()
|
self.prune_handler.start()
|
||||||
|
|
||||||
def register_handlers(self, app):
|
|
||||||
# Register Authorization Endpoints
|
# Register Authorization Endpoints
|
||||||
app.register_local_handler(
|
self.server.register_endpoint(
|
||||||
"/access/api_key", ['GET', 'POST'],
|
"/access/api_key", ['GET', 'POST'],
|
||||||
self._handle_apikey_request, protocol=['http'])
|
self._handle_apikey_request, protocol=['http'])
|
||||||
app.register_local_handler(
|
self.server.register_endpoint(
|
||||||
"/access/oneshot_token", ['GET'],
|
"/access/oneshot_token", ['GET'],
|
||||||
self._handle_token_request, protocol=['http'])
|
self._handle_token_request, protocol=['http'])
|
||||||
|
|
||||||
|
@ -136,9 +133,6 @@ class Authorization:
|
||||||
def _token_expire_handler(self, token):
|
def _token_expire_handler(self, token):
|
||||||
self.access_tokens.pop(token, None)
|
self.access_tokens.pop(token, None)
|
||||||
|
|
||||||
def is_enabled(self):
|
|
||||||
return self.auth_enabled
|
|
||||||
|
|
||||||
def get_access_token(self):
|
def get_access_token(self):
|
||||||
token = base64.b32encode(os.urandom(20)).decode()
|
token = base64.b32encode(os.urandom(20)).decode()
|
||||||
ioloop = IOLoop.current()
|
ioloop = IOLoop.current()
|
||||||
|
@ -167,10 +161,6 @@ class Authorization:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_authorized(self, request):
|
def check_authorized(self, request):
|
||||||
# Authorization is disabled, request may pass
|
|
||||||
if not self.auth_enabled:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Check if IP is trusted
|
# Check if IP is trusted
|
||||||
try:
|
try:
|
||||||
ip = ipaddress.ip_address(request.remote_ip)
|
ip = ipaddress.ip_address(request.remote_ip)
|
||||||
|
@ -240,68 +230,6 @@ class Authorization:
|
||||||
def close(self):
|
def close(self):
|
||||||
self.prune_handler.stop()
|
self.prune_handler.stop()
|
||||||
|
|
||||||
class AuthorizedRequestHandler(tornado.web.RequestHandler):
|
|
||||||
def initialize(self):
|
|
||||||
app = self.settings['parent']
|
|
||||||
self.server = app.get_server()
|
|
||||||
self.auth = app.get_auth()
|
|
||||||
self.wsm = app.get_websocket_manager()
|
|
||||||
|
|
||||||
def set_default_headers(self):
|
def load_component(config):
|
||||||
origin = self.request.headers.get("Origin")
|
return Authorization(config)
|
||||||
# it is necessary to look up the parent app here,
|
|
||||||
# as initialize() may not yet be called
|
|
||||||
auth = self.settings['parent'].get_auth()
|
|
||||||
self.cors_enabled = auth.check_cors(origin, self)
|
|
||||||
|
|
||||||
def prepare(self):
|
|
||||||
if not self.auth.check_authorized(self.request):
|
|
||||||
raise tornado.web.HTTPError(401, "Unauthorized")
|
|
||||||
|
|
||||||
def options(self, *args, **kwargs):
|
|
||||||
# Enable CORS if configured
|
|
||||||
if self.cors_enabled:
|
|
||||||
self.set_status(204)
|
|
||||||
self.finish()
|
|
||||||
else:
|
|
||||||
super(AuthorizedRequestHandler, self).options()
|
|
||||||
|
|
||||||
def get_associated_websocket(self):
|
|
||||||
# Return associated websocket connection if an id
|
|
||||||
# was provided by the request
|
|
||||||
conn = None
|
|
||||||
conn_id = self.get_argument('connection_id', None)
|
|
||||||
if conn_id is not None:
|
|
||||||
try:
|
|
||||||
conn_id = int(conn_id)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
conn = self.wsm.get_websocket(conn_id)
|
|
||||||
return conn
|
|
||||||
|
|
||||||
# Due to the way Python treats multiple inheritance its best
|
|
||||||
# to create a separate authorized handler for serving files
|
|
||||||
class AuthorizedFileHandler(tornado.web.StaticFileHandler):
|
|
||||||
def initialize(self, path, default_filename=None):
|
|
||||||
super(AuthorizedFileHandler, self).initialize(path, default_filename)
|
|
||||||
app = self.settings['parent']
|
|
||||||
self.server = app.get_server()
|
|
||||||
self.auth = app.get_auth()
|
|
||||||
|
|
||||||
def set_default_headers(self):
|
|
||||||
origin = self.request.headers.get("Origin")
|
|
||||||
auth = self.settings['parent'].get_auth()
|
|
||||||
self.cors_enabled = auth.check_cors(origin, self)
|
|
||||||
|
|
||||||
def prepare(self):
|
|
||||||
if not self.auth.check_authorized(self.request):
|
|
||||||
raise tornado.web.HTTPError(401, "Unauthorized")
|
|
||||||
|
|
||||||
def options(self, *args, **kwargs):
|
|
||||||
# Enable CORS if configured
|
|
||||||
if self.cors_enabled:
|
|
||||||
self.set_status(204)
|
|
||||||
self.finish()
|
|
||||||
else:
|
|
||||||
super(AuthorizedFileHandler, self).options()
|
|
|
@ -138,7 +138,7 @@ class Server:
|
||||||
|
|
||||||
# check for optional components
|
# check for optional components
|
||||||
opt_sections = set([s.split()[0] for s in config.sections()]) - \
|
opt_sections = set([s.split()[0] for s in config.sections()]) - \
|
||||||
set(['server', 'authorization', 'system_args'])
|
set(['server', 'system_args'])
|
||||||
for section in opt_sections:
|
for section in opt_sections:
|
||||||
self.load_component(config, section, None)
|
self.load_component(config, section, None)
|
||||||
|
|
||||||
|
|
|
@ -259,7 +259,7 @@ class WebsocketManager:
|
||||||
class WebSocket(WebSocketHandler):
|
class WebSocket(WebSocketHandler):
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
app = self.settings['parent']
|
app = self.settings['parent']
|
||||||
self.auth = app.get_auth()
|
self.server = app.get_server()
|
||||||
self.wsm = app.get_websocket_manager()
|
self.wsm = app.get_websocket_manager()
|
||||||
self.rpc = self.wsm.rpc
|
self.rpc = self.wsm.rpc
|
||||||
self.uid = id(self)
|
self.uid = id(self)
|
||||||
|
@ -303,9 +303,14 @@ class WebSocket(WebSocketHandler):
|
||||||
|
|
||||||
def check_origin(self, origin):
|
def check_origin(self, origin):
|
||||||
if not super(WebSocket, self).check_origin(origin):
|
if not super(WebSocket, self).check_origin(origin):
|
||||||
return self.auth.check_cors(origin)
|
auth = self.server.lookup_component('authorization', None)
|
||||||
|
if auth is not None:
|
||||||
|
return auth.check_cors(origin)
|
||||||
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Check Authorized User
|
# Check Authorized User
|
||||||
def prepare(self):
|
def prepare(self):
|
||||||
if not self.auth.check_authorized(self.request):
|
auth = self.server.lookup_component('authorization', None)
|
||||||
|
if auth is not None and not auth.check_authorized(self.request):
|
||||||
raise tornado.web.HTTPError(401, "Unauthorized")
|
raise tornado.web.HTTPError(401, "Unauthorized")
|
||||||
|
|
Loading…
Reference in New Issue