Added optional authentication and insert-highlighting

This commit is contained in:
Daniel 2017-01-29 22:11:33 +00:00
parent 76d9f78b84
commit 68948d4f09
5 changed files with 133 additions and 14 deletions

View file

@ -42,6 +42,8 @@ If you're using SSL, set the paths to your SSL files here. This is similar to th
The configurator fetches some data from your running HASS instance. If the API isn't available through the default URL, modify this variable to fix this. The configurator fetches some data from your running HASS instance. If the API isn't available through the default URL, modify this variable to fix this.
####HASS_API_PASSWORD (string) ####HASS_API_PASSWORD (string)
If you plan on using the restart button, you have to set your API password. Calling the restart service of HASS is prohibited without authentication. If you plan on using the restart button, you have to set your API password. Calling the restart service of HASS is prohibited without authentication.
####CREDENTIALS (string)
Set credentials in the form of `"username:password"` if authentication should be required for access.
###Embedding into HASS ###Embedding into HASS
HASS has the [panel_iframe](https://home-assistant.io/components/panel_iframe/) component. With this it is possible to embed the configurator directly into HASS, allowing you to modify your configuration through the HASS frontend. HASS has the [panel_iframe](https://home-assistant.io/components/panel_iframe/) component. With this it is possible to embed the configurator directly into HASS, allowing you to modify your configuration through the HASS frontend.

View file

@ -1,3 +1,7 @@
Version 0.0.7 (2017-01-nn)
- Added optional authentication
- Inserted elements are selected as visual feedback
Version 0.0.6 (2017-01-29) Version 0.0.6 (2017-01-29)
- Added SSL support - Added SSL support
- Added HASS restart button - Added HASS restart button

View file

@ -9,6 +9,7 @@ import json
import ssl import ssl
import socketserver import socketserver
import urllib.request import urllib.request
import base64
from string import Template from string import Template
from http.server import BaseHTTPRequestHandler, HTTPServer from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse, parse_qs from urllib.parse import urlparse, parse_qs
@ -24,6 +25,8 @@ SSL_KEY = None
# Set the destination where the HASS API is reachable # Set the destination where the HASS API is reachable
HASS_API = "http://127.0.0.1:8123/api/" HASS_API = "http://127.0.0.1:8123/api/"
HASS_API_PASSWORD = None HASS_API_PASSWORD = None
# To enable authentication, set the credentials in the form of "username:password"
CREDENTIALS = None
### End of options ### End of options
RELEASEURL = "https://api.github.com/repos/danielperna84/hass-poc-configurator/releases/latest" RELEASEURL = "https://api.github.com/repos/danielperna84/hass-poc-configurator/releases/latest"
@ -109,7 +112,7 @@ INDEX = Template("""<!DOCTYPE html>
<div id="menu"> <div id="menu">
<div id="tree"></div><br /> <div id="tree"></div><br />
<label for="triggers">Trigger platforms:</label><br /> <label for="triggers">Trigger platforms:</label><br />
<select id="triggers" onchange="editor.session.insert(editor.getCursorPosition(), this.value)"> <select id="triggers" onchange="insert(this.value)">
<option value="" disabled selected>...</option> <option value="" disabled selected>...</option>
<option value="event">Event</option> <option value="event">Event</option>
<option value="mqtt">MQTT</option> <option value="mqtt">MQTT</option>
@ -121,11 +124,11 @@ INDEX = Template("""<!DOCTYPE html>
<option value="zone">Zone</option> <option value="zone">Zone</option>
</select><br /><br /> </select><br /><br />
<label for="events">Events:</label><br /> <label for="events">Events:</label><br />
<select id="events" onchange="editor.session.insert(editor.getCursorPosition(), this.value)"></select><br /><br /> <select id="events" onchange="insert(this.value)"></select><br /><br />
<label for="entities">Entities:</label><br /> <label for="entities">Entities:</label><br />
<select id="entities" onchange="editor.session.insert(editor.getCursorPosition(), this.value)"></select><br /><br /> <select id="entities" onchange="insert(this.value)"></select><br /><br />
<label for="conditions">Conditions:</label><br /> <label for="conditions">Conditions:</label><br />
<select id="conditions" onchange="editor.session.insert(editor.getCursorPosition(), this.value)"> <select id="conditions" onchange="insert(this.value)">
<option value="" disabled selected>...</option> <option value="" disabled selected>...</option>
<option value="numeric_state">Numeric state</option> <option value="numeric_state">Numeric state</option>
<option value="state">State</option> <option value="state">State</option>
@ -135,7 +138,7 @@ INDEX = Template("""<!DOCTYPE html>
<option value="zone">Zone</option> <option value="zone">Zone</option>
</select><br /><br /> </select><br /><br />
<label for="services">Services:</label><br /> <label for="services">Services:</label><br />
<select id="services" onchange="editor.session.insert(editor.getCursorPosition(), this.value)"></select> <select id="services" onchange="insert(this.value)"></select>
</div> </div>
<div id="toolbar"> <div id="toolbar">
<button id="savebutton" type="button" onclick="save()">Save</button> <button id="savebutton" type="button" onclick="save()">Save</button>
@ -298,6 +301,11 @@ INDEX = Template("""<!DOCTYPE html>
editor.setOption("displayIndentGuides", true); editor.setOption("displayIndentGuides", true);
editor.setOption("highlightSelectedWord", highlightwords); editor.setOption("highlightSelectedWord", highlightwords);
editor.$blockScrolling = Infinity; editor.$blockScrolling = Infinity;
function insert(text) {
var pos = editor.selection.getCursor();
var end = editor.session.insert(pos, text);
editor.selection.setRange({start:pos, end:end});
}
</script> </script>
</html> </html>
""") """)
@ -455,11 +463,60 @@ class RequestHandler(BaseHTTPRequestHandler):
self.wfile.write(bytes(response, "utf8")) self.wfile.write(bytes(response, "utf8"))
return return
class AuthHandler(RequestHandler):
def do_HEAD(self):
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
def do_AUTHHEAD(self):
print("Requesting authorization")
self.send_response(401)
self.send_header('WWW-Authenticate', 'Basic realm=\"HASS-PoC-Configurator\"')
self.send_header('Content-type', 'text/html')
self.end_headers()
def do_GET(self):
global CREDENTIALS
authorization = self.headers.get('Authorization', None)
if authorization == None:
self.do_AUTHHEAD()
self.wfile.write(bytes('no auth header received', 'utf-8'))
pass
elif authorization == 'Basic %s' % CREDENTIALS.decode('utf-8'):
super().do_GET()
pass
else:
self.do_AUTHHEAD()
self.wfile.write(bytes('not authenticated', 'utf-8'))
pass
def do_POST(self):
global CREDENTIALS
authorization = self.headers.get('Authorization', None)
if authorization == None:
self.do_AUTHHEAD()
self.wfile.write(bytes('no auth header received', 'utf-8'))
pass
elif authorization == 'Basic %s' % CREDENTIALS.decode('utf-8'):
super().do_POST()
pass
else:
self.do_AUTHHEAD()
self.wfile.write(bytes('not authenticated', 'utf-8'))
pass
def run(): def run():
global CREDENTIALS
print('Starting server') print('Starting server')
server_address = (LISTENIP, LISTENPORT) server_address = (LISTENIP, LISTENPORT)
if CREDENTIALS:
CREDENTIALS = base64.b64encode(bytes(CREDENTIALS, "utf-8"))
Handler = AuthHandler
else:
Handler = RequestHandler
if not SSL_CERTIFICATE: if not SSL_CERTIFICATE:
httpd = HTTPServer(server_address, RequestHandler) httpd = HTTPServer(server_address, Handler)
else: else:
httpd = socketserver.TCPServer(server_address, RequestHandler) httpd = socketserver.TCPServer(server_address, RequestHandler)
httpd.socket = ssl.wrap_socket(httpd.socket, certfile=SSL_CERTIFICATE, keyfile=SSL_KEY, server_side=True) httpd.socket = ssl.wrap_socket(httpd.socket, certfile=SSL_CERTIFICATE, keyfile=SSL_KEY, server_side=True)

View file

@ -13,7 +13,7 @@ from string import Template
from http.server import BaseHTTPRequestHandler, HTTPServer from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse, parse_qs from urllib.parse import urlparse, parse_qs
### Some options for you to change ######### Some options for you to change #########
LISTENIP = "0.0.0.0" LISTENIP = "0.0.0.0"
LISTENPORT = 3218 LISTENPORT = 3218
# Set BASEPATH to something like "/home/hass/.homeasssitant" if you're not running the configurator from that path # Set BASEPATH to something like "/home/hass/.homeasssitant" if you're not running the configurator from that path
@ -23,7 +23,9 @@ SSL_CERTIFICATE = None
SSL_KEY = None SSL_KEY = None
# Set the destination where the HASS API is reachable # Set the destination where the HASS API is reachable
HASS_API = "http://127.0.0.1:8123/api/" HASS_API = "http://127.0.0.1:8123/api/"
### End of options # To enable authentication, set the credentials in the form of "username:password"
CREDENTIALS = None
################# End of options #################
RELEASEURL = "https://api.github.com/repos/danielperna84/hass-poc-configurator/releases/latest" RELEASEURL = "https://api.github.com/repos/danielperna84/hass-poc-configurator/releases/latest"
VERSION = "0.0.6" VERSION = "0.0.6"
@ -165,11 +167,60 @@ class RequestHandler(BaseHTTPRequestHandler):
self.wfile.write(bytes(response, "utf8")) self.wfile.write(bytes(response, "utf8"))
return return
class AuthHandler(RequestHandler):
def do_HEAD(self):
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
def do_AUTHHEAD(self):
print("Requesting authorization")
self.send_response(401)
self.send_header('WWW-Authenticate', 'Basic realm=\"HASS-PoC-Configurator\"')
self.send_header('Content-type', 'text/html')
self.end_headers()
def do_GET(self):
global CREDENTIALS
authorization = self.headers.get('Authorization', None)
if authorization == None:
self.do_AUTHHEAD()
self.wfile.write(bytes('no auth header received', 'utf-8'))
pass
elif authorization == 'Basic %s' % CREDENTIALS.decode('utf-8'):
super().do_GET()
pass
else:
self.do_AUTHHEAD()
self.wfile.write(bytes('not authenticated', 'utf-8'))
pass
def do_POST(self):
global CREDENTIALS
authorization = self.headers.get('Authorization', None)
if authorization == None:
self.do_AUTHHEAD()
self.wfile.write(bytes('no auth header received', 'utf-8'))
pass
elif authorization == 'Basic %s' % CREDENTIALS.decode('utf-8'):
super().do_POST()
pass
else:
self.do_AUTHHEAD()
self.wfile.write(bytes('not authenticated', 'utf-8'))
pass
def run(): def run():
global CREDENTIALS
print('Starting server') print('Starting server')
server_address = (LISTENIP, LISTENPORT) server_address = (LISTENIP, LISTENPORT)
if CREDENTIALS:
CREDENTIALS = base64.b64encode(bytes(CREDENTIALS, "utf-8"))
Handler = AuthHandler
else:
Handler = RequestHandler
if not SSL_CERTIFICATE: if not SSL_CERTIFICATE:
httpd = HTTPServer(server_address, RequestHandler) httpd = HTTPServer(server_address, Handler)
else: else:
httpd = socketserver.TCPServer(server_address, RequestHandler) httpd = socketserver.TCPServer(server_address, RequestHandler)
httpd.socket = ssl.wrap_socket(httpd.socket, certfile=SSL_CERTIFICATE, keyfile=SSL_KEY, server_side=True) httpd.socket = ssl.wrap_socket(httpd.socket, certfile=SSL_CERTIFICATE, keyfile=SSL_KEY, server_side=True)

View file

@ -78,7 +78,7 @@
<div id="menu"> <div id="menu">
<div id="tree"></div><br /> <div id="tree"></div><br />
<label for="triggers">Trigger platforms:</label><br /> <label for="triggers">Trigger platforms:</label><br />
<select id="triggers" onchange="editor.session.insert(editor.getCursorPosition(), this.value)"> <select id="triggers" onchange="insert(this.value)">
<option value="" disabled selected>...</option> <option value="" disabled selected>...</option>
<option value="event">Event</option> <option value="event">Event</option>
<option value="mqtt">MQTT</option> <option value="mqtt">MQTT</option>
@ -90,11 +90,11 @@
<option value="zone">Zone</option> <option value="zone">Zone</option>
</select><br /><br /> </select><br /><br />
<label for="events">Events:</label><br /> <label for="events">Events:</label><br />
<select id="events" onchange="editor.session.insert(editor.getCursorPosition(), this.value)"></select><br /><br /> <select id="events" onchange="insert(this.value)"></select><br /><br />
<label for="entities">Entities:</label><br /> <label for="entities">Entities:</label><br />
<select id="entities" onchange="editor.session.insert(editor.getCursorPosition(), this.value)"></select><br /><br /> <select id="entities" onchange="insert(this.value)"></select><br /><br />
<label for="conditions">Conditions:</label><br /> <label for="conditions">Conditions:</label><br />
<select id="conditions" onchange="editor.session.insert(editor.getCursorPosition(), this.value)"> <select id="conditions" onchange="insert(this.value)">
<option value="" disabled selected>...</option> <option value="" disabled selected>...</option>
<option value="numeric_state">Numeric state</option> <option value="numeric_state">Numeric state</option>
<option value="state">State</option> <option value="state">State</option>
@ -104,7 +104,7 @@
<option value="zone">Zone</option> <option value="zone">Zone</option>
</select><br /><br /> </select><br /><br />
<label for="services">Services:</label><br /> <label for="services">Services:</label><br />
<select id="services" onchange="editor.session.insert(editor.getCursorPosition(), this.value)"></select> <select id="services" onchange="insert(this.value)"></select>
</div> </div>
<div id="toolbar"> <div id="toolbar">
<button id="savebutton" type="button" onclick="save()">Save</button> <button id="savebutton" type="button" onclick="save()">Save</button>
@ -267,5 +267,10 @@
editor.setOption("displayIndentGuides", true); editor.setOption("displayIndentGuides", true);
editor.setOption("highlightSelectedWord", highlightwords); editor.setOption("highlightSelectedWord", highlightwords);
editor.$blockScrolling = Infinity; editor.$blockScrolling = Infinity;
function insert(text) {
var pos = editor.selection.getCursor();
var end = editor.session.insert(pos, text);
editor.selection.setRange({start:pos, end:end});
}
</script> </script>
</html> </html>