def authorize(self, request): logger.debug( "\n\n\n\n\n\t\t\t\t\t********************** AUTHORIZE **************************" ) logger.debug("Received {} - {}".format(request.method, request.path)) logger.verbose("headers: {}".format(request.headers)) try: received_hash = request.headers.get("Authorization", "").replace("Bearer ", "") if self._validate_confirmation_hash(received_hash): sender = { "channel_template_id": request.headers["X-Channeltemplate-Id"], "client_id": request.headers["X-Client-Id"], "owner_id": request.headers["X-Owner-Id"] } data = { "location": self.implementer.auth_requests(sender=sender) } return Response(response=json.dumps(data), status=200, mimetype="application/json") else: logger.debug("Provided invalid confirmation hash! {}".format( self.confirmation_hash)) return Response(status=403) except Exception: logger.error("Couldn't complete processing request, {}".format( traceback.format_exc(limit=5))) return Response(status=403)
def receive_token(self, request): logger.debug( "\n\n\n\n\n\t\t\t\t\t********************** RECEIVE_TOKEN **************************" ) logger.debug("Received {} - {}".format(request.method, request.path)) logger.verbose("headers: {}".format(request.headers)) try: received_hash = request.headers.get("Authorization").replace( "Bearer ", "") if self._validate_confirmation_hash(received_hash): if request.is_json: received_data = request.get_json() logger.debug(f'Authorize response data: {received_data}') else: return Response(status=422) return self.handle_receive_token( received_data, request.headers["X-Client-Id"], request.headers["X-Owner-Id"]) else: logger.debug("Provided invalid confirmation hash!") return Response(status=403) except Exception: logger.error("Couldn't complete processing request, {}".format( traceback.format_exc(limit=5)))
def validate_quote(quote_id): try: header = { "Authorization": f"Bearer {settings.block['access_token']}", "Accept": "application/json", } if not quote_id: logger.warning(f"[validate_quote] Invalid quote_id") return False url = f"{settings.api_server_full}/applications/{settings.client_id}/quotes/{quote_id}" resp = requests.get(url, headers=header) if int(resp.status_code) == 200: return resp.json() else: logger.verbose( f"[validate_quote] Received response code [{resp.status_code}]" ) raise InvalidRequestException( f"Failed to retrieve quote for {quote_id}") except (OSError, InvalidRequestException) as e: logger.warning( f'[validate_quote] Error while making request to platform: {e}') except Exception: logger.alert( f"[validate_quote] Unexpected error: {traceback.format_exc(limit=5)}" ) return False
def create_channel_id(self, device, channel_template, client_id): # Creating a new channel for the particular device"s id data = { "name": device.get("content", "Device"), "channeltemplate_id": channel_template, "remote_key": device.get('id'), "requesting_client_id": client_id } logger.debug("Initiated POST - {}".format(settings.api_server_full)) logger.verbose(format_str(data, is_json=True)) resp = self.session.post("{}/managers/self/channels".format( settings.api_server_full), json=data) logger.debug("[create_channel_id] Received response code[{}]".format( resp.status_code)) if resp.status_code == 412 and resp.json().get('code') == 2000: raise UnauthorizedException(resp.json().get('text')) if resp.status_code != 201: logger.debug(format_str(resp.json(), is_json=True)) raise Exception( f"Failed to create channel for channel template {channel_template} " f"{traceback.format_exc(limit=5)}") return resp.json()["id"]
def get_access(): """ To send authorization request with 0Auth2.0 to Muzzley platform """ logger.verbose("Trying to authorize with Muzzley...") data = { "client_id": settings.client_id, "client_secret": settings.client_secret, "response_type": settings.grant_type, "scope": settings.scope, "state": "active" } url = settings.auth_url try: logger.debug("Initiated POST - {}".format(url)) resp = requests.post(url, data=data) if resp.status_code == 200: logger.notice("Manager succesfully Authorized with Muzzley") store_info(resp.json()) start_refresher() else: error_msg = format_response(resp) raise Exception(error_msg) except Exception: logger.alert("Unexpected error during authorization {}".format( traceback.format_exc(limit=5))) raise
def validate_channel(channel_id): try: header = { "Authorization": f"Bearer {settings.block['access_token']}", "Accept": "application/json", } if not channel_id: logger.warning( f"[validate_channel] Invalid channel_id: {channel_id}") return {} url = f"{settings.api_server_full}/channels/{channel_id}" resp = requests.get(url, headers=header) if int(resp.status_code) == 200: return resp.json() else: logger.verbose( f"[validate_channel] Received response code [{resp.status_code}]" ) raise ChannelTemplateNotFound( f"Failed to retrieve channel_template_id for {channel_id}") except (OSError, ChannelTemplateNotFound) as e: logger.warning( f'[validate_channel] Error while making request to platform: {e}') except Exception: logger.alert( f"[validate_channel] Unexpected error get_channel_template: {traceback.format_exc(limit=5)}" ) return {}
def get_or_create_channel(self, device, channel_template, client_id): try: channel_id = self.db.get_channel_id(device["id"]) # Validate if still exists on Muzzley url = "{}/channels/{}".format(settings.api_server_full, channel_id) resp = self.session.get(url, data=None) logger.verbose("/v3/channels/{} response code {}".format( channel_id, resp.status_code)) if resp.status_code not in (200, 201): channel_id = self.create_channel_id(device, channel_template, client_id) # Ensure persistence of manufacturer's device id (key) to channel id (field) in redis hash self.db.set_channel_id(device["id"], channel_id, True) logger.verbose( "Channel added to database {}".format(channel_id)) return channel_id except UnauthorizedException as e: logger.error(f"{e}") except Exception: logger.error( f'Error get_or_create_channel {traceback.format_exc(limit=5)}') return None
def on_publish(self, client, userdata, mid): logger.debug( "\n\n\n\n\n\t\t\t\t\t******************* ON PUBLISH ****************************" ) logger.verbose( "Mqtt - Publish acknowledged by broker, mid({}) userdata={}.". format(mid, userdata))
def select_device(self, request): logger.debug( "\n\n\n\n\n\t\t\t\t\t*******************SELECT_DEVICE****************************" ) logger.debug("Received {} - {}".format(request.method, request.path)) logger.verbose("headers: {}".format(request.headers)) try: received_hash = request.headers.get("Authorization", "").replace("Bearer ", "") if self._validate_confirmation_hash(received_hash): if request.is_json: payload = request.get_json() logger.verbose(format_str(payload, is_json=True)) paired_devices = payload["channels"] if not paired_devices: logger.error( "No paired devices found in request: {}".format( payload)) else: return Response(status=422) owner_id = request.headers["X-Owner-Id"] client_id = request.headers["X-Client-Id"] channel_template = request.headers["X-Channeltemplate-Id"] channels, credentials = self.handle_channel_requests( client_id, owner_id, channel_template, paired_devices) sender = { "channel_template_id": channel_template, "client_id": client_id, "owner_id": owner_id } self.implementer.did_pair_devices( sender=sender, credentials=credentials, paired_devices=paired_devices, channels=channels) return Response(response=json.dumps(channels), status=200, mimetype="application/json") else: logger.debug("Provided invalid confirmation hash!") return Response(status=403) except Exception: logger.error("Couldn't complete processing request, {}".format( traceback.format_exc(limit=5))) return Response(status=403)
def store_info(resp): """ Stores the response obtained during authorization with Muzzley in base.Settings.block """ logger.verbose("Caching authorization response info from Muzzley...") if 'access_token' and 'refresh_token' and 'expires' and 'code' and 'endpoints' in resp: settings.block['access_token'] = resp['access_token'] settings.block['refresh_token'] = resp['refresh_token'] settings.block['expires'] = resp['expires'] settings.block['code'] = resp['code'] settings.block['http_ep'] = resp['endpoints']['http'] settings.block['mqtt_ep'] = resp['endpoints']['mqtt']
def update_credentials(self, new_credentials, old_credentials_list): """ Update all credentials in old_credentials_list with new_credentials :param new_credentials: dict :param old_credentials_list: [{ 'key': ':credential_key', 'value': :credential_dict }, ...] """ old_credentials_list = self.check_credentials_man_id(old_credentials_list, new_credentials) error_keys = [cred_['key'] for cred_ in old_credentials_list if cred_['has_error'] is True] old_credentials_list = self.filter_credentials(old_credentials_list, new_credentials.get('client_man_id')) updated_credentials = [] logger.info(f'[TokenRefresher] update_credentials: {len(old_credentials_list)} keys to update') for cred_ in old_credentials_list: key = cred_['key'] credentials = cred_['value'] channel_id = key.split('/')[-1] owner_id = key.split('/')[1] client_app_id = credentials.get('client_id', credentials.get('data', {}).get('client_id', '')) client_man_id = credentials.get('client_man_id') # replace client_id in new credentials with current client_app_id and client_man_id # to keep consistence with different apps new_credentials['client_id'] = client_app_id new_credentials['client_man_id'] = client_man_id try: channeltemplate_id = self.channel_relations[channel_id] except KeyError: channeltemplate_id = self.implementer.get_channel_template(channel_id) if channeltemplate_id and \ (settings.config_boot.get('on_pair', {}).get('update_all_channeltemplates', True) or channeltemplate_id == self.channel_template): logger.debug(f'[update_credentials] new credentials {key}') logger.info(f"[update_credentials] client_app_id: {client_app_id}; owner_id: {owner_id}; " f"channel_id: {channel_id}; channeltemplate_id: {channeltemplate_id}") self.channel_relations[channel_id] = channeltemplate_id stored = self.implementer.store_credentials(owner_id, client_app_id, channeltemplate_id, new_credentials) if stored: self.db.set_credentials(new_credentials, client_app_id, owner_id, channel_id) updated_credentials.append(key) else: logger.verbose(f'[update_credentials] Ignoring key {key}') error_keys.append(key) else: logger.verbose(f'[update_credentials] Ignoring key {key}') error_keys.append(key) return list(set(updated_credentials)), list(set(error_keys))
def get_application(self): try: logger.debug(f"[get_application] Trying to get application data - {settings.webhook_url}") resp = requests.get(settings.webhook_url, headers=self.session.headers) logger.verbose("[get_application] Received response code[{}]".format(resp.status_code)) if int(resp.status_code) == 200: logger.notice("[get_application] Get application successful!") return resp.json() else: raise Exception('[get_application] Error getting application!') except Exception: logger.alert("Failed while get application! {}".format(traceback.format_exc(limit=5))) raise
def devices_list(self, request): logger.debug( "\n\n\n\n\n\t\t\t\t\t********************** LIST_DEVICES **************************" ) logger.debug("Received {} - {}".format(request.method, request.path)) logger.verbose("headers: {}".format(request.headers)) try: received_hash = request.headers.get("Authorization", "").replace("Bearer ", "") if self._validate_confirmation_hash(received_hash): credentials = self.db.get_credentials( request.headers["X-Client-Id"], request.headers["X-Owner-Id"]) if not credentials: logger.error("No credentials found in database") return Response(status=404) sender = { "channel_template_id": request.headers["X-Channeltemplate-Id"], "client_id": request.headers["X-Client-Id"], "owner_id": request.headers["X-Owner-Id"] } data = self.implementer.get_devices(sender=sender, credentials=credentials) if not data: logger.info("No devices found for this user") for element in data: if "content" not in element or ("content" in element and not element["content"]): element["content"] = "" return Response(response=json.dumps(data), status=200, mimetype="application/json") else: logger.debug("Provided invalid confirmation hash!") return Response(status=403) except Exception: logger.error("Couldn't complete processing request, {}".format( traceback.format_exc(limit=5))) return Response(status=403)
def update_all_owners(self, new_credentials, channel_id, ignore_keys=None): ignore_keys = ignore_keys or [] all_owners_credentials = self.db.full_query(f'credential-owners/*/channels/{channel_id}') all_owners_credentials = list(filter(lambda x: x['key'] not in ignore_keys, all_owners_credentials)) all_owners_credentials = self.check_credentials_man_id(all_owners_credentials, new_credentials) error_keys = [cred_['key'] for cred_ in all_owners_credentials if cred_.get('has_error', False) is True] all_owners_credentials = self.filter_credentials(all_owners_credentials, new_credentials.get('client_man_id')) logger.info(f'[update_all_owners] {len(all_owners_credentials)} keys to update for channel {channel_id}') updated_cred = [] if all_owners_credentials: updated_, error_ = self.update_credentials(new_credentials, all_owners_credentials) updated_cred.extend(updated_) updated_cred.extend(ignore_keys) updated_cred.extend(error_) for owner_credentials in all_owners_credentials: owner_id = owner_credentials['key'].split('/')[1] logger.verbose(f'[update_all_owners] Trying to update all credentials for the owner: {owner_id}') updated_cred.extend(self.update_all_channels(new_credentials, owner_id, updated_cred)) return list(set(updated_cred)), list(set(error_keys))
def update_all_channels(self, new_credentials, owner_id, ignore_keys=None): ignore_keys = ignore_keys or [] all_channels_credentials = self.db.full_query(f'credential-owners/{owner_id}/channels/*') all_channels_credentials = list(filter(lambda x: x['key'] not in ignore_keys, all_channels_credentials)) logger.info(f'[update_all_channels] {len(all_channels_credentials)} keys to update for owner {owner_id}') updated_cred = [] if all_channels_credentials: updated_, error_ = self.update_credentials(new_credentials, all_channels_credentials) updated_cred.extend(updated_) ignore_keys.extend(updated_) ignore_keys.extend(error_) for channel_credentials in all_channels_credentials: channel_id = channel_credentials['key'].split('/')[-1] logger.verbose(f'[update_all_channels] Trying to update all credentials for the channel: {channel_id}') updated_, error_keys = self.update_all_owners(new_credentials, channel_id, ignore_keys) updated_cred.extend(updated_) ignore_keys.extend(updated_) ignore_keys.extend(error_) return list(set(updated_cred))
def after(self, response): try: if 'Location' in response.headers: logger.debug('Redirect {} code[{}]'.format( response.headers['Location'], response.status)) else: logger.debug('Responding with status code[{}]'.format( response.status)) if response.mimetype == 'application/json': logger.verbose('\n{}\n'.format( json.dumps(json.loads(response.response[0]), indent=4, sort_keys=True))) except: logger.error('Post request logging failed!') return response
def patch_custom_endpoints(self): try: custom_endpoints = settings.custom_endpoints url = settings.webhook_url data = {'quote_actions': {}} for endpoint in custom_endpoints: data['quote_actions'].update({ endpoint['namespace']: f"{settings.schema_pub}://{settings.host_pub}/" f"{settings.api_version}{endpoint['uri']}" }) if data['quote_actions']: logger.debug(f"[patch_custom_endpoints] Initiated PATCH - {url}") logger.verbose("\n{}\n".format(json.dumps(data, indent=4, sort_keys=True))) resp = requests.patch(url, data=json.dumps(data), headers=self.session.headers) logger.verbose("[patch_{}] Received response code[{}]".format(endpoint['namespace'], resp.status_code)) logger.verbose("\n{}\n".format(json.dumps(resp.json(), indent=4, sort_keys=True))) if int(resp.status_code) == 200: logger.notice(f"[patch_custom_endpoints] {endpoint['namespace']} setup successful!") else: raise Exception(f"[patch_custom_endpoints] {endpoint['namespace']} setup not successful!") except Exception: logger.alert("Failed at patch endpoint! {}".format(traceback.format_exc(limit=5))) raise
def patch_endpoints(self): try: full_host = "{}://{}/{}".format(settings.schema_pub, settings.host_pub, settings.api_version) data = { "authorize": "{}/authorize".format(full_host), "receive_token": "{}/receive-token".format(full_host), "devices_list": "{}/devices-list".format(full_host), "select_device": "{}/select-device".format(full_host) } url = settings.webhook_url logger.debug("Initiated PATCH - {} {}".format( url, self.session.headers)) logger.verbose(format_str(data, is_json=True)) resp = requests.patch(url, data=json.dumps(data), headers=self.session.headers) logger.verbose( "[patch_endpoints] Received response code[{}]".format( resp.status_code)) logger.verbose(format_str(resp.json(), is_json=True)) self.set_confirmation_hash() except Exception: logger.alert("Failed at patch endpoints! {}".format( traceback.format_exc(limit=5))) raise
def _basic_quote_validation(self, request): logger.debug(f"Received {request.method} - {request.path}") logger.verbose(f"headers: {request.headers}") received_hash = request.headers.get("Authorization", "").replace("Bearer ", "") if self._validate_confirmation_hash(received_hash): data = request.json if not data: raise InvalidRequestException("Missing Payload") service_id = data.get('service_id') quote_id = data.get('quote_id') if not (service_id and is_valid_uuid(service_id) and service_id in [_service['id'] for _service in settings.services]): raise InvalidRequestException("Invalid Service") if not (quote_id and is_valid_uuid(quote_id) and validate_quote(quote_id)): raise InvalidRequestException("Invalid Quote") return service_id, quote_id else: logger.debug("[basic_quote_validation] Provided invalid confirmation hash!") raise UnauthorizedException("Invalid token!")
def renew_token(): logger.verbose("Trying to refresh Tokens...") url = settings.renew_url header = {"Content-Type": "application/json"} data = { "client_id": settings.client_id, "refresh_token": settings.block['refresh_token'], "grant_type": settings.grant_type } try: logger.debug("Initiated POST - {}".format(url)) resp = requests.get(url, params=data, headers=header) if resp.status_code == 200: logger.notice("Manager succesfully performed Token refresh") store_info(resp.json()) start_refresher() else: error_msg = format_response(resp) raise Exception(error_msg) except Exception: logger.alert("Unexpected error during token renewal: {}".format( traceback.format_exc(limit=5))) os._exit(1)
def patch_endpoints(self): try: _data = settings.services for _service in _data: try: if settings.config_boot.get('patch_services', True) is True: data = { 'activation_uri': '{}://{}/{}/services/{}/authorize'.format(settings.schema_pub, settings.host_pub, settings.api_version, _service['id']) } logger.debug("[patch_endpoints] Initiated PATCH - {}".format(_service.get('url'))) logger.verbose("\n{}\n".format(json.dumps(data, indent=4, sort_keys=True))) resp = requests.patch('{}/services/{}'.format(settings.api_server_full, _service['id']), data=json.dumps(data), headers=self.session.headers) logger.verbose("[patch_endpoints] Received response code[{}]".format(resp.status_code)) logger.verbose("\n{}\n".format(json.dumps(resp.json(), indent=4, sort_keys=True))) if int(resp.status_code) == 200: logger.notice("[patch_endpoints] Service setup successful!") else: raise Exception('Service setup not successful!') except Exception as e: logger.alert("[patch_endpoints] Failed to set service!\n{}".format(e)) os._exit(1) self.patch_custom_endpoints() self.set_confirmation_hash() except Exception: logger.alert("[patch_endpoints] Failed at patch endpoints! {}".format(traceback.format_exc(limit=5))) raise
from flask_cors import CORS from base.thread_pool import ThreadPool from base.constants import DEFAULT_THREAD_POOL_LIMIT if settings.config_thread_pool.get('enabled', True): thread_pool = ThreadPool( settings.config_thread_pool.get('num_threads', DEFAULT_THREAD_POOL_LIMIT)) thread_pool.start() else: thread_pool = None from base import views from base.common.tcp_base import TCPBase # Flask App logger.verbose("Creating Flask Object...") try: if not settings.mqtt: app = Flask(__name__, instance_relative_config=True) app.config.from_object("flask_config") if settings.enable_cors is True: CORS(app, supports_credentials=True) views = views.Views(app, thread_pool) logger.info("[Boot]: Flask object successfully created!") if settings.config_tcp.get('enabled', False): tcp_ = TCPBase(webhook=views.webhook) tcp_.kickoff() else: app = views.Views() logger.info("[Boot]: Mqtt")
def handle_credentials(credentials, old_credentials, client_id, owner_id, channel_id, ignore_keys=None): from base.solid import implementer refresher = TokenRefresherManager(implementer=implementer) ignore_keys = ignore_keys or [] logger.debug( "\n\n\n\n\n\t\t\t\t\t*******************HANDLE_CREDENTIALS****************************" ) logger.info( f"Client_id {client_id}; Owner_id: {owner_id}; channel_id: {channel_id}" ) if settings.config_refresh.get('enabled') is True: if not channel_id: logger.warning( f"[handle_credentials] channel_id not set: {channel_id}") return elif 'refresh_token' not in old_credentials: logger.error( "[handle_credentials] Refresh token not found in old credentials" ) return else: if not settings.config_boot.get('on_pair', {}).get( 'update_all_channeltemplates', True): refresher.channel_template = implementer.get_channel_template( channel_id) refresher.channel_relations[ channel_id] = refresher.channel_template updated_cred = [] updated_cred.extend(ignore_keys) refresh_token = old_credentials['refresh_token'] credentials_list = refresher.get_credentials_by_refresh_token( refresh_token) or [] # remove updated keys from credentials list credentials_list = [ cred_ for cred_ in credentials_list if cred_['key'] not in ignore_keys ] logger.verbose( f"[handle_credentials] Starting update by token_refresher for channel: {channel_id}" ) updated_, error_keys = refresher.update_credentials( credentials, credentials_list) ignore_keys.extend(updated_) ignore_keys.extend(error_keys) updated_cred.extend(updated_) logger.debug( f"[handle_credentials] Starting update all owners for channel: {channel_id}" ) updated_, error_keys = refresher.update_all_owners( credentials, channel_id, ignore_keys) ignore_keys.extend(updated_) ignore_keys.extend(error_keys) updated_cred.extend(updated_) logger.debug("[handle_credentials] Starting update all channels") updated_cred.extend( refresher.update_all_channels(credentials, owner_id, ignore_keys)) logger.debug( f"[handle_credentials] Updated keys: {list(set(updated_cred))}" ) del refresher.channel_template del refresher.channel_relations
def channels_grant(self, device, client_id, owner_id, channel_template, credentials): try: channel_template = self.implementer.update_channel_template( device['id']) or channel_template channel_id = self.get_or_create_channel(device, channel_template, client_id) if not channel_id: logger.warning("[channels_grant] No channel found") return False # Granting permission to intervenient with id X-Client-Id url = "{}/channels/{}/grant-access".format( settings.api_server_full, channel_id) try: data = { "client_id": client_id, "requesting_client_id": client_id, "role": "application", "remote_key": device.get('id') } logger.debug("Initiated POST - {}".format(url)) logger.verbose(format_str(data, is_json=True)) resp_app = self.session.post(url, json=data) logger.debug( "[channels_grant] Received response code[{}]".format( resp_app.status_code)) if resp_app.status_code == 412 and resp_app.json().get( 'code') == 2000: raise UnauthorizedException(resp_app.json().get('text')) if resp_app.status_code not in (201, 200): logger.debug(format_str(resp_app.json(), is_json=True)) return False except UnauthorizedException as e: logger.error(f"{e}") return False except Exception: logger.error("Failed to grant access to client {} {}".format( client_id, traceback.format_exc(limit=5))) return False # Granting permission to intervenient with id X-Owner-Id try: data = { "client_id": owner_id, "requesting_client_id": client_id, "role": "user", "remote_key": device.get('id') } logger.debug("Initiated POST - {}".format(url)) logger.verbose(format_str(data, is_json=True)) resp_user = self.session.post(url, json=data) logger.verbose( "[channels_grant] Received response code[{}]".format( resp_user.status_code)) if resp_app.status_code == 412 and resp_app.json().get( 'code') == 2000: raise UnauthorizedException(resp_app.json().get('text')) if resp_user.status_code not in (201, 200): logger.debug(format_str(resp_user.json(), is_json=True)) return False except UnauthorizedException as e: logger.error(f"{e}") return False except Exception: logger.error("Failed to grant access to owner {} {}".format( channel_template, traceback.format_exc(limit=5))) return False return channel_id except Exception: logger.error('Error while requesting grant: {}'.format( traceback.format_exc(limit=5))) return None