Add persistent notification for failed login attempts (#3528)

This commit is contained in:
Fabian Affolter 2016-10-02 06:20:16 +02:00 committed by Paulus Schoutsen
parent 9da2d6edd0
commit a61e08680a

View File

@ -11,18 +11,20 @@ import mimetypes
import threading import threading
import re import re
import ssl import ssl
import voluptuous as vol import voluptuous as vol
import homeassistant.remote as rem import homeassistant.remote as rem
from homeassistant import util from homeassistant import util
from homeassistant.const import ( from homeassistant.const import (
SERVER_PORT, HTTP_HEADER_HA_AUTH, HTTP_HEADER_CACHE_CONTROL, SERVER_PORT, HTTP_HEADER_HA_AUTH, HTTP_HEADER_CACHE_CONTROL,
HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, CONTENT_TYPE_JSON,
HTTP_HEADER_ACCESS_CONTROL_ALLOW_HEADERS, ALLOWED_CORS_HEADERS, HTTP_HEADER_ACCESS_CONTROL_ALLOW_HEADERS, ALLOWED_CORS_HEADERS,
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START) EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START)
from homeassistant.core import split_entity_id from homeassistant.core import split_entity_id
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.components import persistent_notification
DOMAIN = 'http' DOMAIN = 'http'
REQUIREMENTS = ('cherrypy==8.1.0', 'static3==0.7.0', 'Werkzeug==0.11.11') REQUIREMENTS = ('cherrypy==8.1.0', 'static3==0.7.0', 'Werkzeug==0.11.11')
@ -37,6 +39,7 @@ CONF_SSL_KEY = 'ssl_key'
CONF_CORS_ORIGINS = 'cors_allowed_origins' CONF_CORS_ORIGINS = 'cors_allowed_origins'
DATA_API_PASSWORD = 'api_password' DATA_API_PASSWORD = 'api_password'
NOTIFICATION_ID_LOGIN = 'http-login'
# TLS configuation follows the best-practice guidelines specified here: # TLS configuation follows the best-practice guidelines specified here:
# https://wiki.mozilla.org/Security/Server_Side_TLS # https://wiki.mozilla.org/Security/Server_Side_TLS
@ -106,7 +109,7 @@ def setup(hass, config):
api_password = util.convert(conf.get(CONF_API_PASSWORD), str) api_password = util.convert(conf.get(CONF_API_PASSWORD), str)
server_host = conf.get(CONF_SERVER_HOST, '0.0.0.0') server_host = conf.get(CONF_SERVER_HOST, '0.0.0.0')
server_port = conf.get(CONF_SERVER_PORT, SERVER_PORT) server_port = conf.get(CONF_SERVER_PORT, SERVER_PORT)
development = str(conf.get(CONF_DEVELOPMENT, "")) == "1" development = str(conf.get(CONF_DEVELOPMENT, '')) == '1'
ssl_certificate = conf.get(CONF_SSL_CERTIFICATE) ssl_certificate = conf.get(CONF_SSL_CERTIFICATE)
ssl_key = conf.get(CONF_SSL_KEY) ssl_key = conf.get(CONF_SSL_KEY)
cors_origins = conf.get(CONF_CORS_ORIGINS, []) cors_origins = conf.get(CONF_CORS_ORIGINS, [])
@ -365,8 +368,8 @@ class HomeAssistantWSGI(object):
context.load_cert_chain(self.ssl_certificate, self.ssl_key) context.load_cert_chain(self.ssl_certificate, self.ssl_key)
self.server.ssl_adapter = ContextSSLAdapter(context) self.server.ssl_adapter = ContextSSLAdapter(context)
threading.Thread(target=self.server.start, daemon=True, threading.Thread(
name='WSGI-server').start() target=self.server.start, daemon=True, name='WSGI-server').start()
def stop(self): def stop(self):
"""Stop the wsgi server.""" """Stop the wsgi server."""
@ -391,10 +394,10 @@ class HomeAssistantWSGI(object):
resp = ex.get_response(request.environ) resp = ex.get_response(request.environ)
if request.accept_mimetypes.accept_json: if request.accept_mimetypes.accept_json:
resp.data = json.dumps({ resp.data = json.dumps({
"result": "error", 'result': 'error',
"message": str(ex), 'message': str(ex),
}) })
resp.mimetype = "application/json" resp.mimetype = CONTENT_TYPE_JSON
return resp return resp
def base_app(self, environ, start_response): def base_app(self, environ, start_response):
@ -403,11 +406,11 @@ class HomeAssistantWSGI(object):
response = self.dispatch_request(request) response = self.dispatch_request(request)
if self.cors_origins: if self.cors_origins:
cors_check = (environ.get("HTTP_ORIGIN") in self.cors_origins) cors_check = (environ.get('HTTP_ORIGIN') in self.cors_origins)
cors_headers = ", ".join(ALLOWED_CORS_HEADERS) cors_headers = ", ".join(ALLOWED_CORS_HEADERS)
if cors_check: if cors_check:
response.headers[HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN] = \ response.headers[HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN] = \
environ.get("HTTP_ORIGIN") environ.get('HTTP_ORIGIN')
response.headers[HTTP_HEADER_ACCESS_CONTROL_ALLOW_HEADERS] = \ response.headers[HTTP_HEADER_ACCESS_CONTROL_ALLOW_HEADERS] = \
cors_headers cors_headers
@ -425,7 +428,7 @@ class HomeAssistantWSGI(object):
# Strip out any cachebusting MD5 fingerprints # Strip out any cachebusting MD5 fingerprints
fingerprinted = _FINGERPRINT.match(environ.get('PATH_INFO', '')) fingerprinted = _FINGERPRINT.match(environ.get('PATH_INFO', ''))
if fingerprinted: if fingerprinted:
environ['PATH_INFO'] = "{}.{}".format(*fingerprinted.groups()) environ['PATH_INFO'] = '{}.{}'.format(*fingerprinted.groups())
return app(environ, start_response) return app(environ, start_response)
@ -489,6 +492,10 @@ class HomeAssistantView(object):
if self.requires_auth and not authenticated: if self.requires_auth and not authenticated:
_LOGGER.warning('Login attempt or request with an invalid ' _LOGGER.warning('Login attempt or request with an invalid '
'password from %s', request.remote_addr) 'password from %s', request.remote_addr)
persistent_notification.create(
self.hass,
'Invalid password used from {}'.format(request.remote_addr),
'Login attempt failed', NOTIFICATION_ID_LOGIN)
raise Unauthorized() raise Unauthorized()
request.authenticated = authenticated request.authenticated = authenticated
@ -512,12 +519,9 @@ class HomeAssistantView(object):
def json(self, result, status_code=200): def json(self, result, status_code=200):
"""Return a JSON response.""" """Return a JSON response."""
msg = json.dumps( msg = json.dumps(
result, result, sort_keys=True, cls=rem.JSONEncoder).encode('UTF-8')
sort_keys=True, return self.Response(
cls=rem.JSONEncoder msg, mimetype=CONTENT_TYPE_JSON, status=status_code)
).encode('UTF-8')
return self.Response(msg, mimetype="application/json",
status=status_code)
def json_message(self, error, status_code=200): def json_message(self, error, status_code=200):
"""Return a JSON message response.""" """Return a JSON message response."""