From ec470ebb4aba416cdb0887a66e994438703cf1df Mon Sep 17 00:00:00 2001 From: Daniel Perna Date: Thu, 28 Jun 2018 22:16:46 +0200 Subject: [PATCH 01/33] Bumped version --- changelog.txt | 2 ++ configurator.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 23f070d..afac74c 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,5 @@ +Version 0.3.0 (2018-) + Version 0.2.9 (2018-06-22) - Material Icons and HASS-help now open in new tab instead of modal (Issues #85 and #34) @danielperna84 - Open file by URL (Issue #95) @danielperna84 diff --git a/configurator.py b/configurator.py index 689b6fa..7fe0dfe 100755 --- a/configurator.py +++ b/configurator.py @@ -77,7 +77,7 @@ logging.Formatter('%(levelname)s:%(asctime)s:%(name)s:%(message)s')) LOG.addHandler(SO) RELEASEURL = "https://api.github.com/repos/danielperna84/hass-configurator/releases/latest" -VERSION = "0.2.9" +VERSION = "0.3.0" BASEDIR = "." DEV = False HTTPD = None From c24654418b2c97234cb88abb7d5c192d9785aa99 Mon Sep 17 00:00:00 2001 From: Daniel Perna Date: Thu, 28 Jun 2018 23:38:24 +0200 Subject: [PATCH 02/33] Allow passing settings via environment variables (Issue #100) --- README.md | 5 ++- changelog.txt | 1 + configurator.py | 84 +++++++++++++++++++++++++++++++------------------ settings.conf | 3 +- 4 files changed, 60 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index c791bb3..dd6229d 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,8 @@ There are no dependencies on Python modules that are not part of the standard li ### Configuration Near the top of the py-file you will find some global variables you can change to customize the configurator a little bit. If you are unfamiliar with Python: when setting variables of the type _string_, you have to write that within quotation marks. The default settings are fine for just checking this out quickly. With more customized setups you will have to change some settings though. -To keep your setting across updates it is also possible to save settings in an external file. In that case copy [settings.conf](https://github.com/danielperna84/hass-configurator/blob/master/settings.conf) whereever you like and append the full path to the file to the command when starting the configurator. E.g. `sudo .configurator.py /home/homeassistant/.homeassistant/mysettings.conf`. This file is in JSON format. So make sure it has a valid syntax (you can set the editor to JSON to get syntax highlighting for the settings). The major difference to the settings in the py-file is, that `None` becomes `null`. +To keep your setting across updates it is also possible to save settings in an external file. In that case copy [settings.conf](https://github.com/danielperna84/hass-configurator/blob/master/settings.conf) whereever you like and append the full path to the file to the command when starting the configurator. E.g. `sudo .configurator.py /home/homeassistant/.homeassistant/mysettings.conf`. This file is in JSON format. So make sure it has a valid syntax (you can set the editor to JSON to get syntax highlighting for the settings). The major difference to the settings in the py-file is, that `None` becomes `null`. +Another way of passing settings (except those defined as lists like e.g. `ALLOWED_NETWORKS`) is by using [environment variables](https://en.wikipedia.org/wiki/Environment_variable). All settings passed via environment variables will overwrite the settings you have set in the `settings.conf` file. This allows you to provide settings in you systemd service file or the way it is usually done with Docker. The names of the environment variables have to be named exactly like the regular ones, prepended with the prefix `HC_`. You can customize this prefix in the `settings.conf` by setting `ENV_PREFIX` to something you like. `ENV_PREFIX` can not be set via environment variable. #### LISTENIP (string) The IP address the service is listening on. By default it is binding to `0.0.0.0`, which is every IPv4 interface on the system. When using `::`, all available IPv6- and IPv4-addresses will be used. @@ -72,6 +73,8 @@ If set to `true`, directories will be displayed at the top. If set to _somesecretkeynobodycanguess_, you can browse to `https://your.configurator:3218/somesecretkeynobodycanguess` from any IP, and it will be removed from the `BANNED_IPS` list (in case it has been banned before) and added to the `ALLOWED_NETWORKS` list. Once the request has been processed you will automatically be redirected to the configurator. Think of this as dynamically allowing access from untrusted IPs by providing a secret key (_open sesame!_). Keep in mind, that once the IP has been added, you will either have to restart the configurator or manually remove the IP through the _Network status_ to revoke access. #### VERIFY_HOSTNAME (string) HTTP requests include the hostname to which the request has been made. To improve security you can set this parameter to `yourdomain.example.com`. This will check if the hostname within the request matches the one you are expecting. If it does not match, a `403 Forbidden` response will be sent. As a result attackers that scan your IP address won't be able to connect unless they know the correct hostname. Be careful with this option though, because it prohibits you from accessing the configurator directly via IP. +#### ENV_PREFIX (string) +To modify the default prefix for settings passed as environment variables (`HC_`) change this setting to another value that meets your demands. __Note regarding `ALLOWED_NETWORKS`, `BANNED_IPS` and `BANLIMIT`__: The way this is implemented works in the following order: diff --git a/changelog.txt b/changelog.txt index afac74c..16bcc77 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +1,5 @@ Version 0.3.0 (2018-) +- Allow passing settings (except lists) via environment variables (Issue #100) @danielperna84 Version 0.2.9 (2018-06-22) - Material Icons and HASS-help now open in new tab instead of modal (Issues #85 and #34) @danielperna84 diff --git a/configurator.py b/configurator.py index 7fe0dfe..2503b02 100755 --- a/configurator.py +++ b/configurator.py @@ -66,6 +66,8 @@ # Verify the hostname used in the request. Block access if it doesn't match # this value VERIFY_HOSTNAME = None +# Prefix for environment variables +ENV_PREFIX = "HC_" ### End of options LOGLEVEL = logging.INFO @@ -3352,37 +3354,55 @@ def signal_handler(sig, frame): def load_settings(settingsfile): global LISTENIP, LISTENPORT, BASEPATH, SSL_CERTIFICATE, SSL_KEY, HASS_API, \ HASS_API_PASSWORD, CREDENTIALS, ALLOWED_NETWORKS, BANNED_IPS, BANLIMIT, \ - DEV, IGNORE_PATTERN, DIRSFIRST, SESAME, VERIFY_HOSTNAME, ENFORCE_BASEPATH - try: - if os.path.isfile(settingsfile): - with open(settingsfile) as fptr: - settings = json.loads(fptr.read()) - LISTENIP = settings.get("LISTENIP", LISTENIP) - LISTENPORT = settings.get("LISTENPORT", LISTENPORT) - BASEPATH = settings.get("BASEPATH", BASEPATH) - ENFORCE_BASEPATH = settings.get("ENFORCE_BASEPATH", ENFORCE_BASEPATH) - SSL_CERTIFICATE = settings.get("SSL_CERTIFICATE", SSL_CERTIFICATE) - SSL_KEY = settings.get("SSL_KEY", SSL_KEY) - HASS_API = settings.get("HASS_API", HASS_API) - HASS_API_PASSWORD = settings.get("HASS_API_PASSWORD", HASS_API_PASSWORD) - CREDENTIALS = settings.get("CREDENTIALS", CREDENTIALS) - ALLOWED_NETWORKS = settings.get("ALLOWED_NETWORKS", ALLOWED_NETWORKS) - BANNED_IPS = settings.get("BANNED_IPS", BANNED_IPS) - BANLIMIT = settings.get("BANLIMIT", BANLIMIT) - DEV = settings.get("DEV", DEV) - IGNORE_PATTERN = settings.get("IGNORE_PATTERN", IGNORE_PATTERN) - DIRSFIRST = settings.get("DIRSFIRST", DIRSFIRST) - SESAME = settings.get("SESAME", SESAME) - VERIFY_HOSTNAME = settings.get("VERIFY_HOSTNAME", VERIFY_HOSTNAME) - except Exception as err: - LOG.warning(err) - LOG.warning("Not loading static settings") - return False + DEV, IGNORE_PATTERN, DIRSFIRST, SESAME, VERIFY_HOSTNAME, ENFORCE_BASEPATH, \ + ENV_PREFIX + settings = {} + if settingsfile: + try: + if os.path.isfile(settingsfile): + with open(settingsfile) as fptr: + settings = json.loads(fptr.read()) + except Exception as err: + LOG.warning(err) + LOG.warning("Not loading settings from file") + ENV_PREFIX = settings.get('ENV_PREFIX', ENV_PREFIX) + for key, value in os.environ.items(): + if key.startswith(ENV_PREFIX): + # Convert booleans + if value in ['true', 'false', 'True', 'False']: + value = True if value in ['true', 'True'] else False + # Convert None / null + elif value in ['none', 'None' 'null']: + value = None + # Convert plain numbers + elif value.isnumeric(): + value = int(value) + settings[key[len(ENV_PREFIX):]] = value + LISTENIP = settings.get("LISTENIP", LISTENIP) + LISTENPORT = settings.get("LISTENPORT", LISTENPORT) + BASEPATH = settings.get("BASEPATH", BASEPATH) + ENFORCE_BASEPATH = settings.get("ENFORCE_BASEPATH", ENFORCE_BASEPATH) + SSL_CERTIFICATE = settings.get("SSL_CERTIFICATE", SSL_CERTIFICATE) + SSL_KEY = settings.get("SSL_KEY", SSL_KEY) + HASS_API = settings.get("HASS_API", HASS_API) + HASS_API_PASSWORD = settings.get("HASS_API_PASSWORD", HASS_API_PASSWORD) + CREDENTIALS = settings.get("CREDENTIALS", CREDENTIALS) + ALLOWED_NETWORKS = settings.get("ALLOWED_NETWORKS", ALLOWED_NETWORKS) + BANNED_IPS = settings.get("BANNED_IPS", BANNED_IPS) + BANLIMIT = settings.get("BANLIMIT", BANLIMIT) + DEV = settings.get("DEV", DEV) + IGNORE_PATTERN = settings.get("IGNORE_PATTERN", IGNORE_PATTERN) + DIRSFIRST = settings.get("DIRSFIRST", DIRSFIRST) + SESAME = settings.get("SESAME", SESAME) + VERIFY_HOSTNAME = settings.get("VERIFY_HOSTNAME", VERIFY_HOSTNAME) + def is_safe_path(basedir, path, follow_symlinks=True): + if basedir is None: + return True if follow_symlinks: - return os.path.realpath(path).startswith(basedir) - return os.path.abspath(path).startswith(basedir) + return os.path.realpath(path).startswith(basedir.encode('utf-8')) + return os.path.abspath(path).startswith(basedir.encode('utf-8')) def get_dircontent(path, repo=None): dircontent = [] @@ -3520,7 +3540,7 @@ def do_GET(self): try: if filename: filename = unquote(filename[0]).encode('utf-8') - if ENFORCE_BASEPATH and not is_safe_path(BASEPATH.encode('utf-8'), filename): + if ENFORCE_BASEPATH and not is_safe_path(BASEPATH, filename): raise OSError('Access denied.') if os.path.isfile(os.path.join(BASEDIR.encode('utf-8'), filename)): with open(os.path.join(BASEDIR.encode('utf-8'), filename)) as fptr: @@ -3538,7 +3558,7 @@ def do_GET(self): try: if filename: filename = unquote(filename[0]).encode('utf-8') - if ENFORCE_BASEPATH and not is_safe_path(BASEPATH.encode('utf-8'), filename): + if ENFORCE_BASEPATH and not is_safe_path(BASEPATH, filename): raise OSError('Access denied.') LOG.info(filename) if os.path.isfile(os.path.join(BASEDIR.encode('utf-8'), filename)): @@ -3565,7 +3585,7 @@ def do_GET(self): if dirpath: dirpath = unquote(dirpath[0]).encode('utf-8') if os.path.isdir(dirpath): - if ENFORCE_BASEPATH and not is_safe_path(BASEPATH.encode('utf-8'), dirpath): + if ENFORCE_BASEPATH and not is_safe_path(BASEPATH, dirpath): raise OSError('Access denied.') repo = None activebranch = None @@ -4396,6 +4416,8 @@ def main(args): global HTTPD, CREDENTIALS if args: load_settings(args[0]) + else: + load_settings(None) LOG.info("Starting server") CustomServer = SimpleServer if ':' in LISTENIP: diff --git a/settings.conf b/settings.conf index 2896540..f06c747 100644 --- a/settings.conf +++ b/settings.conf @@ -14,5 +14,6 @@ "IGNORE_PATTERN": [], "DIRSFIRST": false, "SESAME": null, - "VERIFY_HOSTNAME": null + "VERIFY_HOSTNAME": null, + "ENV_PREFIX": "HC_" } From 8502faee6e67403927b719b761402e942b4e6151 Mon Sep 17 00:00:00 2001 From: Daniel Perna Date: Fri, 29 Jun 2018 00:09:16 +0200 Subject: [PATCH 03/33] Added basic git stash functionality (Issue #16) --- changelog.txt | 1 + configurator.py | 63 +++++++++++++++++++++++++++++++++++++++++++++++++ dev.html | 31 ++++++++++++++++++++++++ 3 files changed, 95 insertions(+) diff --git a/changelog.txt b/changelog.txt index 16bcc77..f2068b7 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,6 @@ Version 0.3.0 (2018-) - Allow passing settings (except lists) via environment variables (Issue #100) @danielperna84 +- Added basic git stash functionality (Issue #16) @danielperna84 Version 0.2.9 (2018-06-22) - Material Icons and HASS-help now open in new tab instead of modal (Issues #85 and #34) @danielperna84 diff --git a/configurator.py b/configurator.py index 2503b02..4cdd7cc 100755 --- a/configurator.py +++ b/configurator.py @@ -699,11 +699,13 @@
  • git init
  • git commit
  • git push
  • +
  • git stash
  • +