Also protect resources from blueprint plugins with the api key (unless the plugin specifies otherwise)

This commit is contained in:
Gina Häußge 2014-11-19 09:02:33 +01:00
parent fec51ebc8e
commit 254145da22
4 changed files with 101 additions and 77 deletions

View file

@ -176,6 +176,13 @@ class BlueprintPlugin(Plugin):
return None
def is_blueprint_protected(self):
"""
Whether the blueprint is supposed to be protected by API key (the default) or not.
"""
return True
class SettingsPlugin(Plugin):
def on_settings_load(self):

View file

@ -306,9 +306,20 @@ class Server():
app.register_blueprint(apps, url_prefix="/apps")
# also register any blueprints defined in BlueprintPlugins
octoprint.plugin.call_plugin(octoprint.plugin.types.BlueprintPlugin,
"get_blueprint",
callback=lambda name, _, blueprint: app.register_blueprint(blueprint, url_prefix="/plugin/{name}".format(name=name)))
blueprint_plugins = octoprint.plugin.plugin_manager().get_implementations(octoprint.plugin.BlueprintPlugin)
for name, plugin in blueprint_plugins.items():
blueprint = plugin.get_blueprint()
if blueprint is None:
continue
if plugin.is_blueprint_protected():
from octoprint.server.util import apiKeyRequestHandler, corsResponseHandler
blueprint.before_request(apiKeyRequestHandler)
blueprint.after_request(corsResponseHandler)
url_prefix = "/plugin/{name}".format(name=name)
app.register_blueprint(blueprint, url_prefix=url_prefix)
logger.debug("Registered API of plugin {name} under URL prefix {url_prefix}".format(name=name, url_prefix=url_prefix))
self._router = SockJSRouter(self._createSocketConnection, "/sockjs")

View file

@ -17,9 +17,9 @@ import octoprint.util as util
import octoprint.users
import octoprint.server
import octoprint.plugin
from octoprint.server import admin_permission, NO_CONTENT, UI_API_KEY, appSessionManager
from octoprint.server import admin_permission, NO_CONTENT
from octoprint.settings import settings as s, valid_boolean_trues
from octoprint.server.util import get_api_key, get_user_for_apikey
from octoprint.server.util import apiKeyRequestHandler, corsResponseHandler
from octoprint.server.util.flask import restricted_access
@ -40,78 +40,8 @@ from . import slicing as api_slicing
VERSION = "0.1"
def optionsAllowOrigin(request):
""" Always reply 200 on OPTIONS request """
resp = current_app.make_default_options_response()
# Allow the origin which made the XHR
resp.headers['Access-Control-Allow-Origin'] = request.headers['Origin']
# Allow the actual method
resp.headers['Access-Control-Allow-Methods'] = request.headers['Access-Control-Request-Method']
# Allow for 10 seconds
resp.headers['Access-Control-Max-Age'] = "10"
# 'preflight' request contains the non-standard headers the real request will have (like X-Api-Key)
customRequestHeaders = request.headers.get('Access-Control-Request-Headers', None)
if customRequestHeaders is not None:
# If present => allow them all
resp.headers['Access-Control-Allow-Headers'] = customRequestHeaders
return resp
@api.before_request
def beforeApiRequests():
"""
All requests in this blueprint need to be made supplying an API key. This may be the UI_API_KEY, in which case
the underlying request processing will directly take place, or it may be the global or a user specific case. In any
case it has to be present and must be valid, so anything other than the above three types will result in denying
the request.
"""
if request.method == 'OPTIONS' and s().getBoolean(["api", "allowCrossOrigin"]):
return optionsAllowOrigin(request)
apikey = get_api_key(request)
if apikey is None:
# no api key => 401
return make_response("No API key provided", 401)
if apikey == UI_API_KEY:
# ui api key => continue regular request processing
return
if not s().get(["api", "enabled"]):
# api disabled => 401
return make_response("API disabled", 401)
if apikey == s().get(["api", "key"]):
# global api key => continue regular request processing
return
if appSessionManager.validate(apikey):
# app session key => continue regular request processing
return
user = get_user_for_apikey(apikey)
if user is not None:
# user specific api key => continue regular request processing
return
# invalid api key => 401
return make_response("Invalid API key", 401)
@api.after_request
def afterApiRequests(resp):
# Allow crossdomain
allowCrossOrigin = s().getBoolean(["api", "allowCrossOrigin"])
if request.method != 'OPTIONS' and 'Origin' in request.headers and allowCrossOrigin:
resp.headers['Access-Control-Allow-Origin'] = request.headers['Origin']
return resp
api.before_request(apiKeyRequestHandler)
api.after_request(corsResponseHandler)
#~~ data from plugins

View file

@ -10,12 +10,88 @@ import octoprint.timelapse
import octoprint.server
from octoprint.users import ApiUser
import flask as _flask
from . import flask
from . import sockjs
from . import tornado
from . import watchdog
def apiKeyRequestHandler():
"""
All requests in this blueprint need to be made supplying an API key. This may be the UI_API_KEY, in which case
the underlying request processing will directly take place, or it may be the global or a user specific case. In any
case it has to be present and must be valid, so anything other than the above three types will result in denying
the request.
"""
import octoprint.server
if _flask.request.method == 'OPTIONS' and settings().getBoolean(["api", "allowCrossOrigin"]):
return optionsAllowOrigin(_flask.request)
apikey = get_api_key(_flask.request)
if apikey is None:
# no api key => 401
return _flask.make_response("No API key provided", 401)
if apikey == octoprint.server.UI_API_KEY:
# ui api key => continue regular request processing
return
if not settings().get(["api", "enabled"]):
# api disabled => 401
return _flask.make_response("API disabled", 401)
if apikey == settings().get(["api", "key"]):
# global api key => continue regular request processing
return
if octoprint.server.appSessionManager.validate(apikey):
# app session key => continue regular request processing
return
user = get_user_for_apikey(apikey)
if user is not None:
# user specific api key => continue regular request processing
return
# invalid api key => 401
return _flask.make_response("Invalid API key", 401)
def corsResponseHandler(resp):
# Allow crossdomain
allowCrossOrigin = settings().getBoolean(["api", "allowCrossOrigin"])
if _flask.request.method != 'OPTIONS' and 'Origin' in _flask.request.headers and allowCrossOrigin:
resp.headers['Access-Control-Allow-Origin'] = _flask.request.headers['Origin']
return resp
def optionsAllowOrigin(request):
""" Always reply 200 on OPTIONS request """
resp = _flask.current_app.make_default_options_response()
# Allow the origin which made the XHR
resp.headers['Access-Control-Allow-Origin'] = request.headers['Origin']
# Allow the actual method
resp.headers['Access-Control-Allow-Methods'] = request.headers['Access-Control-Request-Method']
# Allow for 10 seconds
resp.headers['Access-Control-Max-Age'] = "10"
# 'preflight' request contains the non-standard headers the real request will have (like X-Api-Key)
customRequestHeaders = request.headers.get('Access-Control-Request-Headers', None)
if customRequestHeaders is not None:
# If present => allow them all
resp.headers['Access-Control-Allow-Headers'] = customRequestHeaders
return resp
def get_user_for_apikey(apikey):
if settings().get(["api", "enabled"]) and apikey is not None:
if apikey == settings().get(["api", "key"]) or octoprint.server.appSessionManager.validate(apikey):