From 14991ac3b97637b3137922c5b29b671a1281da60 Mon Sep 17 00:00:00 2001 From: Arksine <9563098+Arksine@users.noreply.github.com> Date: Fri, 9 Apr 2021 08:45:40 -0400 Subject: [PATCH] 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 --- moonraker/app.py | 80 ++++++++++++++++++-- moonraker/{ => components}/authorization.py | 82 ++------------------- moonraker/moonraker.py | 2 +- moonraker/websockets.py | 11 ++- 4 files changed, 86 insertions(+), 89 deletions(-) rename moonraker/{ => components}/authorization.py (74%) diff --git a/moonraker/app.py b/moonraker/app.py index 45ff2e2..ac6ca59 100644 --- a/moonraker/app.py +++ b/moonraker/app.py @@ -18,8 +18,6 @@ from tornado.routing import Rule, PathMatches, AnyMatches from tornado.log import access_log from utils import ServerError 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.targets import FileTarget, ValueTarget @@ -89,7 +87,6 @@ class MoonrakerApp: # Set Up Websocket and Authorization Managers self.wsm = WebsocketManager(self.server) - self.auth = Authorization(config['authorization']) mimetypes.add_type('text/plain', '.log') mimetypes.add_type('text/plain', '.gcode') @@ -123,7 +120,6 @@ class MoonrakerApp: "moonraker.log", logfile, force=True) self.register_static_file_handler( "klippy.log", DEFAULT_KLIPPY_LOG_PATH, force=True) - self.auth.register_handlers(self) def listen(self, host, port): self.tornado_server = self.app.listen( @@ -148,9 +144,6 @@ class MoonrakerApp: def get_server(self): return self.server - def get_auth(self): - return self.auth - def get_websocket_manager(self): return self.wsm @@ -159,7 +152,6 @@ class MoonrakerApp: self.tornado_server.stop() await self.tornado_server.close_all_connections() await self.wsm.close() - self.auth.close() def register_remote_handler(self, endpoint): if endpoint in RESERVED_ENDPOINTS: @@ -265,6 +257,78 @@ class MoonrakerApp: self.api_cache[endpoint] = 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): def initialize(self, callback, methods, need_object_parser=False, is_remote=True, wrap_result=True): diff --git a/moonraker/authorization.py b/moonraker/components/authorization.py similarity index 74% rename from moonraker/authorization.py rename to moonraker/components/authorization.py index 41735b1..1566eb1 100644 --- a/moonraker/authorization.py +++ b/moonraker/components/authorization.py @@ -10,7 +10,6 @@ import time import ipaddress import re import logging -import tornado from tornado.ioloop import IOLoop, PeriodicCallback from utils import ServerError @@ -20,10 +19,10 @@ PRUNE_CHECK_TIME = 300 * 1000 class Authorization: def __init__(self, config): + self.server = config.get_server() api_key_file = config.get('api_key_file', "~/.moonraker_api_key") self.api_key_file = os.path.expanduser(api_key_file) self.api_key = self._read_api_key() - self.auth_enabled = config.getboolean('enabled', True) self.trusted_connections = {} self.access_tokens = {} @@ -70,7 +69,6 @@ class Authorization: logging.info( f"Authorization Configuration Loaded\n" - f"Auth Enabled: {self.auth_enabled}\n" f"Trusted Clients:\n{t_clients}\n" f"CORS Domains:\n{c_domains}") @@ -78,12 +76,11 @@ class Authorization: self._prune_conn_handler, PRUNE_CHECK_TIME) self.prune_handler.start() - def register_handlers(self, app): # Register Authorization Endpoints - app.register_local_handler( + self.server.register_endpoint( "/access/api_key", ['GET', 'POST'], self._handle_apikey_request, protocol=['http']) - app.register_local_handler( + self.server.register_endpoint( "/access/oneshot_token", ['GET'], self._handle_token_request, protocol=['http']) @@ -136,9 +133,6 @@ class Authorization: def _token_expire_handler(self, token): self.access_tokens.pop(token, None) - def is_enabled(self): - return self.auth_enabled - def get_access_token(self): token = base64.b32encode(os.urandom(20)).decode() ioloop = IOLoop.current() @@ -167,10 +161,6 @@ class Authorization: return False def check_authorized(self, request): - # Authorization is disabled, request may pass - if not self.auth_enabled: - return True - # Check if IP is trusted try: ip = ipaddress.ip_address(request.remote_ip) @@ -240,68 +230,6 @@ class Authorization: def close(self): 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): - origin = self.request.headers.get("Origin") - # 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() +def load_component(config): + return Authorization(config) diff --git a/moonraker/moonraker.py b/moonraker/moonraker.py index 3c131d7..4cb25a7 100755 --- a/moonraker/moonraker.py +++ b/moonraker/moonraker.py @@ -138,7 +138,7 @@ class Server: # check for optional components 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: self.load_component(config, section, None) diff --git a/moonraker/websockets.py b/moonraker/websockets.py index 5b3f626..0629399 100644 --- a/moonraker/websockets.py +++ b/moonraker/websockets.py @@ -259,7 +259,7 @@ class WebsocketManager: class WebSocket(WebSocketHandler): def initialize(self): app = self.settings['parent'] - self.auth = app.get_auth() + self.server = app.get_server() self.wsm = app.get_websocket_manager() self.rpc = self.wsm.rpc self.uid = id(self) @@ -303,9 +303,14 @@ class WebSocket(WebSocketHandler): def check_origin(self, 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 + # Check Authorized User 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")