diff --git a/docs/api/settings.rst b/docs/api/settings.rst index 345a4413..acc79722 100644 --- a/docs/api/settings.rst +++ b/docs/api/settings.rst @@ -68,6 +68,23 @@ Save settings // ... } +.. _sec-api-settings-generateapikey: + +Regenerate the system wide API key +================================== + +.. http:post:: /api/settings/apikey + + Generates a new system wide API key. + + Does not expect a body. Will return the generated API key as ``apikey`` + property in the JSON object contained in the response body. + + Requires admin rights. + + :status 200: No error + :status 403: No admin rights + .. _sec-api-settings-datamodel: Data model @@ -87,6 +104,9 @@ mapped from the same fields in ``config.yaml`` unless otherwise noted: - * - ``api.key`` - Only maps to ``api.key`` in ``config.yaml`` if request is sent with admin rights, set to ``n/a`` otherwise. + Starting with OctoPrint 1.3.3 setting this field via :ref:`the API ` is not possible, + only :ref:`regenerting it ` is supported. Setting a custom value is only + possible through `config.yaml`. * - ``api.allowCrossOrigin`` - * - ``appearance.name`` diff --git a/docs/jsclientlib/settings.rst b/docs/jsclientlib/settings.rst index ad804b8b..a8894718 100644 --- a/docs/jsclientlib/settings.rst +++ b/docs/jsclientlib/settings.rst @@ -35,6 +35,13 @@ :param object opts: Additional options for the request :returns Promise: A `jQuery Promise `_ for the request's response +.. js:function:: OctoPrintClient.settings.generateApiKey(opts) + + Generate a new system wide API key. + + :param object opts: Additional options for the request + :returns Promise: A `jQuery Promise `_ for the request's response + .. seealso:: :ref:`Settings API ` diff --git a/src/octoprint/server/api/settings.py b/src/octoprint/server/api/settings.py index 0276eb01..c03315a6 100644 --- a/src/octoprint/server/api/settings.py +++ b/src/octoprint/server/api/settings.py @@ -15,7 +15,7 @@ from octoprint.events import eventManager, Events from octoprint.settings import settings, valid_boolean_trues from octoprint.server import admin_permission, printer -from octoprint.server.api import api +from octoprint.server.api import api, NO_CONTENT from octoprint.server.util.flask import restricted_access, with_revalidation_checking import octoprint.plugin @@ -83,7 +83,7 @@ def getSettings(): data = { "api": { "enabled": s.getBoolean(["api", "enabled"]), - "key": s.get(["api", "key"]) if admin_permission.can() else "n/a", + "key": s.get(["api", "key"]) if admin_permission.can() else None, "allowCrossOrigin": s.get(["api", "allowCrossOrigin"]) }, "appearance": { @@ -258,6 +258,23 @@ def setSettings(): _saveSettings(data) return getSettings() + +@api.route("/settings/apikey", methods=["POST"]) +@restricted_access +@admin_permission.require(403) +def generateApiKey(): + apikey = settings().generateApiKey() + return jsonify(apikey=apikey) + + +@api.route("/settings/apikey", methods=["DELETE"]) +@restricted_access +@admin_permission.require(403) +def deleteApiKey(): + settings().deleteApiKey() + return NO_CONTENT + + def _saveSettings(data): logger = logging.getLogger(__name__) @@ -268,7 +285,6 @@ def _saveSettings(data): if "api" in data.keys(): if "enabled" in data["api"].keys(): s.setBoolean(["api", "enabled"], data["api"]["enabled"]) - if "key" in data["api"].keys(): s.set(["api", "key"], data["api"]["key"], True) if "allowCrossOrigin" in data["api"].keys(): s.setBoolean(["api", "allowCrossOrigin"], data["api"]["allowCrossOrigin"]) if "appearance" in data.keys(): diff --git a/src/octoprint/settings.py b/src/octoprint/settings.py index 57c3f49d..7ea1e7a0 100644 --- a/src/octoprint/settings.py +++ b/src/octoprint/settings.py @@ -557,8 +557,7 @@ class Settings(object): self.load(migrate=True) if self.get(["api", "key"]) is None: - self.set(["api", "key"], ''.join('%02X' % z for z in bytes(uuid.uuid4().bytes))) - self.save(force=True) + self.generateApiKey() self._script_env = self._init_script_templating() @@ -1497,6 +1496,17 @@ class Settings(object): with atomic_write(filename, "wb", max_permissions=0o666) as f: f.write(script) + def generateApiKey(self): + apikey = ''.join('%02X' % z for z in bytes(uuid.uuid4().bytes)) + self.set(["api", "key"], apikey) + self.save(force=True) + return apikey + + def deleteApiKey(self): + self.set(["api", "key"], None) + self.save(force=True) + + def _default_basedir(applicationName): # taken from http://stackoverflow.com/questions/1084697/how-do-i-store-desktop-application-data-in-a-cross-platform-way-for-python if sys.platform == "darwin": diff --git a/src/octoprint/static/js/app/client/settings.js b/src/octoprint/static/js/app/client/settings.js index 81157bc0..b1bd6e62 100644 --- a/src/octoprint/static/js/app/client/settings.js +++ b/src/octoprint/static/js/app/client/settings.js @@ -6,6 +6,7 @@ } })(this, function(OctoPrintClient, $) { var url = "api/settings"; + var apiKeyUrl = url + "/apikey"; var OctoPrintSettingsClient = function(base) { this.base = base; @@ -40,6 +41,10 @@ return this.save(data, opts); }; + OctoPrintSettingsClient.prototype.generateApiKey = function (opts) { + return this.base.postJson(apiKeyUrl, opts); + }; + OctoPrintClient.registerComponent("settings", OctoPrintSettingsClient); return OctoPrintSettingsClient; }); diff --git a/src/octoprint/static/js/app/viewmodels/settings.js b/src/octoprint/static/js/app/viewmodels/settings.js index 1125a2ff..785abf97 100644 --- a/src/octoprint/static/js/app/viewmodels/settings.js +++ b/src/octoprint/static/js/app/viewmodels/settings.js @@ -415,6 +415,19 @@ $(function() { self.settingsDialog.modal("hide"); }; + self.generateApiKey = function() { + if (!CONFIG_ACCESS_CONTROL) return; + + showConfirmationDialog(gettext("This will generate a new API Key. The old API Key will cease to function immediately."), + function() { + OctoPrint.settings.generateApiKey() + .done(function(response) { + self.api_key(response.apikey); + self.requestData(); + }); + }); + }; + self.showTranslationManager = function() { self.translationManagerDialog.modal(); return false; diff --git a/src/octoprint/static/js/app/viewmodels/usersettings.js b/src/octoprint/static/js/app/viewmodels/usersettings.js index b07e92f9..27bfbae9 100644 --- a/src/octoprint/static/js/app/viewmodels/usersettings.js +++ b/src/octoprint/static/js/app/viewmodels/usersettings.js @@ -71,16 +71,32 @@ $(function() { self.generateApikey = function() { if (!CONFIG_ACCESS_CONTROL) return; - self.users.generateApikey(self.currentUser().name, function(response) { - self.access_apikey(response.apikey); - }); + + var generate = function() { + self.users.generateApikey(self.currentUser().name) + .done(function(response) { + self.access_apikey(response.apikey); + }); + }; + + if (self.access_apikey()) { + showConfirmationDialog(gettext("This will generate a new API Key. The old API Key will cease to function immediately."), + generate); + } else { + generate(); + } }; self.deleteApikey = function() { if (!CONFIG_ACCESS_CONTROL) return; - self.users.deleteApikey(self.currentUser().name, function() { - self.access_apikey(undefined); - }); + if (!self.access_apikey()) return; + + showConfirmationDialog(gettext("This will delete the API Key. It will cease to to function immediately."), function() { + self.users.deleteApikey(self.currentUser().name) + .done(function() { + self.access_apikey(undefined); + }); + }) }; self.updateSettings = function(username, settings) { diff --git a/src/octoprint/templates/dialogs/settings/api.jinja2 b/src/octoprint/templates/dialogs/settings/api.jinja2 index a2c99b91..8237476a 100644 --- a/src/octoprint/templates/dialogs/settings/api.jinja2 +++ b/src/octoprint/templates/dialogs/settings/api.jinja2 @@ -16,10 +16,14 @@
- +
+ + +
+ {{ _('Please note that changes to the API key are applied immediately, without having to "Save" first.') }}
-
+
diff --git a/src/octoprint/templates/dialogs/usersettings/access.jinja2 b/src/octoprint/templates/dialogs/usersettings/access.jinja2 index 6cb7e2f9..67dee1f9 100644 --- a/src/octoprint/templates/dialogs/usersettings/access.jinja2 +++ b/src/octoprint/templates/dialogs/usersettings/access.jinja2 @@ -24,14 +24,14 @@
- - - + + +
- + {{ _('Please note that changes to the API key are applied immediately, without having to "Confirm" first.') }}
-
+