示例#1
0
    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
示例#2
0
    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())
示例#3
0
    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
示例#4
0
    def _init_(self):
        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.session_validation_cache = ExpiringDict()

        if self.allow_system_session:
            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
        else:
            self.system_session = None
            self.system_login_key = None
示例#5
0
class YomboAPI(YomboLibrary):

    contentType = None

    def _init_(self):
        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.session_validation_cache = ExpiringDict()

        if self.allow_system_session:
            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
        else:
            self.system_session = None
            self.system_login_key = None

    def _load_(self):
        if self._Atoms['loader.operation_mode'] == 'run':
            self.init_defer = Deferred()
            self.validate_system_login()
            return self.init_defer

    def _start_(self):
        # print "system_session status: %s" % self.system_session
        # print "system_login_key status: %s" % self.system_login_key
        pass

    def _stop_(self):
        pass

    def _unload_(self):
        pass

    @inlineCallbacks
    def gateway_index(self, session=None):
        results = yield self.request("GET", "/v1/gateway", None, session)
        if results['code'] == 200:
            returnValue(results)
        elif results['code'] == 404:
            raise YomboWarning("Server cannot get gateways")
        else:
            if results['content']['message'] == "Invalid Token.":
                raise YomboWarningCredentails("URI: '%s' requires credentials." % results['content']['response']['uri'])
            raise YomboWarning("Unknown error: %s" % results['content'])

    @inlineCallbacks
    def gateway_get(self, gateway_id, session=None):
        results = yield self.request("GET", "/v1/gateway/%s" % gateway_id, None, session)
        if results['code'] == 200:
            returnValue(results)
        elif results['code'] == 404:
            raise YomboWarning("Server cannot find requested gateway: %s" % gateway_id)
        else:
            raise YomboWarning("Unknown error: %s" % results['content']['message'])

    @inlineCallbacks
    def gateway_put(self, gateway_id, values, session=None):
        results = yield self.request("PATCH", "/v1/gateway/%s" % gateway_id, values, session)
        if results['code'] == 200:
            returnValue(results)
        elif results['code'] == 404:
            raise YomboWarning("Server cannot find requested gateway: %s" % gateway_id)
        else:
            raise YomboWarning("Unknown error: %s" % results['content']['message'])

    @inlineCallbacks
    def gateway__module_get(self, gateway_id, session=None):
        results = yield self.request("GET", "/v1/gateway/%s/modules" % gateway_id, None, session)
        if results['code'] == 200:
            returnValue(results)
        elif results['code'] == 404:
            raise YomboWarning("Server cannot find requested gateway: %s" % gateway_id)
        else:
            raise YomboWarning("Unknown error: %s" % results['content']['message'])

    @inlineCallbacks
    def gateway__module_put(self, gateway_id, values, session=None):
        results = yield self.request("PATCH", "/v1/gateway/%s/modules" % gateway_id, values, session)
        if results['code'] == 200:
            returnValue(results)
        elif results['code'] == 404:
            raise YomboWarning("Server cannot find requested gateway: %s" % gateway_id)
        else:
            raise YomboWarning("Unknown error: %s" % results['content']['message'])

    @inlineCallbacks
    def gateway_config_index(self, gateway_id, session=None):
        results = yield self.request("GET", "/v1/gateway/%s/config" % gateway_id, None, session)
        if results['code'] == 200:
            returnValue(results)
        elif results['code'] == 404:
            raise YomboWarning("Server cannot get gateways")
        else:
            raise YomboWarning("Unknown error: %s" % results['content']['message'])

    # Below are the core help functions

    def save_system_session(self, session):
        print "api save_system_session0: %s" % session
        self.system_session = session
        print "api save_system_session1: %s" % session
        self._Configs.set('yomboapi', 'auth_session', session)  # to be encrypted with gpg later
        print "api save_system_session2: %s" % session

    def save_system_login_key(self, login_key):
        print "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@api save_system_login_key: %s" % login_key
        self.system_login_key = login_key
        print "api save_system_login_key1: %s" % login_key
        self._Configs.set('yomboapi', 'login_key', login_key)  # to be encrypted with gpg later
        print "api save_system_login_key2: %s" % login_key

    def select_session(self, session_id=None, session_key=None):
        if session_id is None or session_key is None:
            if self.allow_system_session:
                return self.system_session, self.system_login_key

        logger.info("select_session: Yombo API has no session data for 'selection_session'")
        return None, None

    def clear_session_cache(self, session=None):
        if (session is None):
            self.session_validation_cache.clear()
        else:
            hashed = sha1(session)
            if hashed in self.session_validation_cache:
                del self.session_validation_cache[hashed]  # None works too...

    @inlineCallbacks
    def validate_system_login(self):
        """
        Validates a system session if it exists. If not, it tries the login_key and creates a new session.

        :return:
        """
        if self.allow_system_session is False:
            self._States.set('yomboapi.valid_system_session', False)
            self.init_defer.callback(10)
            returnValue(False)

        if self.system_session is None and self.system_login_key is None:
            print "validate_system_login: self.system_session: %s" % self.system_session
            print "validate_system_login: self.system_login_key: %s" % self.system_login_key
            logger.warn("No saved system session information and no login_key. Disabling automated system changes.")
            self._States.set('yomboapi.valid_system_session', False)
            self.valid_system_session = False
            if self.init_defer is not None:
                self.init_defer.callback(10)
            returnValue(None)

        self.clear_session_cache()
        if self.system_session is not None:
            results = yield self.do_validate_session(self.system_session)
            if (results is True):
                print "has a system session!"
                self._States.set('yomboapi.valid_system_session', True)
                self.valid_system_session = True
                self.init_defer.callback(10)
                returnValue(True)

        if self.system_login_key is not None:
            results = yield self.user_login_with_key(self.system_login_key)
            print "reslts: %s" % results
            if (results is not False):
                print "has a system login key!"
                self._Configs.set('yomboapi', 'auth_session', results['session'])  # to be encrypted with gpg later
                self.system_session = results['session']
                self._States.set('yomboapi.valid_system_session', True)
                self.valid_system_session = True
                self.init_defer.callback(10)
                returnValue(True)

        print "API system has some data, but it's invalid!"
        self._States.set('yomboapi.valid_system_session', False)
        self.valid_system_session = False
        self.init_defer.callback(10)
        returnValue(False)

    @inlineCallbacks
    def validate_session(self, session_id=None, session_key=None, clear_cache=False):
        session_id, session_key = self.select_session(session_id, session_key)
        if session_id is None or session_key is None:
            logger.debug("Yombo API session information is not valid: {id}:{key}", id=session_id, key=session_key)

        hashed = sha1(session_id + session_key)
        if hashed in self.session_validation_cache:
            if clear_cache is True:
                del self.session_validation_cache[hashed]
            else:
                returnValue(self.session_validation_cache[hashed])

        results = yield self.do_validate_session(session_id, session_key)
        self.session_validation_cache[hashed] = results
        returnValue(results)

    @inlineCallbacks
    def do_validate_session(self, session):
        try:
            results = yield self.request("GET", "/v1/user/session/validate", None, session)
        except Exception, e:
            logger.debug("$$$1 API Errror: {error}", error=e)
            returnValue(False)


        logger.debug("$$$a REsults from API: {results}", results=results['content'])
# waiting on final API.yombo.com to complete this.  If we get something, we are good for now.

        if (results['content']['code'] != 200):
            returnValue(False)
        elif (results['content']['response']['session_status'] == 'valid'):
            returnValue(True)
        else:
            returnValue(False)
示例#6
0
 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)
示例#7
0
class LogEvents(YomboLibrary):
    """
    Manages all notifications.

    """
    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)
        # return self.init_deferred

    def _load_(self):
        self._LocalDB = self._Libraries['localdb']
        self._checkExpiredLoop = LoopingCall(self.check_expired)
        self._checkExpiredLoop.start(self._Configs.get('notifications', 'check_expired', 30, False))
        self.load_notifications()


    def _stop_(self):
        if self.init_deferred is not None and self.init_deferred.called is False:
            self.init_deferred.callback(1)  # if we don't check for this, we can't stop!

    def _clear_(self):
        """
        Clear all devices. Should only be called by the loader module
        during a reconfiguration event. B{Do not call this function!}
        """
        self.notifications.clear()

    def _reload_(self):
        self._clear_()
        self.load_notifications()

    def check_expired(self):
        """
        Called by looping call to periodically purge expired notifications.
        :return:
        """
        cur_time = int(time())
        for id, notice in self.notifications.iteritems():
            print "cur : expired = %s : %s" % (cur_time, notice.expire)
            if cur_time > notice.expire:
                print "deleting notice: %s" % notice.title
                del self.notifications[id]
        self._LocalDB.delete_expired_notifications()

    def get(self, notification_requested):
        """
        Performs the actual search.

        .. note::

           Modules shouldn't use this function. Use the built in reference to
           find notification: `self._Notifications['8w3h4sa']`

        :raises YomboWarning: Raised when notifcation cannot be found.
        :param notification_requested: The input type ID or input type label to search for.
        :type notification_requested: string
        :return: A dict containing details about the notification
        :rtype: dict
        """
        if notification_requested in self.notifications:
            return self.notifications[notification_requested]
        else:
            raise YomboWarning('Notification not found: %s' % notification_requested)

    def delete(self, notification_requested):
        """
        Deletes a provided notification.

        :param notification_requested:
        :return:
        """
        try:
            del self.notifications[notification_requested]
        except:
            pass
        self._LocalDB.delete_notification(notification_requested)

    @inlineCallbacks
    def load_notifications(self):
        """
        Load the last few notifications into memory.
        """
        notifications = yield self._LocalDB.get_notifications()
        for notice in notifications:
            notice = notice.__dict__
            if notice['expire'] < int(time()):
                continue
            notice['meta'] = json.loads(notice['meta'])
            self.add_notice(notice, from_db=True)
        logger.debug("Done load_notifications: {notifications}", notifications=self.notifications)
        # self.init_deferred.callback(10)

    def add_notice(self, notice, from_db=False, persist=True, create_event=False):
        """
        Add a new notice.

        :param notice: A dictionary containing notification details.
        :type record: dict
        :returns: Pointer to new notice. Only used during unittest
        """
        print "adding notice1: %s" % notice
        if 'id' not in notice:
            notice['id'] = random_string(length=16)
        if 'type' not in notice:
            notice['type'] = 'system'
        if 'priority' not in notice:
            notice['priority'] = 'normal'
        if 'source' not in notice:
            notice['source'] = ''

        if 'expire' not in notice:
            if 'timeout' in notice:
                notice['expire'] = int(time()) + notice['timeout']
            else:
                notice['expire'] = int(time()) + 3600
        else:
            if notice['expire'] > int(time()):
                YomboWarning("New notification is set to expire before current time.")
        if 'created' not in notice:
            notice['created'] = int(time())

        if 'acknowledged' not in notice:
            notice['acknowledged'] = False
        else:
            if notice['acknowledged'] not in (True, False):
                YomboWarning("New notification 'acknowledged' must be either True or False.")

        if 'title' not in notice:
            raise YomboWarning("New notification requires a title.")
        if 'message' not in notice:
            raise YomboWarning("New notification requires a message.")
        if 'meta' not in notice:
            notice['meta'] = {}

        logger.debug("notice: {notice}", notice=notice)
        if from_db is False:
            self._LocalDB.add_notification(notice)
            self.notifications.prepend(notice['id'], Notification(notice))
        else:
            self.notifications[notice['id']] = Notification(notice)
            # self.notifications = OrderedDict(sorted(self.notifications.iteritems(), key=lambda x: x[1]['created']))
            pass
        return notice['id']
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)
示例#9
0
    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)
示例#11
0
 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)
示例#12
0
class LogEvents(YomboLibrary):
    """
    Manages all notifications.

    """
    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)
        # return self.init_deferred

    def _load_(self):
        self._LocalDB = self._Libraries['localdb']
        self._checkExpiredLoop = LoopingCall(self.check_expired)
        self._checkExpiredLoop.start(
            self._Configs.get('notifications', 'check_expired', 30, False))
        self.load_notifications()

    def _stop_(self):
        if self.init_deferred is not None and self.init_deferred.called is False:
            self.init_deferred.callback(
                1)  # if we don't check for this, we can't stop!

    def _clear_(self):
        """
        Clear all devices. Should only be called by the loader module
        during a reconfiguration event. B{Do not call this function!}
        """
        self.notifications.clear()

    def _reload_(self):
        self._clear_()
        self.load_notifications()

    def check_expired(self):
        """
        Called by looping call to periodically purge expired notifications.
        :return:
        """
        cur_time = int(time())
        for id, notice in self.notifications.items():
            print("cur : expired = %s : %s" % (cur_time, notice.expire))
            if cur_time > notice.expire:
                print("deleting notice: %s" % notice.title)
                del self.notifications[id]
        self._LocalDB.delete_expired_notifications()

    def get(self, notification_requested):
        """
        Performs the actual search.

        .. note::

           Modules shouldn't use this function. Use the built in reference to
           find notification: `self._Notifications['8w3h4sa']`

        :raises YomboWarning: Raised when notifcation cannot be found.
        :param notification_requested: The input type ID or input type label to search for.
        :type notification_requested: string
        :return: A dict containing details about the notification
        :rtype: dict
        """
        if notification_requested in self.notifications:
            return self.notifications[notification_requested]
        else:
            raise YomboWarning('Notification not found: %s' %
                               notification_requested)

    def delete(self, notification_requested):
        """
        Deletes a provided notification.

        :param notification_requested:
        :return:
        """
        try:
            del self.notifications[notification_requested]
        except:
            pass
        self._LocalDB.delete_notification(notification_requested)

    @inlineCallbacks
    def load_notifications(self):
        """
        Load the last few notifications into memory.
        """
        notifications = yield self._LocalDB.get_notifications()
        for notice in notifications:
            notice = notice.__dict__
            if notice['expire'] < int(time()):
                continue
            notice['meta'] = json.loads(notice['meta'])
            self.add_notice(notice, from_db=True)
        logger.debug("Done load_notifications: {notifications}",
                     notifications=self.notifications)
        # self.init_deferred.callback(10)

    def add_notice(self,
                   notice,
                   from_db=False,
                   persist=True,
                   create_event=False):
        """
        Add a new notice.

        :param notice: A dictionary containing notification details.
        :type record: dict
        :returns: Pointer to new notice. Only used during unittest
        """
        print("adding notice1: %s" % notice)
        if 'id' not in notice:
            notice['id'] = random_string(length=16)
        if 'type' not in notice:
            notice['type'] = 'system'
        if 'priority' not in notice:
            notice['priority'] = 'normal'
        if 'source' not in notice:
            notice['source'] = ''

        if 'expire' not in notice:
            if 'timeout' in notice:
                notice['expire'] = int(time()) + notice['timeout']
            else:
                notice['expire'] = int(time()) + 3600
        else:
            if notice['expire'] > int(time()):
                YomboWarning(
                    "New notification is set to expire before current time.")
        if 'created_at' not in notice:
            notice['created_at'] = int(time())

        if 'acknowledged' not in notice:
            notice['acknowledged'] = False
        else:
            if notice['acknowledged'] not in (True, False):
                YomboWarning(
                    "New notification 'acknowledged' must be either True or False."
                )

        if 'title' not in notice:
            raise YomboWarning("New notification requires a title.")
        if 'message' not in notice:
            raise YomboWarning("New notification requires a message.")
        if 'meta' not in notice:
            notice['meta'] = {}

        logger.debug("notice: {notice}", notice=notice)
        if from_db is False:
            self._LocalDB.add_notification(notice)
            self.notifications.prepend(notice['id'], Notification(notice))
        else:
            self.notifications[notice['id']] = Notification(notice)
            # self.notifications = OrderedDict(sorted(self.notifications.items(), key=lambda x: x[1]['created_at']))
            pass
        return notice['id']
示例#13
0
class YomboAPI(YomboLibrary):

    # contentType = None

    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

    @inlineCallbacks
    def gateway_index(self, session=None):
        results = yield self.request("GET", "/v1/gateway", None, session)
        if results['code'] == 200:
            returnValue(results)
        elif results['code'] == 404:
            raise YomboWarning("Server cannot get gateways")
        else:
            if results['content']['message'] == "Invalid Token.":
                raise YomboWarningCredentails(
                    "URI: '%s' requires credentials." %
                    results['content']['response']['uri'])
            raise YomboWarning("Unknown error: %s" % results['content'])

    @inlineCallbacks
    def gateway_get(self, gateway_id, session=None):
        results = yield self.request("GET", "/v1/gateway/%s" % gateway_id,
                                     None, session)
        if results['code'] == 200:
            returnValue(results)
        elif results['code'] == 404:
            raise YomboWarning("Server cannot find requested gateway: %s" %
                               gateway_id)
        else:
            raise YomboWarning("Unknown error: %s" %
                               results['content']['message'])

    @inlineCallbacks
    def gateway_put(self, gateway_id, values, session=None):
        results = yield self.request("PATCH", "/v1/gateway/%s" % gateway_id,
                                     values, session)
        if results['code'] == 200:
            returnValue(results)
        elif results['code'] == 404:
            raise YomboWarning("Server cannot find requested gateway: %s" %
                               gateway_id)
        else:
            raise YomboWarning("Unknown error: %s" %
                               results['content']['message'])

    @inlineCallbacks
    def gateway__module_get(self, gateway_id, session=None):
        results = yield self.request("GET",
                                     "/v1/gateway/%s/modules" % gateway_id,
                                     None, session)
        if results['code'] == 200:
            returnValue(results)
        elif results['code'] == 404:
            raise YomboWarning("Server cannot find requested gateway: %s" %
                               gateway_id)
        else:
            raise YomboWarning("Unknown error: %s" %
                               results['content']['message'])

    @inlineCallbacks
    def gateway__module_put(self, gateway_id, values, session=None):
        results = yield self.request("PUT",
                                     "/v1/gateway/%s/modules" % gateway_id,
                                     values, session)
        if results['code'] == 200:
            returnValue(results)
        elif results['code'] == 404:
            raise YomboWarning("Server cannot find requested gateway: %s" %
                               gateway_id)
        else:
            raise YomboWarning("Unknown error: %s" %
                               results['content']['message'])

    @inlineCallbacks
    def gateway_config_index(self, gateway_id, session=None):
        results = yield self.request("GET",
                                     "/v1/gateway/%s/config" % gateway_id,
                                     None, session)
        if results['code'] == 200:
            returnValue(results)
        elif results['code'] == 404:
            raise YomboWarning("Server cannot get gateways")
        else:
            raise YomboWarning("Unknown error: %s" %
                               results['content']['message'])

    # Below are the core help functions

    def save_system_session(self, session):
        self.system_session = session
        self._Configs.set('yomboapi', 'auth_session',
                          session)  # to be encrypted with gpg later

    def save_system_login_key(self, login_key):
        self.system_login_key = login_key
        self._Configs.set('yomboapi', 'login_key',
                          login_key)  # to be encrypted with gpg later

    def select_session(self, session_id=None, session_key=None):
        if session_id is None or session_key is None:
            if self.allow_system_session:
                return self.system_session, self.system_login_key

        logger.info(
            "select_session: Yombo API has no session data for 'selection_session'"
        )
        return None, None

    def clear_session_cache(self, session=None):
        if (session is None):
            self.session_validation_cache.clear()
        else:
            hashed = sha1(session)
            if hashed in self.session_validation_cache:
                del self.session_validation_cache[hashed]  # None works too...

    @inlineCallbacks
    def validate_system_login(self):
        """
        Validates that the system has a valid user login key and an active system session.

        If the system session is invalid or expired, it will attempt to automatically createa  new session with
        the systemt he login key.

        If the system login key is invalid, the system will exit.

        :return:
        """
        if self.allow_system_session is False:
            self._States.set('yomboapi.valid_system_session', False)
            self.valid_system_session = False
            self._States.set('yomboapi.valid_login_key', False)
            self.valid_login_key = False
            if self.init_defer is not None:
                self.init_defer.callback(10)
            returnValue(False)

        if self.system_session is None and self.system_login_key is None:
            logger.warn(
                "No saved system session information and no login_key. Disabling automated system changes."
            )
            self._States.set('yomboapi.valid_system_session', False)
            self.valid_system_session = False
            self._States.set('yomboapi.valid_login_key', False)
            self.valid_login_key = False
            if self.init_defer is not None:
                self.init_defer.callback(10)
            returnValue(False)

        if self.system_login_key is None:
            logger.warn("System doesn't have a login key!")
        else:
            results = yield self.do_validate_login_key(self.system_login_key)
            if results is True:
                logger.debug("System has a valid login key.")
                self._States.set('yomboapi.valid_login_key', True)
                self.valid_login_key = True
            else:
                logger.warn("System has an invalid login key.")
                self._States.set('yomboapi.valid_login_key', False)
                self.valid_login_key = False

        self.clear_session_cache()
        results = yield self.do_validate_session(self.system_session)
        if results is True:
            logger.debug("Yombo API has a system session!")
            self._States.set('yomboapi.valid_system_session', True)
            self.valid_system_session = True
            if self.init_defer is not None:
                self.init_defer.callback(10)
            returnValue(True)
        else:  # if invalid, try to get one with the login key!
            if self.valid_login_key:
                results = yield self.user_login_with_key(self.system_login_key)
                if results is not False:
                    self._Configs.set(
                        'yomboapi', 'auth_session',
                        results['session'])  # to be encrypted with gpg later
                    self.system_session = results['session']
                    self._States.set('yomboapi.valid_system_session', True)
                    self.valid_system_session = True
                    if self.init_defer is not None:
                        self.init_defer.callback(10)
                    returnValue(True)

        logger.warn("Yombo API does not have a login system session!")
        self._States.set('yomboapi.valid_system_session', False)
        self.valid_system_session = False

        if self.init_defer is not None:
            self.init_defer.callback(10)
        returnValue(False)

    @inlineCallbacks
    def validate_session(self,
                         session_id=None,
                         session_key=None,
                         clear_cache=False):
        session_id, session_key = self.select_session(session_id, session_key)
        if session_id is None or session_key is None:
            logger.debug(
                "Yombo API session information is not valid: {id}:{key}",
                id=session_id,
                key=session_key)

        hashed = sha1(session_id + session_key)
        if hashed in self.session_validation_cache:
            if clear_cache is True:
                del self.session_validation_cache[hashed]
            else:
                returnValue(self.session_validation_cache[hashed])

        results = yield self.do_validate_session(session_id, session_key)
        self.session_validation_cache[hashed] = results
        returnValue(results)

    @inlineCallbacks
    def do_validate_login_key(self, login_key):
        try:
            results = yield self.request(
                "GET", "/v1/user/login_key/validate/%s" % login_key)
        except Exception as e:
            logger.info("do_validate_login_key API Errror: {error}", error=e)
            returnValue(False)

        logger.debug("Login key results: REsults from API: {results}",
                     results=results['content'])
        # waiting on final API.yombo.com to complete this.  If we get something, we are good for now.

        if (results['content']['code'] != 200):
            returnValue(False)
        else:
            returnValue(results['content']['response']['login'])

    @inlineCallbacks
    def do_validate_session(self, session):
        try:
            results = yield self.request("GET",
                                         "/v1/user/session/validate",
                                         None,
                                         session=session)
        except Exception as e:
            logger.debug("$$$1 API Errror: {error}", error=e)
            returnValue(False)

        logger.debug("$$$a REsults from API: {results}",
                     results=results['content'])
        # waiting on final API.yombo.com to complete this.  If we get something, we are good for now.

        if (results['content']['code'] != 200):
            returnValue(False)
        else:
            returnValue(results['content']['response']['login'])

    @inlineCallbacks
    def user_login_with_key(self, login_key):
        results = yield self.request("POST", "/v1/user/login",
                                     {'login_key': login_key}, False)
        try:
            results = yield self.request("POST", "/v1/user/login",
                                         {'login_key': login_key}, False)
        except Exception as e:
            logger.debug("$$$2 API Errror: {error}", error=e)
            returnValue(False)

        logger.info(
            "user_login_with_key Results from API for login w key: {results}",
            results=results['content'])
        # waiting on final API.yombo.com to complete this.  If we get something, we are good for now.

        if (results['content']['code'] != 200):
            returnValue(False)
        elif (results['content']['message'] == 'Logged in'):
            returnValue(results['content']['response']['login'])
        else:
            returnValue(False)

    @inlineCallbacks
    def user_login_with_credentials(self, username, password,
                                    g_recaptcha_response):
        # credentials = { 'username':username, 'password':password}
        results = yield self.request(
            "POST", "/v1/user/login", {
                'username': username,
                'password': password,
                'g-recaptcha-response': g_recaptcha_response
            }, False)
        logger.info("$$$3 REsults from API login creds: {results}",
                    results=results)

        return results

    @inlineCallbacks
    def gateways(self, session_info=None):
        results = yield self.request("GET", "/v1/gateway")
        logger.debug("$$$4 REsults from API: {results}", results=results)

        if results['Code'] == 200:  # life is good!
            returnValue(results['Response']['Gateway'])
        else:
            returnValue(False)

    def make_headers(self, session):
        headers = {
            'Content-Type': self.contentType,
            'Authorization': 'Yombo-Gateway-v1',
            'x-api-key': self.api_key,
            'User-Agent': 'yombo-gateway-v0_12_0',
        }
        if session is not None:
            headers['Authorization'] = 'Bearer %s' % session

        # for k, v in headers.items():
        #     headers[k] = v.encode('utf-8')
        return headers

    def errorHandler(self, result):
        raise YomboWarning("Problem with request: %s" % result)

    @inlineCallbacks
    def request(self, method, path, data=None, session=None):
        path = self.base_url + path

        logger.debug("{method}: {path}", method=method, path=path)
        # if session is False:
        #     session = None
        if session is None:
            if self.system_session is None:
                if self.valid_system_session is False:
                    raise YomboWarningCredentails(
                        "Yombo request needs an API session.")
            session = self.system_session
        if session is False:
            session = None
        results = None
        headers = self.make_headers(session)

        if data is not None:
            data = json.dumps(data).encode()
        logger.debug("yombo api request data: {data}", data=data)

        if method == 'GET':
            results = yield self._get(path, headers, data)
        elif method == 'POST':
            results = yield self._post(path, headers, data)
        elif method == 'PATCH':
            results = yield self._patch(path, headers, data)
        elif method == 'PUT':
            results = yield self._put(path, headers, data)
        elif method == 'DELETE':
            results = yield self._delete(path, headers, data)
        else:
            raise Exception("Bad request type?? %s: %s" % (method, path))

        returnValue(results)

    @inlineCallbacks
    def _get(self, path, headers, args=None):
        path = path
        # response = yield treq.get(path, params=args, agent=self.custom_agent, headers=headers)
        response = yield treq.get(path, headers=headers, params=args)
        content = yield treq.content(response)
        # logger.debug("getting URL: {path}  headers: {headers}", path=path, agent=self.custom_agent, headers=headers)
        final_response = self.decode_results(content,
                                             self.response_headers(response),
                                             response.code, response.phrase)
        returnValue(final_response)

    @inlineCallbacks
    def _patch(self, path, headers, data):
        print("yapi patch called. path: %s... headers: %s... data: %s" %
              (path, headers, data))
        response = yield treq.patch(path,
                                    data=data,
                                    agent=self.custom_agent,
                                    headers=headers)
        content = yield treq.content(response)
        final_response = self.decode_results(content,
                                             self.response_headers(response),
                                             response.code, response.phrase)
        returnValue(final_response)

    @inlineCallbacks
    def _post(self, path, headers, data):
        print("yapi post called. path: %s... headers: %s... data: %s" %
              (path, headers, data))

        response = yield treq.post(path,
                                   data=data,
                                   agent=self.custom_agent,
                                   headers=headers)
        content = yield treq.content(response)
        final_response = self.decode_results(content,
                                             self.response_headers(response),
                                             response.code, response.phrase)
        print("dddd: %s" % final_response)
        returnValue(final_response)

    @inlineCallbacks
    def _put(self, path, headers, data):
        response = yield treq.put(path,
                                  data=data,
                                  agent=self.custom_agent,
                                  headers=headers)
        content = yield treq.content(response)
        final_response = self.decode_results(content,
                                             self.response_headers(response),
                                             response.code, response.phrase)
        returnValue(final_response)

    @inlineCallbacks
    def _delete(self, path, headers, args={}):
        response = yield treq.delete(path,
                                     params=args,
                                     agent=self.custom_agent,
                                     headers=headers)
        content = yield treq.content(response)
        final_response = self.decode_results(content,
                                             self.response_headers(response),
                                             response.code, response.phrase)
        returnValue(final_response)

    #
    # def __encode(self, data):
    #     return json.dumps(data)

    def response_headers(self, response):
        data = {}
        raw_headers = bytes_to_unicode(response.headers._rawHeaders)
        for key, value in raw_headers.items():
            data[key.lower()] = value
        return data

    def decode_results(self, content, headers, code, phrase):
        # print("decode_results headers: %s" % headers)

        content_type = headers['content-type'][0]

        # print( "######  content: %s" % content)
        if content_type == 'application/json':
            try:
                content = json.loads(content)
                content_type = "dict"
            except Exception:
                raise YomboWarning(
                    "Receive yombo api response reported json, but isn't: %s" %
                    content)
        elif content_type == 'application/msgpack':
            try:
                content = msgpack.loads(content)
                content_type = "dict"
            except Exception:
                raise YomboWarning(
                    "Receive yombo api response reported msgpack, but isn't.")
        else:
            try:
                content = json.loads(content)
                content_type = "dict"
            except Exception:
                try:
                    content = msgpack.loads(content)
                    content_type = "dict"
                except Exception:
                    content_type = "string"

        results = {
            'content': content,
            'content_type': content_type,
            'code': code,
            'phrase': phrase,
            'headers': headers,
        }
        if content_type == "string":
            results['code'] = 500
            results['data'] = []
            results['content'] = {
                'message': 'Unknown api error',
                'html_message': 'Unknown api error',
            }
            print("Error content: %s" % content)
            return results
        else:
            if 'response' in content:
                if 'locator' in content['response']:
                    results['data'] = content['response'][content['response']
                                                          ['locator']]
                else:
                    results['data'] = []
            return results
示例#14
0
class YomboAPI(YomboLibrary):
    @property
    def valid_login_key(self):
        return self._States.get('yomboapi.valid_login_key', False)

    @valid_login_key.setter
    def valid_login_key(self, val):
        return self._States.set('yomboapi.valid_login_key', val)

    @property
    def valid_system_session(self):
        return self._States.get('yomboapi.valid_system_session', False)

    @valid_system_session.setter
    def valid_system_session(self, val):
        return self._States.set('yomboapi.valid_system_session', val)

    def __str__(self):
        """
        Returns the name of the library.
        :return: Name of the library
        :rtype: string
        """
        return "Yombo Yombo API library"

    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()

        self.system_login_key = self._Configs.get(
            'yomboapi', 'login_key', None)  # to be encrypted with gpg later
        self.system_session = self._Configs.get(
            'yomboapi', 'auth_session', None)  # to be encrypted with gpg later

        if self._Loader.operating_mode == 'run':
            self.init_defer = Deferred()
            self.validate_system_login()
            return self.init_defer

    def save_system_session(self, session):
        self.system_session = session
        self._Configs.set('yomboapi', 'auth_session',
                          session)  # to be encrypted with gpg later

    def save_system_login_key(self, login_key):
        self.system_login_key = login_key
        self._Configs.set('yomboapi', 'login_key',
                          login_key)  # to be encrypted with gpg later

    def select_session(self, session_id=None, session_key=None):
        if session_id is None or session_key is None:
            if self.allow_system_session:
                return self.system_session, self.system_login_key

        logger.info(
            "select_session: Yombo API has no session data for 'selection_session'"
        )
        return None, None

    def clear_session_cache(self, session=None):
        if session is None:
            self.session_validation_cache.clear()
        else:
            hashed = sha224(session)
            if hashed in self.session_validation_cache:
                del self.session_validation_cache[hashed]  # None works too...

    @inlineCallbacks
    def validate_system_login(self):
        """
        Validates that the system has a valid user login key and an active system session.

        If the system session is invalid or expired, it will attempt to automatically createa  new session with
        the systemt he login key.

        If the system login key is invalid, the system will exit.

        :return:
        """
        # print("!!!!!!!!!!!  starting ytombo api validation")
        if self.allow_system_session is False:
            self.valid_system_session = False
            self.valid_login_key = False
            if self.init_defer is not None:
                self.init_defer.callback(10)
            logger.info(
                "System session has been disabled. Won't be able to update Yombo cloud settings."
            )
            return False

        if self.system_session is None and self.system_login_key is None:
            logger.info(
                "No saved system session information and no login_key. Won't be able to update Yombo cloud settings."
            )
            self.valid_system_session = False
            self.valid_login_key = False
            if self.init_defer is not None:
                self.init_defer.callback(10)
            return False

        if self.system_login_key is None:
            logger.warn("System doesn't have a login key!")
        else:
            results = yield self.do_validate_login_key(self.system_login_key)
            if results is True:
                logger.debug("System has a valid login key.")
                self.valid_login_key = True
            else:
                logger.warn("System has an invalid login key.")
                self.valid_login_key = False

        if self.valid_login_key is None:
            logger.warn(
                "Cannot get system session token, no login token exists.!")
        else:
            if self.system_session is not None:
                results = yield self.do_validate_session(self.system_session)
            else:
                results = False
            if results is True:
                logger.debug("System has a valid session token.")
                self.valid_system_session = True
                # self._Configs.set('yomboapi', 'auth_session', self.system_session)  # to be encrypted with gpg later
            else:
                if self.valid_login_key is True:
                    new_session = yield self.user_login_with_key(
                        self.system_login_key)
                    if new_session is False:
                        self.valid_system_session = False
                        logger.warn("System has an invalid session token.")
                    else:
                        self._Configs.set('yomboapi', 'auth_session',
                                          new_session['session']
                                          )  # to be encrypted with gpg later
                        self.valid_system_session = new_session['session']
                        self.valid_system_session = True

        if self.init_defer is not None:
            self.init_defer.callback(10)

    @inlineCallbacks
    def do_validate_login_key(self, login_key):
        try:
            results = yield self.request("POST", "/v1/user/login_key/validate",
                                         {'login_key': login_key})
        except Exception as e:
            logger.debug("do_validate_login_key API Errror: {error}", error=e)
            return False

        # logger.debug("Login key results: REsults from API: {results}", results=results['content'])
        # waiting on final API.yombo.com to complete this.  If we get something, we are good for now.

        if (results['content']['code'] != 200):
            return False
        else:
            return results['content']['response']['login']

    @inlineCallbacks
    def do_validate_session(self, session):
        try:
            results = yield self.request("POST", "/v1/user/session/validate",
                                         {'session': session})
            # results = yield self.request("GET", "/v1/user/session/validate", None, session=session)
        except Exception as e:
            logger.debug("$$$1 API Errror: {error}", error=e)
            return False

        # logger.debug("$$$a REsults from API: {results}", results=results['content'])
        # waiting on final API.yombo.com to complete this.  If we get something, we are good for now.

        if (results['content']['code'] != 200):
            return False
        else:
            return results['content']['response']['login']

    @inlineCallbacks
    def user_login_with_key(self, login_key):
        try:
            results = yield self.request("POST", "/v1/user/login",
                                         {'login_key': login_key}, False)
        except Exception as e:
            logger.debug("$$$2 API Errror: {error}", error=e)
            return False

        # logger.info("user_login_with_key Results from API for login w key: {results}", results=results['content'])
        # waiting on final API.yombo.com to complete this.  If we get something, we are good for now.

        if results['content']['code'] != 200:
            return False
        elif results['content']['message'] != 'Logged in':
            return False
        else:
            self._capture_system_login(results['content']['response']['login'])
            return results['content']

    @inlineCallbacks
    def user_login_with_credentials(self, username, password,
                                    g_recaptcha_response):
        results = yield self.request(
            "POST", "/v1/user/login", {
                'username': username,
                'password': password,
                'g-recaptcha-response': g_recaptcha_response
            }, False)
        # except YomboWarning as e:
        #     results = {
        #         'status': 'failed',
        #         'msg': "Couldn't delete command: %s" % e.message,
        #         'apimsg': "Couldn't delete command: %s" % e.message,
        #         'apimsghtml': "Couldn't delete command: %s" % e.html_message,
        #     }
        #     return results
        # logger.info("$$$3 REsults from API login creds: {results}", results=results)

        #        if results['content']['code'] != 200:
        if results['content']['code'] != 200:
            return False
        elif results['content']['message'] != 'Logged in':
            return False
        else:
            self._capture_system_login(results['content']['response']['login'])
            return results['content']
            # return results['content']['response']['login']

    def _capture_system_login(self, data):
        if self.allow_system_session is False:
            return
        captured_id = data['user_id']
        try:
            owner_id = self._Configs.get("core", "owner_id")
        except KeyError as e:
            pass
        else:
            if captured_id == owner_id:
                self.save_system_login_key(data['login_key'])
                self.save_system_session(data['session'])
                self.valid_system_session = True
                self.valid_login_key = True

    @inlineCallbacks
    def gateways(self, session_info=None):
        results = yield self.request("GET", "/v1/gateway")
        logger.debug("$$$4 REsults from API: {results}", results=results)

        if results['Code'] == 200:  # life is good!
            return results['Response']['Gateway']
        else:
            return False

    def make_headers(self, session):
        headers = {
            'Content-Type': self.contentType,
            'Authorization': 'Yombo-Gateway-v1',
            'x-api-key': self.api_key,
            'User-Agent': 'yombo-gateway-v0_14_0',
        }
        if session is not None:
            headers['Authorization'] = 'Bearer %s' % session

        return headers

    def errorHandler(self, result):
        raise YomboWarning("Problem with request: %s" % result)

    @inlineCallbacks
    def request(self, method, path, data=None, session=None):
        path = self.base_url + path

        logger.info("{method}: {path}: {data}",
                    method=method,
                    path=path,
                    data=data)
        # if session is False:
        #     session = None
        if session is None:
            if self.system_session is None:
                if self.valid_system_session is False:
                    raise YomboWarningCredentails(
                        "Yombo request needs an API session.")
            session = self.system_session
        if session is False:
            session = None
        results = None
        headers = self.make_headers(session)

        if data is not None:
            data = json.dumps(data).encode()
        logger.debug("yombo api request data: {data}", data=data)

        if method == 'GET':
            results = yield self._get(path, headers, data)
        elif method == 'POST':
            results = yield self._post(path, headers, data)
        elif method == 'PATCH':
            results = yield self._patch(path, headers, data)
        elif method == 'PUT':
            results = yield self._put(path, headers, data)
        elif method == 'DELETE':
            results = yield self._delete(path, headers, data)
        else:
            raise Exception("Bad request type?? %s: %s" % (method, path))

        return results

    @inlineCallbacks
    def _get(self, path, headers, args=None):
        path = path
        # response = yield treq.get(path, params=args, agent=self.custom_agent, headers=headers)
        response = yield treq.get(path, headers=headers, params=args)
        content = yield treq.content(response)
        # logger.debug("getting URL: {path}  headers: {headers}", path=path, agent=self.custom_agent, headers=headers)
        final_response = self.decode_results(content,
                                             self.response_headers(response),
                                             response.code, response.phrase)
        return final_response

    @inlineCallbacks
    def _patch(self, path, headers, data):
        # print("yapi patch called. path: %s... headers: %s... data: %s" % (path, headers, data))
        response = yield treq.patch(path,
                                    data=data,
                                    agent=self.custom_agent,
                                    headers=headers)
        content = yield treq.content(response)
        final_response = self.decode_results(content,
                                             self.response_headers(response),
                                             response.code, response.phrase)
        return final_response

    @inlineCallbacks
    def _post(self, path, headers, data):
        # print("yapi post called. path: %s... headers: %s... data: %s" % (path, headers, data))

        response = yield treq.post(path,
                                   data=data,
                                   agent=self.custom_agent,
                                   headers=headers)
        content = yield treq.content(response)
        final_response = self.decode_results(content,
                                             self.response_headers(response),
                                             response.code, response.phrase)
        # print("dddd: %s" % final_response)
        return final_response

    @inlineCallbacks
    def _put(self, path, headers, data):
        response = yield treq.put(path,
                                  data=data,
                                  agent=self.custom_agent,
                                  headers=headers)
        content = yield treq.content(response)
        final_response = self.decode_results(content,
                                             self.response_headers(response),
                                             response.code, response.phrase)
        return final_response

    @inlineCallbacks
    def _delete(self, path, headers, args={}):
        response = yield treq.delete(path,
                                     params=args,
                                     agent=self.custom_agent,
                                     headers=headers)
        content = yield treq.content(response)
        final_response = self.decode_results(content,
                                             self.response_headers(response),
                                             response.code, response.phrase)
        return final_response

    def response_headers(self, response):
        data = {}
        raw_headers = bytes_to_unicode(response.headers._rawHeaders)
        for key, value in raw_headers.items():
            data[key.lower()] = value
        return data

    def decode_results(self, content, headers, code, phrase):
        # print("decode_results headers: %s" % headers)

        # print(content)
        content_type = headers['content-type'][0]
        phrase = bytes_to_unicode(phrase)

        # print( "######  content: %s" % content)
        if content_type == 'application/json':
            try:
                content = json.loads(content)
                content_type = "dict"
            except Exception:
                raise YomboWarning(
                    "Receive yombo api response reported json, but isn't: %s" %
                    content)
        elif content_type == 'application/msgpack':
            try:
                content = msgpack.loads(content)
                content_type = "dict"
            except Exception:
                raise YomboWarning(
                    "Receive yombo api response reported msgpack, but isn't.")
        else:
            try:
                content = json.loads(content)
                content_type = "dict"
            except Exception:
                try:
                    content = msgpack.loads(content)
                    content_type = "dict"
                except Exception:
                    content_type = "string"

        if code < 200:
            status = 'ok'
        else:
            status = 'error'
        results = {
            'status': status,
            'content': content,
            'content_type': content_type,
            'code': code,
            'phrase': phrase,
            'headers': headers,
        }

        if content_type == "string":
            logger.warn("Error content: {content}", content=content)
            raise YomboWarning('Unknown api error',
                               500,
                               html_message='Unknown api error')
        else:
            if 'response' in content:
                if 'locator' in content['response']:
                    results['data'] = content['response'][content['response']
                                                          ['locator']]
                else:
                    results['data'] = []

            # Check if there was any errors, if so, raise something.
            if code >= 300:
                # print("data: %s" % content)
                if 'message' in content:
                    message = content['message']
                else:
                    message = phrase
                if 'html_message' in content:
                    html_message = content['html_message']
                else:
                    html_message = phrase
                raise YomboWarning(message, code, html_message=html_message)
            return results