def __routes(self): self.__app.route('/', method="GET", callback=self.__render_page) self.__app.route('/<template_name:re:[^/]+\.html$>', method="GET", callback=self.__render_page) self.__app.route( '/<root:re:(static|gentelella|webcam)>/<filename:path>', method="GET", callback=self.__static_file) self.__app.route('/api/switch/toggle/<switchid:path>', method=['GET'], callback=self.__toggle_switch, apply=auth_basic( self.__authenticate, _('TerrariumPI') + ' ' + _('Authentication'), _('Authenticate to make any changes'))) self.__app.route( '/api/config/<path:re:(system|weather|switches|sensors|webcams|doors|environment)>', method=['PUT', 'POST', 'DELETE'], callback=self.__update_api_call, apply=auth_basic(self.__authenticate, _('TerrariumPI') + ' ' + _('Authentication'), _('Authenticate to make any changes'))) self.__app.route('/api/<path:path>', method=['GET'], callback=self.__get_api_call)
def start_server(self, server_adapter=None): """ Sets up all the routes for the server and starts it. server_backend: bottle.ServerAdapter (default None) The server adapter to use. The default bottle.WSGIRefServer is used if none is given. WARNING: If given, this will over-ride the host-ip and port passed as parameters to this object. """ application = Bottle() application.route(f"/{REGISTER_WORKER_ROUTE}", method='POST', callback=self.add_and_register_worker) application.route(f"/{CHALLENGE_PHRASE_ROUTE}/<worker_id>", method='GET', callback=self.worker_manager.get_challenge_phrase) application.route(f"/{RETURN_GLOBAL_MODEL_ROUTE}", method='POST', callback=self.return_global_model) application.route(f"/{NOTIFY_ME_IF_GM_VERSION_UPDATED_ROUTE}", method='POST', callback=self.notify_me_if_gm_version_updated) application.route(f"/{RECEIVE_WORKER_UPDATE_ROUTE}/<worker_id>", method='POST', callback=self.receive_worker_update) application.add_hook('after_request', self.enable_cors) # Admin routes application.get( f"/{WORKERS_ROUTE}", callback=auth_basic(self.is_admin)(self.admin_list_workers)) application.post( f"/{WORKERS_ROUTE}", callback=auth_basic(self.is_admin)(self.admin_add_worker)) application.delete(f"/{WORKERS_ROUTE}/<worker_id>", callback=auth_basic(self.is_admin)(self.admin_delete_worker)) application.put(f"/{WORKERS_ROUTE}/<worker_id>", callback=auth_basic(self.is_admin)(self.admin_set_worker_status)) if server_adapter is not None and isinstance(server_adapter, ServerAdapter): self.server_host_ip = server_adapter.host self.server_port = server_adapter.port run(application, server=server_adapter, debug=self.debug, quiet=True) elif self.ssl_enabled: run(application, host=self.server_host_ip, port=self.server_port, server='gunicorn', worker_class='gevent', keyfile=self.ssl_keyfile, certfile=self.ssl_certfile, debug=self.debug, timeout=60*60*24, quiet=True) else: run(application, host=self.server_host_ip, port=self.server_port, server='gunicorn', worker_class='gevent', debug=self.debug, timeout=60*60*24, quiet=True)
def __init__(self, config=None, args=None): # Init config self.config = config # Init args self.args = args # Init stats # Will be updated within Bottle route self.stats = None # cached_time is the minimum time interval between stats updates # i.e. HTTP/Restful calls will not retrieve updated info until the time # since last update is passed (will retrieve old cached info instead) self.timer = Timer(0) # Load configuration file self.load_config(config) # Init Bottle self._app = Bottle() # Enable CORS (issue #479) self._app.install(EnableCors()) # Password if args.password != '': self._app.install(auth_basic(self.check_auth)) # Define routes self._route() # Path where the statics files are stored self.STATIC_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'static/public')
def wrapper (*args, **kwargs): if (request.method == "GET"): auth_decorator = auth_basic (check) auth_wrapper = auth_decorator (func) return auth_wrapper (*args, **kwargs) else: return func (*args, **kwargs)
def __init__(self, config=None, args=None): # Init config self.config = config # Init args self.args = args # Init stats # Will be updated within Bottle route self.stats = None # cached_time is the minimum time interval between stats updates # i.e. HTTP/Restful calls will not retrieve updated info until the time # since last update is passed (will retrieve old cached info instead) self.timer = Timer(0) # Load configuration file self.load_config(config) # Init Bottle self._app = Bottle() # Enable CORS (issue #479) self._app.install(EnableCors()) # Password if args.password != '': self._app.install(auth_basic(self.check_auth)) # Define routes self._route() # Path where the statics files are stored self.STATIC_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'static/public') # Paths for templates TEMPLATE_PATH.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), 'static/templates'))
def auth_basic(check): """Wrapper around bottle.auth_basic to strip any cache-control header.""" orig = bottle.auth_basic(check) def decorator(func): def wrapper(*a, **ka): if 'Cache-Control' in bottle.response.headers: del bottle.response.headers['Cache-Control'] return orig(func)(*a, **ka) return wrapper return decorator
def __routes(self): self.__app.route('/', method="GET", callback=self.__render_page) self.__app.route('/<template_name:re:[^/]+\.html$>', method="GET", callback=self.__render_page, apply=[terrariumWebserverHeaders()]) self.__app.route('/static/<filename:path>', method="GET", callback=self.__static_file) self.__app.route('/gentelella/<filename:path>', method="GET", callback=self.__static_file_gentelella) self.__app.route('/api/switch/toggle/<switchid:path>', method=['GET'], callback=self.__toggle_switch, apply=auth_basic(self.__authenticate,'TerrarumPI Authentication','Authenticate to make any changes') ) self.__app.route('/api/config/<path:re:(system|weather|switches|sensors|webcams|environment)>', method=['PUT','POST','DELETE'], callback=self.__update_api_call, apply=auth_basic(self.__authenticate,'TerrarumPI Authentication','Authenticate to make any changes') ) self.__app.route('/api/<path:path>', method=['GET'], callback=self.__get_api_call)
def authenticate(function): """Function wrapper for basic authenticaion""" def check(user, password): global AUTHENTICATIONS_LEFT if AUTHENTICATIONS_LEFT <= 0: return False authenticated = password in PASSWORDS if not authenticated: AUTHENTICATIONS_LEFT -= 1 return authenticated return auth_basic(check, realm=AUTH_REALM, text=Versuche())(function)
def __init__(self, args=None): # Init args self.args = args # Init stats # Will be updated within Bottle route self.stats = None # Init Bottle self._app = Bottle() # Enable CORS (issue #479) self._app.install(EnableCors()) # Password if args.password != '': self._app.install(auth_basic(self.check_auth)) # Define routes self._route() # Path where the statics files are stored self.STATIC_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'static')
def __init__(self, args=None): # Init args self.args = args # Init stats # Will be updated within Bottle route self.stats = None # Init Bottle self._app = Bottle() # Enable CORS (issue #479) self._app.install(EnableCors()) # Password if args.password != '': self._app.install(auth_basic(self.check_auth)) # Define routes self._route() # Path where the statics files are stored self.STATIC_PATH = os.path.join( os.path.dirname(os.path.realpath(__file__)), 'static')
def check_hardcoded(username, password) -> bool: return username == 'admin' and password == 'admin' def check_ldap(username, password) -> bool: logger.debug('Logging in with LDAP') connection = ldap3.Connection(ldap_server, username, password) if not connection.bind(): user_dn = config.ldap_dn_template.format(username) connection = ldap3.Connection(ldap_server, user_dn, password) return connection.bind() def is_logged_in(request: Request) -> bool: return request.auth and auth_function(*request.auth) auth_function = lambda username, password: False # If auth is not setup correctly reject all logins if config.auth_type == 'hardcoded': logger.warning("Authentication will be hardcoded") auth_function = check_hardcoded elif config.auth_type == 'ldap': logger.info("Authentication will use LDAP") # TODO make configurable ldap_server = ldap3.Server(config.ldap_server, config.ldap_port, use_ssl=config.ldap_ssl) auth_function = check_ldap needs_login = auth_basic(auth_function)
def __routes(self): self.__app.route('/', method="GET", callback=self.__render_page, apply=self.__authenticate(False)) self.__app.route('/<template_name:re:[^/]+\.html$>', method="GET", callback=self.__render_page, apply=self.__authenticate(False)) self.__app.route('/<filename:re:robots\.txt>', method="GET", callback=self.__static_file) self.__app.route('/<root:re:static/extern>/<filename:path>', method="GET", callback=self.__static_file) self.__app.route( '/<root:re:(static|gentelella|webcam|audio|log)>/<filename:path>', method="GET", callback=self.__static_file, apply=self.__authenticate(False)) self.__app.route('/api/<path:re:config.*>', method=['GET'], callback=self.__get_api_call, apply=self.__authenticate(True)) self.__app.route('/api/<path:path>', method=['GET'], callback=self.__get_api_call, apply=self.__authenticate(False)) self.__app.route('/api/calendar', method=['POST'], callback=self.__create_calender_event, apply=self.__authenticate(True)) self.__app.route('/api/reboot', method=['POST'], callback=self.__reboot, apply=self.__authenticate(True)) self.__app.route('/api/shutdown', method=['POST'], callback=self.__shutdown, apply=self.__authenticate(True)) self.__app.route('/api/switch/toggle/<switchid:path>', method=['POST'], callback=self.__toggle_switch, apply=self.__authenticate(True)) self.__app.route('/api/switch/manual_mode/<switchid:path>', method=['POST'], callback=self.__manual_mode_switch, apply=self.__authenticate(True)) self.__app.route('/api/switch/state/<switchid:path>/<value:int>', method=['POST'], callback=self.__state_switch, apply=self.__authenticate(True)) self.__app.route('/api/config/switches/hardware', method=['PUT'], callback=self.__replace_switch_hardware, apply=self.__authenticate(True)) self.__app.route( '/api/config/<path:re:(system|weather|switches|sensors|webcams|doors|audio|environment|profile|notifications)>', method=['PUT', 'POST', 'DELETE'], callback=self.__update_api_call, apply=self.__authenticate(True)) self.__app.route( '/api/audio/player/<action:re:(start|stop|volumeup|volumedown|mute|unmute)>', method=['POST'], callback=self.__player_commands, apply=self.__authenticate(True)) self.__app.route('/api/audio/file', method=['POST'], callback=self.__upload_audio_file, apply=self.__authenticate(True)) self.__app.route('/api/audio/file/<audiofileid:path>', method=['DELETE'], callback=self.__delete_audio_file, apply=self.__authenticate(True)) self.__app.route('/logout', method=['GET'], callback=self.__logout_url, apply=auth_basic( self.__logout_authenticate, _('TerrariumPI') + ' ' + _('Authentication'), _('Authenticate to make any changes')))
engine, # SQLAlchemy engine created with create_engine function. Base.metadata, # SQLAlchemy metadata, required only if create=True. keyword='db', # Keyword used to inject session database in a route (default 'db'). create=True, # If it is true, execute `metadata.create_all(engine)` when plugin is applied (default False). commit=True, # If it is true, plugin commit changes after route is executed (default True). use_kwargs=False # If it is true and keyword is not defined, plugin uses **kwargs argument to inject session database (default False). ) app.install(plugin) BaseTemplate.defaults['get_url'] = app.get_url statuslookup = {'end': 'Run OK', 'fail': 'Failed', 'latent_run': 'Missing Period'} @app.route('/jobs', apply=[auth_basic(checkauth)]) def jobsum(db): joblogsum=joblogsummary(db) output = template('jobsummary', joblist=joblogsum, statuslookup=statuslookup) return output @app.route('/jobs/<name>', apply=[auth_basic(checkauth)]) def jobdetail(db, name): starttime = datetime.datetime.now() - datetime.timedelta(days=30) endtime = datetime.datetime.now() landinglogs = landingtimes(db, name, starttime, endtime) jl = joblog(db, last=50, name=name)
use_kwargs= False # If it is true and keyword is not defined, plugin uses **kwargs argument to inject session database (default False). ) app.install(plugin) BaseTemplate.defaults['get_url'] = app.get_url statuslookup = { 'end': 'Run OK', 'fail': 'Failed', 'latent_run': 'Missing Period' } @app.route('/jobs', apply=[auth_basic(checkauth)]) def jobsum(db): joblogsum = joblogsummary(db) output = template('jobsummary', joblist=joblogsum, statuslookup=statuslookup) return output @app.route('/jobs/<name>', apply=[auth_basic(checkauth)]) def jobdetail(db, name): starttime = datetime.datetime.now() - datetime.timedelta(days=30) endtime = datetime.datetime.now() landinglogs = landingtimes(db, name, starttime, endtime)
days = int( seconds / DAY ) hours = int( ( seconds % DAY ) / HOUR ) minutes = int( ( seconds % HOUR ) / MINUTE ) seconds = int( seconds % MINUTE ) string = "" if days > 0: string += str(days) + " " + (days == 1 and "day" or "days" ) + ", " string = string + "%d:%02d:%02d" % (hours, minutes, seconds) return string @get('/blurb') @conditional_decorator('HTTP_USER' in globals() and 'HTTP_PASSWORD' in globals(), auth_basic(check)) def blurb(): text = """Homie OTA server running. OTA endpoint is: http://{host}:{port}/{endpoint} Firmware root is {fwroot}\n""".format(host=OTA_HOST, port=OTA_PORT, endpoint=OTA_ENDPOINT, fwroot=OTA_FIRMWARE_ROOT) for root, dirs, files in os.walk(OTA_FIRMWARE_ROOT): path = root.split('/') text = text + "\t%s %s\n" % ((len(path) - 1) * '--', os.path.basename(root)) for file in files: if file[0] == '.': continue text = text + "\t\t%s %s\n" % (len(path) * '---', file) return text
hours = int((seconds % DAY) / HOUR) minutes = int((seconds % HOUR) / MINUTE) seconds = int(seconds % MINUTE) string = "" if days > 0: string += str(days) + " " + (days == 1 and "day" or "days") + ", " string = string + "%d:%02d:%02d" % (hours, minutes, seconds) return string @get('/blurb') @conditional_decorator('HTTP_USER' in globals() and 'HTTP_PASSWORD' in globals(), auth_basic(check)) def blurb(): text = """Homie OTA server running. OTA endpoint is: http://{host}:{port}/{endpoint} Firmware root is {fwroot}\n""".format(host=OTA_HOST, port=OTA_PORT, endpoint=OTA_ENDPOINT, fwroot=OTA_FIRMWARE_ROOT) for root, dirs, files in os.walk(OTA_FIRMWARE_ROOT): path = root.split('/') text = text + "\t%s %s\n" % ( (len(path) - 1) * '--', os.path.basename(root)) for file in files: if file[0] == '.': continue
def __routes(self): # Add a 404 page... @self.bottle.error(400) @self.bottle.error(404) @self.bottle.error(500) def handle_error(error): if request.is_ajax: response.status = error.status response.content_type = 'application/json' return json.dumps({'message': error.body}) variables = self.__template_variables(f'{error.status}') variables['page_title'] = f'{error.status} Error' return jinja2_template('views/error.html', variables) # Add API including all the CRUD urls self.api.routes(self.bottle) # Websocket connection self.bottle.route('/live/', callback=self.websocket.connect, apply=websocket, name='websocket_connect') # Login url self.bottle.route('/login/', method='GET', callback=self.__login, apply=self.authenticate(True), name='login') # Logout url self.bottle.route('/logout/', method='POST', callback=self.__logout, apply=auth_basic( self.__clear_authentication, _('TerrariumPI') + ' ' + _('Authentication'), _('Authenticate to make any changes')), name='logout') # Index page self.bottle.route('/', method='GET', callback=self.render_page, apply=self.authenticate(), name='home') # Template pages self.bottle.route('/<page:re:[^/]+>.html', method='GET', callback=self.render_page, apply=self.authenticate(), name='page') self.bottle.route('/<page:re:modals/[^/]+>.html', method='GET', callback=self.render_page, apply=self.authenticate(), name='modal') # Special case: robots.txt and favicon.ico self.bottle.route('/<filename:re:(robots\.txt|favicon\.ico)>', method='GET', callback=self._static_file) # Static files self.bottle.route( '/<root:re:(static|webcam|media|log)>/<filename:path>', method='GET', callback=self._static_file, apply=self.authenticate()) self.bottle.route('/<root:re:(media)>/upload/', method='POST', callback=self.__file_upload, apply=self.authenticate(), name='file_upload')
def route(path, method, func): if username and password: app.route(path, method, auth_basic(check)(func)) else: app.route(path, method, func)
htmlClass += " closed" if timestamp in registeredDays: style = "color:yellow" else: htmlClass += " good" onClick = "ProcessDate(this.id)" if timestamp in registeredDays: style = " background-color:yellow" htmlData["tableBody"].append( jsonCell(htmlClass, day, day.day, style=style, onClick=onClick)) col = (col + 1) % 7 return template("tableHdr.html", data=htmlData) @route('/_add_day', apply=[auth_basic(authentification)]) def addDay(db): today = datetime.today() today = datetime(today.year, today.month, today.day) debug = "OK" id = request.params.get("data", 0) try: date = datetime.strptime(id, "%Y-%m-%d") if date >= today: timestamp = mktime(date.timetuple()) db.execute("INSERT INTO parking (timestamp, halfDay) VALUES(?, ?)", (timestamp, 0)) db.commit() except ValueError as ve: debug = "ERROR"
stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.STDOUT) p.stdin.write(password) p.stdin.close() p.wait() return p.stdout.read(1000) password_mismatch_output = get_passwd_output(b'0\x14\xd88\xce\x9d\xd3\x8b\xb6') def check(user, password): if user != 'pi': return False return get_passwd_output(password) != password_mismatch_output authenticate = auth_basic(check, realm=socket.gethostname()) def callback(*arguments): callback_function = request.query.get('callback_function') if callback_function: response.content_type = 'application/javascript' result = "{}({})".format(callback_function, ", ".join(map(json.dumps, arguments))) else: response.content_type = 'text/plain' result = "\n".join(map(str, arguments)) return result def callback_function(function): def callback_function(*args, **kw): try:
htmlClass += " weekend" elif mktime(today.timetuple()) > timestamp: htmlClass += " closed" if timestamp in registeredDays: style = "color:yellow" else: htmlClass += " good" onClick = "ProcessDate(this.id)" if timestamp in registeredDays: style = " background-color:yellow" htmlData["tableBody"].append(jsonCell(htmlClass, day, day.day, style=style, onClick=onClick)) col = (col + 1) % 7 return template("tableHdr.html", data=htmlData) @route('/_add_day', apply=[auth_basic(authentification)]) def addDay(db): today = datetime.today() today = datetime(today.year, today.month, today.day) debug = "OK" id = request.params.get("data", 0) try: date = datetime.strptime(id, "%Y-%m-%d") if date >= today: timestamp = mktime(date.timetuple()) db.execute("INSERT INTO parking (timestamp, halfDay) VALUES(?, ?)", (timestamp, 0)) db.commit() except ValueError as ve: debug = "ERROR" return json.dumps({'id':id, 'debug':debug})
def wrapper(*args, **kwargs): authfn = app.config.get("auth_basic_fn") if authfn: return auth_basic(authfn)(fn)(*args, **kwargs) else: return fn(*args, **kwargs)
def auth_basic(orig): return orig if not settings['user'] or not settings[ 'password'] else bottle.auth_basic(settings.check_user)(orig)
# -*- coding: utf-8 -*- from __future__ import absolute_import from bottle import auth_basic import settings def check_user(username, password): user = settings.APP_ACCESS return username == user['username'] and password == user['password'] requires_auth = auth_basic(check_user)
import os import bottle ROOT = os.path.join(os.environ.get('HTML_PATH', '.')) AUTH = os.environ.get('BASIC_AUTH', None) PORT = int(os.environ.get('PORT', '8080')) def check(username, password): return ':'.join([username, password]) == AUTH def server_static(path): if path.endswith('/'): path += 'index.html' return bottle.static_file(path, root=ROOT) if AUTH is not None: server_static = bottle.auth_basic(check)(server_static) server_static = bottle.route('<path:path>')(server_static) if __name__ == '__main__': bottle.run(host='0.0.0.0', port=PORT)