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:
Arksine 2021-04-09 08:45:40 -04:00
parent 57836047f6
commit 14991ac3b9
4 changed files with 86 additions and 89 deletions

View File

@ -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):

View File

@ -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)

View File

@ -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)

View File

@ -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")