def _init_(self, **kwargs): self.custom_agent = Agent(reactor, connectTimeout=20) self.contentType = self._Configs.get('yomboapi', 'contenttype', 'application/json', False) # TODO: Msgpack later self.base_url = self._Configs.get('yomboapi', 'baseurl', "https://api.yombo.net/api", False) self.allow_system_session = self._Configs.get('yomboapi', 'allow_system_session', True) self.init_defer = None self.api_key = self._Configs.get('yomboapi', 'api_key', 'aBMKp5QcQoW43ipauw88R0PT2AohcE', False) self.valid_system_session = None self.valid_login_key = None self.session_validation_cache = ExpiringDict() try: self.system_session = self._Configs.get( 'yomboapi', 'auth_session') # to be encrypted with gpg later self.system_login_key = self._Configs.get( 'yomboapi', 'login_key') # to be encrypted with gpg later except KeyError: self.system_session = None self.system_login_key = None if self._Loader.operating_mode == 'run': self.init_defer = Deferred() self.validate_system_login() return self.init_defer
def _init_(self, **kwargs): self.enabled = self._Configs.get('webinterface', 'enabled', True) if not self.enabled: return self.gateway_id = self._Configs.get2('core', 'gwid', 'local', False) # self._LocalDB = self._Loader.loadedLibraries['localdb'] self._current_dir = self._Atoms.get('yombo.path') + "/yombo" self._dir = '/lib/webinterface/' self._build_dist() # Make all the JS and CSS files self.secret_pin_totp = self._Configs.get2( 'webinterface', 'auth_pin_totp', yombo.utils.random_string( length=16, letters='ABCDEFGHIJKLMNOPQRSTUVWXYZ234567')) self.api = self._Loader.loadedLibraries['yomboapi'] self._VoiceCmds = self._Loader.loadedLibraries['voicecmds'] self.misc_wi_data = {} self.sessions = Sessions(self._Loader) self.wi_port_nonsecure = self._Configs.get2('webinterface', 'nonsecure_port', 8080) self.wi_port_secure = self._Configs.get2('webinterface', 'secure_port', 8443) self.webapp.templates = jinja2.Environment( loader=jinja2.FileSystemLoader(self._current_dir)) self.setup_basic_filters() route_atoms(self.webapp) route_automation(self.webapp) route_api_v1(self.webapp) route_configs(self.webapp) route_devices(self.webapp) route_locations(self.webapp) route_devtools_debug(self.webapp) route_devtools_config(self.webapp) route_gateways(self.webapp) route_home(self.webapp) route_misc(self.webapp) route_modules(self.webapp) route_notices(self.webapp) route_panel(self.webapp) route_setup_wizard(self.webapp) route_statistics(self.webapp) route_states(self.webapp) route_system(self.webapp) route_voicecmds(self.webapp) self.temp_data = ExpiringDict(max_age_seconds=1800) self.web_server_started = False self.web_server_ssl_started = False self.already_start_web_servers = False self.web_factory = None # just here to set a password if it doesn't exist. mqtt_password = self._Configs.get('mqtt_users', 'panel.webinterface', yombo.utils.random_string())
def _init_(self, **kwargs): """ On startup, various libraries will need certs (webinterface, MQTT) for encryption. This module stores certificates in a directory so other programs can use certs as well. It's working data is stored in the database, while a backup is kept in the file system as well and is only used if the data is missing from the database. If a cert isn't avail for the requested sslname, it will receive a self-signed certificate. :return: """ # Since SSL generation can take some time on slower devices, we use a simple queue system. self.generate_csr_queue = self._Queue.new( 'library.sslcerts.generate_csr', self.generate_csr) self.hostname = gethostname() self.gateway_id = self._Configs.get('core', 'gwid', 'local', False) self.fqdn = self._Configs.get2('dns', 'fqdn', None, False) self.received_message_for_unknown = ExpiringDict(100, 600) self.self_signed_cert_file = self._Atoms.get( 'yombo.path') + "/usr/etc/certs/sslcert_selfsigned.cert.pem" self.self_signed_key_file = self._Atoms.get( 'yombo.path') + "/usr/etc/certs/sslcert_selfsigned.key.pem" self.self_signed_expires = self._Configs.get("sslcerts", "self_signed_expires", None, False) self.self_signed_created = self._Configs.get("sslcerts", "self_signed_created", None, False) if os.path.exists(self.self_signed_cert_file) is False or \ self.self_signed_expires is None or \ self.self_signed_expires < int(time() + (60*60*24*60)) or \ self.self_signed_created is None or \ not os.path.exists(self.self_signed_key_file): logger.info( "Generating a self signed cert for SSL. This can take a few moments." ) yield self._create_self_signed_cert() self.self_signed_cert = yield read_file(self.self_signed_cert_file) self.self_signed_key = yield read_file(self.self_signed_key_file) self.managed_certs = yield self._SQLDict.get( self, "managed_certs", serializer=self.sslcert_serializer, unserializer=self.sslcert_unserializer) # for name, data in self.managed_certs.items(): # print("cert name: %s" % name) # print(" cert data: %s" % data.__dict__) self.check_if_certs_need_update_loop = None
class SSLCerts(YomboLibrary): """ Responsible for managing various encryption and TLS (SSL) certificates. """ managed_certs = {} received_message_for_unknown = ExpiringDict(100, 600) def __contains__(self, cert_requested): """ Looks for an sslkey with the given sslname. >>> if "webinterface" in self._SSLCerts["library_webinterface"]: #by uuid :param cert_requested: The ssl cert sslname to search for. :type cert_requested: string :return: Returns true if exists, otherwise false. :rtype: bool """ if cert_requested in self.managed_certs: return True else: return False @inlineCallbacks def _init_(self, **kwargs): """ On startup, various libraries will need certs (webinterface, MQTT) for encryption. This module stores certificates in a directory so other programs can use certs as well. It's working data is stored in the database, while a backup is kept in the file system as well and is only used if the data is missing from the database. If a cert isn't avail for the requested sslname, it will receive a self-signed certificate. :return: """ # Since SSL generation can take some time on slower devices, we use a simple queue system. self.generate_csr_queue = self._Queue.new( "library.sslcerts.generate_csr", self.generate_csr) self.hostname = gethostname() self.local_gateway = self._Gateways.local self.self_signed_cert_file = self._Atoms.get( "working_dir") + "/etc/certs/sslcert_selfsigned.cert.pem" self.self_signed_key_file = self._Atoms.get( "working_dir") + "/etc/certs/sslcert_selfsigned.key.pem" self.self_signed_expires_at = self._Configs.get( "sslcerts", "self_signed_expires_at", None, False) self.self_signed_created_at = self._Configs.get( "sslcerts", "self_signed_created_at", None, False) self.default_key_size = self._Configs.get("sslcerts", "default_key_size", 2048) if os.path.exists(self.self_signed_cert_file) is False or \ self.self_signed_expires_at is None or \ self.self_signed_expires_at < int(time() + (60*60*24*60)) or \ self.self_signed_created_at is None or \ not os.path.exists(self.self_signed_key_file): logger.info( "Generating a self signed cert for SSL. This can take a few moments." ) yield self._create_self_signed_cert() self.self_signed_cert = yield read_file(self.self_signed_cert_file) self.self_signed_key = yield read_file(self.self_signed_key_file) self.managed_certs = yield self._SQLDict.get( self, "managed_certs", serializer=self.sslcert_serializer, unserializer=self.sslcert_unserializer) for key, item in self.managed_certs.items(): print(f"Managed certs: {self.managed_certs}") self.check_if_certs_need_update_loop = None @inlineCallbacks def _load_(self, **kwargs): """ Starts the loop to check if any certs need to be updated. :return: """ self.check_if_certs_need_update_loop = LoopingCall( self.check_if_certs_need_update) self.check_if_certs_need_update_loop.start( self._Configs.get("sqldict", "save_interval", random_int(60 * 60 * 24, .1), False), False) # Check if any libraries or modules need certs. if self.local_gateway.dns_name is None: logger.warn( "Unable to generate sign ssl/tls certs, gateway has no domain name." ) return if self._Loader.operating_mode != "run": return sslcerts = yield global_invoke_all( "_sslcerts_", called_by=self, ) for component_name, ssl_certs in sslcerts.items(): logger.debug( f"Adding new managed certs from hook: {component_name}") if isinstance(ssl_certs, tuple) is False and isinstance( ssl_certs, list) is False: ssl_certs = [ssl_certs] for ssl_item in ssl_certs: yield self.add_sslcert(ssl_item) def _stop_(self, **kwargs): """ Simply stop any loops, tell all the certs to save themselves to disk as a backup. :return: """ if hasattr(self, "check_if_certs_need_update_loop"): if self.check_if_certs_need_update_loop is not None and self.check_if_certs_need_update_loop.running: self.check_if_certs_need_update_loop.stop() if hasattr(self, "managed_certs"): for sslname, cert in self.managed_certs.items(): cert.stop() def sslcert_serializer(self, item): """ Used to hydrate the list of certs. Somethings shouldn't be stored in the SQLDict. :param item: :return: """ return item.asdict() @inlineCallbacks def sslcert_unserializer(self, item): """ Used by SQLDict to hydrate an item stored. :param item: :return: """ # print(f"sslcert unserialze: {item}") results = SSLCert(self, "sqldict", DictObject(item)) yield results.start() return results @inlineCallbacks def check_if_certs_need_update(self): """ Called periodically to see if any certs need to be updated. Once a day is enough, we have 30 days to get this done. """ for sslname, cert in self.managed_certs.items(): yield cert.check_if_rotate_needed() @inlineCallbacks def add_sslcert(self, ssl_data): """ Called when new SSL Certs need to be managed. :param sslcerts: :param bypass_checks: For internal use only. :return: """ logger.debug("add_sslcert: {ssl_data}", ssl_data=ssl_data) if self.local_gateway.dns_name is None: logger.warn( "Unable to generate sign ssl/tls certs, gateway has no domain name." ) return try: ssl_data = self.check_csr_input( ssl_data) # Clean up module developers input. except YomboWarning as e: logger.warn(f"Cannot add cert: {e}") return if ssl_data["sslname"] in self.managed_certs: self.managed_certs[ssl_data["sslname"]].update_attributes(ssl_data) else: yield self._import_cert(ssl_data["sslname"], DictObject(ssl_data)) @inlineCallbacks def _import_cert(self, cert_name, data, source=None): if source is None: source = "sslcerts" self.managed_certs[cert_name] = SSLCert(self, source, DictObject(data)) yield self.managed_certs[cert_name].start() def get(self, sslname_requested): """ Gets a cert for the request name. .. note:: self._SSLCerts("library_webinterface", self.have_updated_ssl_cert) """ # logger.debug("looking for: {sslname_requested}", sslname_requested=sslname_requested) if sslname_requested in self.managed_certs: # logger.debug("found by cert! {sslname_requested}", sslname_requested=sslname_requested) return self.managed_certs[sslname_requested].get() else: if sslname_requested != "selfsigned": logger.info( "Could not find cert for '{sslname}', sending self signed. Library or module should implement _sslcerts_ with a callback method.", sslname=sslname_requested) return self.get_self_signed() def get_self_signed(self): key_crypt = crypto.load_privatekey(crypto.FILETYPE_PEM, self.self_signed_key) if isinstance(key_crypt, tuple): key_crypt = key_crypt[0] cert_crypt = crypto.load_certificate(crypto.FILETYPE_PEM, self.self_signed_cert) if isinstance(cert_crypt, tuple): cert_crypt = cert_crypt[0] return { "key": self.self_signed_key, "cert": self.self_signed_cert, "chain": None, "key_crypt": key_crypt, "cert_crypt": cert_crypt, "chain_crypt": None, "expires_at": self.self_signed_expires_at, "created_at": self.self_signed_created_at, "signed_at": self.self_signed_created_at, "self_signed": True, "cert_file": self.self_signed_cert_file, "key_file": self.self_signed_key_file, "chain_file": None, } def check_csr_input(self, csr_request): results = {} if "sslname" not in csr_request: raise YomboWarning("'sslname' is required.") results["sslname"] = csr_request["sslname"] if self.local_gateway.dns_name is None: raise YomboWarning( "Unable to create SSL Certs, no system domain set.") if "cn" not in csr_request: raise YomboWarning( f"'cn' must be included, and must end with our local FQDN: {self.local_gateway.dns_name}" ) elif csr_request["cn"].endswith(self.local_gateway.dns_name) is False: results[ "cn"] = csr_request["cn"] + "." + self.local_gateway.dns_name else: results["cn"] = csr_request["cn"] if "sans" not in csr_request: results["sans"] = None else: san_list = [] for san in csr_request["sans"]: if san.endswith(self.local_gateway.dns_name) is False: san_list.append( str(san + "." + self.local_gateway.dns_name)) else: san_list.append(str(san)) results["sans"] = san_list # if "key_type" in csr_request: # allow changing default, might change in the future. # if csr_request["key_type"] != "rsa": # raise YomboWarning("key_type must be 'rsa', received: %s" % csr_request["key_type"]) # results["key_type"] = csr_request["key_type"] # else: # if "key_size" in csr_request: if csr_request["key_size"] < 2048: csr_request["key_size"] = 2048 if csr_request["key_size"] > 4096: csr_request["key_size"] = 4096 else: csr_request["key_size"] = self.default_key_size results["key_type"] = "rsa" results["key_size"] = csr_request["key_size"] if "csr_file" not in csr_request: csr_request["csr_file"] = None results["csr_file"] = csr_request["csr_file"] if "key_file" not in csr_request: csr_request["key_file"] = None results["key_file"] = csr_request["key_file"] if "callback" in csr_request: results["update_callback"] = csr_request["callback"] elif "update_callback" in csr_request: results["update_callback"] = csr_request["update_callback"] if "callback_type" in csr_request: results["update_callback_type"] = csr_request["callback_type"] elif "update_callback_type" in csr_request: results["update_callback_type"] = csr_request[ "update_callback_type"] if "callback_component" in csr_request: results["update_callback_component"] = csr_request[ "callback_component"] elif "update_callback_component" in csr_request: results["update_callback_component"] = csr_request[ "update_callback_component"] if "callback_function" in csr_request: results["update_callback_function"] = csr_request[ "callback_function"] elif "update_callback_function" in csr_request: results["update_callback_function"] = csr_request[ "update_callback_function"] return results @inlineCallbacks def generate_csr(self, args): """ This function shouldn't be called directly. Instead, use the queue "self.generate_csr_queue.put(request, callback, callback_args)" or "self._SSLCerts.generate_csr_queue.put()". Requests certs to be made. Will return right away with a request ID. A callback can be set to return the cert once it's complete. :return: """ logger.debug("Generate_CSR called with args: {args}", args=args) kwargs = self.check_csr_input(args) if kwargs["key_type"] == "rsa": kwargs["key_type"] = crypto.TYPE_RSA else: kwargs["key_type"] = crypto.TYPE_DSA req = crypto.X509Req() req.get_subject().CN = kwargs["cn"] req.get_subject().countryName = "US" req.get_subject().stateOrProvinceName = "California" req.get_subject().localityName = "Sacramento" req.get_subject().organizationName = "Yombo" req.get_subject( ).organizationalUnitName = "Gateway " + self.gateway_id[0:15] # Appends SAN to have "DNS:" if kwargs["sans"] is not None: san_string = [] for i in kwargs["sans"]: san_string.append(f"DNS: {i}") san_string = ", ".join(san_string) x509_extensions = [ crypto.X509Extension(b"subjectAltName", False, unicode_to_bytes(san_string)) ] req.add_extensions(x509_extensions) start = time() key = yield threads.deferToThread( self._generate_key, **{ "key_type": kwargs["key_type"], "key_size": kwargs["key_size"] }) duration = round(float(time()) - start, 4) self._Events.new("sslcerts", "generate_new", (args["sslname"], kwargs["cn"], san_string, duration)) req.set_pubkey(key) req.sign(key, "sha256") csr = crypto.dump_certificate_request(crypto.FILETYPE_PEM, req) key_file = crypto.dump_privatekey(crypto.FILETYPE_PEM, key) if kwargs["csr_file"] is not None: yield save_file(kwargs["csr_file"], csr) if kwargs["key_file"] is not None: yield save_file(kwargs["key_file"], key_file) return { "csr": csr, "csr_hash": sha256_compact(unicode_to_bytes(csr)), "key": key_file } @inlineCallbacks def _create_self_signed_cert(self): """ Creates a self signed cert. Shouldn't be called directly except by this library for its own use. """ logger.debug("Creating self signed cert.") req = crypto.X509() req.get_subject().CN = "localhost" req.get_subject().countryName = "US" req.get_subject().stateOrProvinceName = "California" req.get_subject().localityName = "Self Signed" req.get_subject().organizationName = "Yombo" req.get_subject( ).organizationalUnitName = f"Gateway {self.gateway_id[0:15]} Self Signed" req.set_serial_number(int(time())) req.gmtime_adj_notBefore(0) req.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60) self.self_signed_expires_at = time() + (10 * 365 * 24 * 60 * 60) self.self_signed_created_at = time() self._Configs.set("sslcerts", "self_signed_expires_at", self.self_signed_expires_at) self._Configs.set("sslcerts", "self_signed_created_at", self.self_signed_created_at) req.set_issuer(req.get_subject()) key = yield threads.deferToThread( self._generate_key, **{ "key_type": crypto.TYPE_RSA, "key_size": self.default_key_size }) req.set_pubkey(key) req.sign(key, "sha256") csr_key = crypto.dump_certificate(crypto.FILETYPE_PEM, req) key_file = crypto.dump_privatekey(crypto.FILETYPE_PEM, key) yield save_file(self.self_signed_cert_file, csr_key) yield save_file(self.self_signed_key_file, key_file) return {"csr_key": csr_key, "key": key_file} def _generate_key(self, **kwargs): """ This is a blocking function and should only be called by the sslcerts library. This is called in a seperate thread. Responsible for generating a key and csr. :return: """ # logger.debug("About to generate key: {kwargs}", kwargs=kwargs) key = crypto.PKey() key.generate_key(kwargs["key_type"], kwargs["key_size"]) return key def send_csr_request(self, csr_text, sslname): """ Submit CSR request to Yombo. The sslname is also sent to be used for tracking. This will be returned directly back to us. This allows us to get out signed cert back if we happen to restart between sending the CSR and getting the signed key back. :param csr_text: CSR request text :param sslname: Name of the ssl for tracking. :return: """ logger.info("Sending CSR request for cert: {sslname}", sslname=sslname) if len(sslname) > 100: raise YomboWarning("'sslname' too long, limit is 100 characters.") body = { "csr_text": csr_text, "sslname": sslname, } request_msg = self._AMQPYombo.generate_message_request( exchange_name="ysrv.e.gw_sslcerts", source="yombo.gateway.lib.amqpyobo", destination="yombo.server.sslcerts", request_type="csr_request", body=body, ) self._AMQPYombo.publish(**request_msg) return request_msg def amqp_incoming_request(self, headers, body, **kwargs): """ Signed SSL certs comes as requests, even though it's really a response. This avoids the requirement that all "responses" have a sent correlation ID. :param headers: :param body: :param kwargs: :return: """ # print(f"sslcerts: amqp_incoming: {body}") # print(f"sslcerts: amqp_incoming: {headers}") request_type = headers["request_type"] kwargs["headers"] = headers kwargs["body"] = body if request_type == "csr_response": self.amqp_incoming_response_to_csr_request(**kwargs) else: logger.warn( "AMQP:Handler:Control - Received unknown request_type: {request_type}", request_type=request_type) def amqp_incoming_response_to_csr_request(self, body=None, properties=None, correlation_info=None, **kwargs): """ Called when we get a signed cert back from a CSR. :param body: :param properties: :param correlation_info: :param kwargs: :return: """ logger.debug("Received CSR response message: {body}", body=body) if "sslname" not in body: logger.warn( "Discarding response, doesn't have an sslname attached." ) # can't raise exception due to AMPQ processing. return logger.info("Received a new signed SSL/TLS certificate for: {sslname}", sslname=body["sslname"]) sslname = bytes_to_unicode(body["sslname"]) if sslname not in self.managed_certs: logger.warn( "It doesn't appear we have a managed cert for the given SSL name. Lets store it for a few minutes: {sslname}", sslname=sslname) if sslname in self.received_message_for_unknown: self.received_message_for_unknown[sslname].append(body) else: self.received_message_for_unknown[sslname] = [body] else: self.managed_certs[sslname].amqp_incoming_response_to_csr_request( properties, body, correlation_info) def validate_csr_private_certs_match(self, csr_text, key_text): csr = crypto.load_certificate_request(crypto.FILETYPE_PEM, csr_text) key = crypto.load_privatekey(crypto.FILETYPE_PEM, key_text) return csr.verify(key)
def _init_(self, **kwargs): setup_webinterface_reference( self) # Sets a reference to this library in auth.py self.webapp = Klein() self.webapp.webinterface = self self.api_key = self._Configs.get("frontend.api_key", random_string(length=75)) self.frontend_building: bool = False self.web_interface_fully_started: bool = False self.enabled = self._Configs.get("webinterface.enabled", True) self.fqdn = self._Configs.get("dns.fqdn", None, False, instance=True) self.enabled = self._Configs.get("core.enabled", True) if not self.enabled: return self.file_cache = ExpiringDict( max_len=100, max_age_seconds=120 ) # used to load a few static files into memory that are commonly used. self.translators = {} self.idempotence = self._Cache.ttl(name="lib.webinterface.idempotence", ttl=300) self.wi_dir = "/lib/webinterface" self.misc_wi_data = {} self.wi_port_nonsecure = self._Configs.get( "webinterface.nonsecure_port", 8080, instance=True) self.wi_port_secure = self._Configs.get("webinterface.secure_port", 8443, instance=True) self.webapp.templates = jinja2.Environment( loader=jinja2.FileSystemLoader(f"{self._app_dir}/yombo"), extensions=["jinja2.ext.loopcontrols"]) self.setup_basic_filters() self.web_interface_listener = None self.web_interface_ssl_listener = None self.api_stream_spectators = { } # Tracks all the spectators connected. An alternative to MQTT listening. if self._Configs.get("webinterface.enable_default_routes", default=True, create=False): yield self.webinterface_load_routes() # Loads all the routes. self.npm_build_results = None self.temp_data = ExpiringDict(max_age_seconds=1800) self.web_server_started = False self.web_server_ssl_started = False self.setup_wizard_map_js = None self.web_factory = None self.user_login_tokens = self._Cache.ttl(name="lib.users.cache", ttl=300)
def _init_(self, **kwargs): self.frontend_building = False self.web_interface_fully_started = False self.enabled = self._Configs.get("webinterface", "enabled", True) self.fqdn = self._Configs.get2("dns", "fqdn", None, False) self.enabled = self._Configs.get("core", "enabled", True) if not self.enabled: return self.file_cache = ExpiringDict( max_len=100, max_age_seconds=120 ) # used to load a few static files into memory that are commonly used. self.translators = {} self.idempotence = self._Cache.ttl(name="lib.webinterface.idempotence", ttl=300) self.working_dir = self._Atoms.get("working_dir") self.app_dir = self._Atoms.get("app_dir") self.wi_dir = "/lib/webinterface" self.misc_wi_data = {} self.wi_port_nonsecure = self._Configs.get2("webinterface", "nonsecure_port", 8080) self.wi_port_secure = self._Configs.get2("webinterface", "secure_port", 8443) self.webapp.templates = jinja2.Environment( loader=jinja2.FileSystemLoader(f"{self.app_dir}/yombo"), extensions=["jinja2.ext.loopcontrols"]) self.setup_basic_filters() self.web_interface_listener = None self.web_interface_ssl_listener = None self.api_stream_spectators = {} # Load API routes route_api_v1_atoms(self.webapp) route_api_v1_automation_rules(self.webapp) # route_api_v1_camera(self.webapp) route_api_v1_debug(self.webapp) route_api_v1_device(self.webapp) route_api_v1_device_command(self.webapp) # route_api_v1_events(self.webapp) # route_api_v1_gateway(self.webapp) # route_api_v1_module(self.webapp) route_api_v1_mqtt(self.webapp) # route_api_v1_notification(self.webapp) route_api_v1_scenes(self.webapp) # route_api_v1_server(self.webapp) # route_api_v1_statistics(self.webapp) # route_api_v1_stream(self.webapp, self) route_api_v1_states(self.webapp) route_api_v1_system(self.webapp) # route_api_v1_storage(self.webapp) route_api_v1_user(self.webapp) # route_api_v1_webinterface_logs(self.webapp) # Load web server routes route_home(self.webapp) route_misc(self.webapp) route_system(self.webapp) route_user(self.webapp) if self.operating_mode != "run": from yombo.lib.webinterface.routes.restore import route_restore from yombo.lib.webinterface.routes.setup_wizard import route_setup_wizard route_setup_wizard(self.webapp) route_restore(self.webapp) self.npm_build_results = None self.temp_data = ExpiringDict(max_age_seconds=1800) self.web_server_started = False self.web_server_ssl_started = False self.web_factory = None self.user_login_tokens = self._Cache.ttl(name="lib.users.cache", ttl=300)
def _init_(self): """ Setups up the basic framework. """ # We only cache the last few events, and only for certain time. self.notifications = ExpiringDict(max_len=100, max_age_seconds=600)