def get(self, request): auth_token = request['system_authtoken'] friendly_name = get_deployment_friendly_name(auth_token) encryption_context = SplunkEncryptionContext( auth_token, constants.SPACEBRIDGE_APP_NAME, SodiumClient(LOGGER)) mdm_sign_public_key = get_mdm_public_signing_key(auth_token) mdm_keypair_generation_time = get_mdm_update_timestamp( request, auth_token) return { 'payload': { constants.DEPLOYMENT_FRIENDLY_NAME: friendly_name, constants.SIGN_PUBLIC_KEY: py23.b64encode_to_str(encryption_context.sign_public_key()), constants.DEPLOYMENT_ID: encryption_context.sign_public_key( transform=encryption_context.generichash_hex), constants.ENCRYPT_PUBLIC_KEY: py23.b64encode_to_str(encryption_context.encrypt_public_key()), constants.SERVER_VERSION: str(app_version()), constants.MDM_SIGN_PUBLIC_KEY: mdm_sign_public_key, constants.MDM_KEYPAIR_GENERATION_TIME: mdm_keypair_generation_time }, 'status': 200 }
def create_tv_config_map(device_ids, async_kvstore_client, request_context): """ Function to create tv config map for initializing TVBookmark class :param device_ids: device ids to fetch tv configs for :param request_context: Used to authenticate kvstore requests :param async_kvstore_client: kvstore client used to make kvstore requests """ # When we are setting a tv bookmark, we're only given device ids tv_config_map = {} tv_configs = yield get_drone_mode_tvs(request_context, async_kvstore_client, device_ids=device_ids) LOGGER.debug('device_ids in create_tv_config_map=%s', device_ids) LOGGER.debug('tv_configs in create_tv_config_map=%s', tv_configs) for tv_config in tv_configs: device_id = b64encode_to_str(base64.urlsafe_b64decode(str(tv_config.get(constants.KEY)))) LOGGER.debug('tv_config for device_id=%s is: %s', device_id, tv_config) # Don't save captain id and captain url. bookmark should be agnostic to this concept # only upon activation should we care about the captain stuff. This way, when we activate # a config in a grid configuration, it starts off with no captain data, and their for a captain # is immediately elected. if not all(key in tv_config for key in TVConfig.required_kvstore_keys()): raise SpacebridgeApiRequestError('No config data exists for device_id={}' .format(device_id), status_code=http.NOT_FOUND) tv_config[constants.CAPTAIN_ID] = '' tv_config[constants.CAPTAIN_URL] = '' tv_config_object = TVConfig(**tv_config) tv_config_map[device_id] = json.loads(tv_config_object.to_json()) defer.returnValue(tv_config_map)
def parse_proxy_settings(proxy_url, default_port=DEFAULT_HTTP_PORT): """ Helper to parse our proxy settings :param proxy_url: :param default_port: :return: """ if proxy_url is None: return {} # Strip https:// or http:// url = proxy_url.replace('http://', '') url = url.replace('https://', '').strip() # Split by '@', indicates basic authentication if '@' in url: auth, proxy_host_port = url.split('@') else: auth, proxy_host_port = None, url # Split by ':' if ':' in proxy_host_port: host, port = proxy_host_port.split(':') else: host = proxy_host_port port = default_port if auth is not None: auth = py23.b64encode_to_str(auth.encode('utf-8')).strip() else: auth = None return {'host': host, 'port': int(port), 'auth': auth}
def secure_session_token(encryption_context, username, password): """ :param encryption_context: An encryption context with encryption public and private keys :param username: the current user's username :param password: the current user's password :return: """ public_key = encryption_context.encrypt_public_key() private_key = encryption_context.encrypt_private_key() session_token_raw = json.dumps({ 'username': username, 'password': password, }).encode('utf-8') ciphertext = encrypt_session_token(encryption_context.sodium_client, session_token_raw, public_key, private_key) return py23.b64encode_to_str(ciphertext)
def __init__(self, device_id='', device_name='', mode=TVConfigModeType.UNKNOWN_MODE.value, content='', tv_grid=None, timestamp='0', captain_id='', captain_url='', input_tokens=None, user_choices=None, slideshow_duration=None, _user='', _key=''): self.device_id = device_id if _key and not device_id: raw_id = base64.urlsafe_b64decode(str(_key)) self.device_id = py23.b64encode_to_str(raw_id) self.device_name = device_name self.mode = mode self.content = content self._user = _user self._key = _key if tv_grid is None: tv_grid = {} self.tv_grid = tv_grid # Important: note that proto's json representation of int64 fields is a string self.timestamp = timestamp self.captain_id = captain_id self.captain_url = captain_url if input_tokens is None: input_tokens = "{}" if user_choices is None: user_choices = "{}" self.user_choices = user_choices self.input_tokens = input_tokens self.slideshow_duration = slideshow_duration
def format_response(self, response): if isinstance(response, dict) and isinstance(response.get('status'), int): headers = response.get('headers') if 'payload' in response: payload = response['payload'] if isinstance(payload, str) or (sys.version_info < (3, 0) and isinstance(payload, unicode)): payload = { 'message': payload, 'status': response['status'] } json_response = { 'payload': payload, 'status': response['status'] } if headers: json_response['headers'] = headers return json.dumps(json_response) if 'binary' in response: json_response = { 'payload_base64': py23.b64encode_to_str(response['binary']), 'status': response['status'], } if headers: json_response['headers'] = headers return json.dumps(json_response) status = response.get('status', 500) if isinstance(response, dict) else 500 if not isinstance(status, int): status = 500 json_response = {'payload': response, 'status': status} return json.dumps(json_response)
def fetch_valid_tvs(request_context, async_kvstore_client, user=None, tv_ids=None): """ Function to fetch valid tvs for drone mode :param request_context: request context used to make kvstore requests :param async_kvstore_client: async client used to make kvstore requests :param device_id: optional device id to filter on """ # fetch list of all registered tvs valid_tv_protos = [] valid_tv_json = [] if not user: user = request_context.current_user tv_list = yield get_registered_tvs(request_context.system_auth_header, user, async_kvstore_client, device_ids=tv_ids) # fetch devices from drone_mode_tvs collection # if there is data, return that, otherwise just return blank TV data # construct set containing device ids drone_mode_tvs = yield get_drone_mode_tvs(request_context, async_kvstore_client, user=user) LOGGER.debug('registered_tvs=%s, drone mode tvs=%s', tv_list, drone_mode_tvs) drone_mode_tv_dict = {} for element in drone_mode_tvs: if constants.CONTENT in element: raw_id = base64.urlsafe_b64decode(str(element[constants.KEY])) encoded_id = b64encode_to_str(raw_id) element[constants.DEVICE_ID] = encoded_id drone_mode_tv_dict[encoded_id] = element if tv_list: active_subscriptions = yield fetch_subscriptions( request_context.auth_header, async_kvstore_client, user_list=[user], subscription_type=constants.DRONE_MODE_TV, device_ids=tv_ids) LOGGER.debug('active_subscriptions=%s', active_subscriptions) active_subscription_ids = { subscription.device_id for subscription in active_subscriptions } for device in tv_list: device_id = device[constants.DEVICE_ID] tv_proto = drone_mode_pb2.TVData() tv_proto.device_id = device_id tv_proto.display_name = device['device_name'] tv_proto.is_active = device_id in active_subscription_ids tv_proto.tv_config.SetInParent() if device_id in drone_mode_tv_dict: tv_config = TVConfig(**drone_mode_tv_dict[device_id]) tv_config.set_protobuf(tv_proto.tv_config) valid_tv_protos.append(tv_proto) json_obj = MessageToDict(tv_proto, including_default_value_fields=True, use_integers_for_enums=True, preserving_proto_field_name=True) # since we're storing the user choices and input tokens as blobs, # when we deserialize from proto to dict, we need to json dumps each of the fields json_obj[constants.TV_CONFIG][constants.INPUT_TOKENS] = json.dumps( json_obj.get(constants.TV_CONFIG, {}).get(constants.INPUT_TOKENS, {})) json_obj[constants.TV_CONFIG][constants.USER_CHOICES] = json.dumps( json_obj.get(constants.TV_CONFIG, {}).get(constants.USER_CHOICES, {})) valid_tv_json.append(json_obj) LOGGER.debug( 'finished fetch valid tvs: valid_tv_protos=%s, valid_tv_json=%s', valid_tv_protos, valid_tv_json) defer.returnValue([valid_tv_protos, valid_tv_json])
async def persist_device_info(self, device_info, username): """ Write device info to KV Store collections :param device_info (DeviceInfo) :param username (String) :return None """ url_safe_device_id = py23.urlsafe_b64encode_to_str( device_info.device_id) device_id = py23.b64encode_to_str(device_info.device_id) # Insert into public keys table device_public_keys_payload = { '_key': url_safe_device_id, 'encrypt_public_key': py23.b64encode_to_str(device_info.encrypt_public_key), 'sign_public_key': py23.b64encode_to_str(device_info.sign_public_key) } registration_payload = { '_key': url_safe_device_id, 'app_id': device_info.app_id, 'device_type': resolve_app_name(device_info.app_id), 'device_name': self.build_device_name(device_info, username), 'user': username, 'device_id': device_id } keys_resp = await self.async_kvstore_client.async_kvstore_post_request( constants.DEVICE_PUBLIC_KEYS_COLLECTION_NAME, json.dumps(device_public_keys_payload), self.system_auth_header) keys_resp_code = keys_resp.code keys_resp_text = await keys_resp.text() allowed_results = (HTTPStatus.CREATED, HTTPStatus.OK, HTTPStatus.CONFLICT) if keys_resp_code not in allowed_results: raise CloudgatewayMdmRegistrationError( CloudgatewayMdmRegistrationError.ErrorType.UNKNOWN_ERROR, keys_resp_text) devices_resp = await self.async_kvstore_client.async_kvstore_post_request( constants.REGISTERED_DEVICES_COLLECTION_NAME, json.dumps(registration_payload), self.system_auth_header, owner=username) devices_resp_code = devices_resp.code devices_resp_text = await devices_resp.text() if devices_resp_code not in allowed_results: raise CloudgatewayMdmRegistrationError( CloudgatewayMdmRegistrationError.ErrorType.UNKNOWN_ERROR, devices_resp_text) # If this call fails it's not a big deal, it's just an optimization. Modular input which runs every day # will pick up this user as having a registered device in any case. r = await self.async_kvstore_client.async_kvstore_post_request( constants.REGISTERED_USERS_COLLECTION_NAME, json.dumps({"_key": username}), self.system_auth_header) LOGGER.info( "Received response_code={} back on add user to registered users collection" .format(r.code))
def process_ar_workspace_image_set_request(request_context, client_single_request, server_single_response, async_kvstore_client, async_ar_permissions_client): """ This method processes AR Workspace image set requests, overwrites the workspace positioning data for the workspace corresponding to the dashboard with the specified image. :param request_context: :param client_single_request: :param server_single_response: :param async_kvstore_client: :return: """ yield async_ar_permissions_client.check_permission( request_context, Capabilities.WORKSPACE_WRITE, message=WORKSPACE_MODIFY_PERMISSION_REQUIRED) dashboard_key = client_single_request.arWorkspaceImageSetRequest.arWorkspaceAnchored.key if not dashboard_key or not dashboard_key.anchorId: raise SpacebridgeApiRequestError( "Call to AR workspace IMAGE SET with no anchorId", status_code=400) dashboard_id = make_dashboard_name(dashboard_key.dashboardId) dashboard_name = make_dashboard_name(dashboard_key.dashboardId, dashboard_key.anchorId) # all the ar workspaces with images will have the ARWorkspaceKey object saved with them ar_workspace = py23.b64encode_to_str( client_single_request.arWorkspaceImageSetRequest.arWorkspaceAnchored. arWorkspace.SerializeToString()) json_body = { KEY: dashboard_name, ORIGINAL_WORKSPACE_FIELD: dashboard_id, WORKSPACE_DATA: ar_workspace } response = yield async_kvstore_client.async_kvstore_post_request( AR_DASHBOARDS_COLLECTION_NAME, data=json.dumps(json_body), auth_header=request_context.auth_header) if response.code == http.CONFLICT: # in this case the workspace already exists, so make an update post request response = yield async_kvstore_client.async_kvstore_post_request( AR_DASHBOARDS_COLLECTION_NAME, data=json.dumps(json_body), auth_header=request_context.auth_header, key_id=dashboard_name) message = yield response.text() if response.code not in [http.OK, http.CREATED]: raise SpacebridgeApiRequestError( "Call to AR workspace IMAGE SET failed with code={} message={}". format(response.code, message), status_code=response.code) LOGGER.info( "Call to AR workspace SET succeeded with code={} message={}".format( response.code, message)) server_single_response.arWorkspaceImageSetResponse.dashboardId.dashboardId = dashboard_key.dashboardId server_single_response.arWorkspaceImageSetResponse.dashboardId.anchorId = dashboard_key.anchorId
def process_subscribe_drone_mode_tv(request_context, client_subscription_message, server_subscription_response, async_client_factory): """ Process subscribe to drone mode tv events for a device id :param request_context: Used to authenticate kvstore requests :param client_single_request: client request object protobuf :param single_server_response: server response object protobuf :param async_client factory: factory class used to generate kvstore and spacebridge clients """ LOGGER.debug('start of subscribe drone mode tv') async_kvstore_client = async_client_factory.kvstore_client() # base64 encode device id device_id = client_subscription_message.clientSubscribeRequest.droneModeTVSubscribe.deviceId encoded_device_id = b64encode_to_str(device_id) urlsafe_encoded_device_id = urlsafe_b64encode_to_str(device_id) # Grab TV data tv_data = yield get_registered_tvs(request_context.auth_header, request_context.current_user, async_kvstore_client) LOGGER.debug('raw device id=%s, encoded_device_id=%s, urlsafe_encoded_device_id=%s, tv_data=%s', device_id, encoded_device_id, urlsafe_encoded_device_id, tv_data) # validate that device is a valid apple tv registered to this user valid_device_id = any(device.get(constants.DEVICE_ID) == encoded_device_id for device in tv_data) if not valid_device_id: error_message = 'Invalid device id={}'.format(encoded_device_id) raise SpacebridgeApiRequestError(error_message, status_code=http.BAD_REQUEST) # construct parameters for subscription creation / updating ttl_seconds = client_subscription_message.clientSubscribeRequest.ttlSeconds expiration_time = get_expiration_timestamp_str(ttl_seconds=ttl_seconds) now = get_current_timestamp_str() params = { constants.TTL_SECONDS: str(ttl_seconds), constants.DEVICE_ID: encoded_device_id, constants.EXPIRED_TIME: expiration_time, constants.USER: request_context.current_user, constants.LAST_UPDATE_TIME: now, constants.SUBSCRIPTION_TYPE: constants.DRONE_MODE_TV } # delete existing subscriptions query = { constants.QUERY: json.dumps({ constants.DEVICE_ID: encoded_device_id, constants.USER: request_context.current_user, constants.SUBSCRIPTION_TYPE: constants.DRONE_MODE_TV }) } delete_response = yield async_kvstore_client.async_kvstore_delete_request( collection=constants.SUBSCRIPTIONS_COLLECTION_NAME, params=query, owner=constants.NOBODY, auth_header=request_context.auth_header) yield check_and_raise_error(delete_response, request_context, 'Delete existing tv subscriptions') # create subscription subscription_id = yield write_drone_mode_subscription(request_context, params, async_client_factory) server_subscription_response.subscriptionId = subscription_id server_subscription_response.serverSubscribeResponse.droneModeTVSubscribe.deviceId = device_id LOGGER.debug('Successfully wrote drone mode tv subscription with id=%s', subscription_id)
def authentication_query_request(auth_code, encryption_context): """ Abstraction layer for the spacebridge request. This function: 1. Makes the registration query GET request to the spacebridge endpoint 2. Parses the protobuf response 3. Packs the response values into a response object. Binary objects are encoded to ensure kvstore compatibility :param auth_code: Authorization code of the device being registered :return: response object containing "public_key", "device_id", and "conf_code" """ # Makes the registration query GET request to the spacebridge endpoint try: headers = { 'Authorization': encryption_context.sign_public_key( transform=encryption_context.generichash_hex) } response = requests.get('%s/api/registrations/%s' % (config.get_spacebridge_domain(), auth_code), headers=headers, proxies=config.get_proxies()) except Exception: LOGGER.exception("Exception contacting spacebridge") raise Errors.SpacebridgeServerError('Unable to reach Spacebridge', 503) # Parses the protobuf response spacebridge_response = http_pb2.AuthenticationQueryResponse() spacebridge_response.ParseFromString(response.content) if spacebridge_response.HasField('error'): if response.status_code == 500: raise Errors.SpacebridgeServerError( 'Spacebridge encountered an internal error: %s' % spacebridge_response.error.message, 500) raise Errors.SpacebridgeServerError( 'Spacebridge request error: %s' % spacebridge_response.error.message, response.status_code) if not str(response.status_code).startswith('2'): raise Errors.SpacebridgeServerError( "Spacebridge error: %s" % str(response.content), response.status_code) # Packs the response values into a response object. Binary objects are encoded to ensure kvstore compatibility encrypt_public_key = spacebridge_response.payload.publicKeyForEncryption sign_public_key = spacebridge_response.payload.publicKeyForSigning response = { 'encrypt_public_key': py23.b64encode_to_str(encrypt_public_key), 'sign_public_key': py23.b64encode_to_str(sign_public_key), 'device_id': py23.b64encode_to_str(spacebridge_response.payload.deviceId), 'conf_code': encryption_context.generichash_hex(sign_public_key).upper()[:8], } try: response[APP_TYPE_LABEL] = translate_app_name( http_pb2.AppType.Name(spacebridge_response.payload.appType)) except ValueError as err: # When app_type is 'APPTYPE_INVALID' raise Errors.SpacebridgeRestError('Registration Error: %s' % str(err), 501) return response
def process_tv_config_delete_request(request_context, client_single_request, single_server_response, async_client_factory): """This method will process a tvConfigDeleteRequest. :param request_context: Used to authenticate kvstore requests :param client_single_request: client request object protobuf :param single_server_response: server response object protobuf :param async_client factory: factory class used to generate kvstore and spacebridge clients """ async_kvstore_client = async_client_factory.kvstore_client() tv_config_delete_request = client_single_request.tvConfigDeleteRequest device_ids = tv_config_delete_request.deviceId LOGGER.debug('device_ids to delete=%s', device_ids) yield validate_devices(set(device_ids), request_context, async_kvstore_client) # need to fetch configs before deleting to make sure we're not deleting the captain # and orphaning the workers, if so, for each one that is a captain, # we need to fetch the other configs in the grid # delete the to be deleted captain from the device_id list, and update # the existing members of the grid tvs = yield get_drone_mode_tvs(request_context, async_kvstore_client, device_ids=device_ids) # fetch configs from kvstore # check to see if they are currently captain for tv in tvs: raw_id = base64.urlsafe_b64decode(str(tv[constants.KEY])) encoded_id = b64encode_to_str(raw_id) tv[constants.DEVICE_ID] = encoded_id if (has_grid(tv, is_json=True)): tv_config = TVConfig(**tv) grid_ids = tv.get(constants.TV_GRID, {}).get(constants.DEVICE_IDS, []) yield remove_from_grid(tv_config=tv_config, device_ids=grid_ids, request_context=request_context, async_kvstore_client=async_kvstore_client) entries_to_delete = [] for device_id in device_ids: kvstore_key = b64_to_urlsafe_b64(device_id) post_data = {constants.KEY: kvstore_key} entries_to_delete.append(post_data) deleted_ids = yield async_kvstore_client.async_batch_save_request( request_context.system_auth_header, constants.DRONE_MODE_TVS_COLLECTION_NAME, entries_to_delete, owner=request_context.current_user) deleted_device_ids = [urlsafe_b64_to_b64(deleted_id) for deleted_id in deleted_ids] if deleted_device_ids: single_server_response.tvConfigDeleteResponse.deletedIds.extend(deleted_device_ids) if not deleted_ids: raise SpacebridgeApiRequestError('None of the device_ids={} were deleted'.format(device_ids), status_code=http.INTERNAL_SERVER_ERROR) elif len(deleted_ids) != len(device_ids): kvstore_keys = {b64_to_urlsafe_b64(device_id) for device_id in device_ids} LOGGER.error('TV configs with these ids: %s were deleted, ' 'while TV configs with these ids: %s were not.', deleted_ids, list(kvstore_keys-set(deleted_ids))) # update current deleted tv subscriptions and # all ipads registered to current user yield process_subscriptions(request_context, async_client_factory, tv_device_ids=device_ids) LOGGER.info('Successful TV Config Delete Request for device_ids=%s', deleted_device_ids)
def process_message(message_sender, client_application_message, server_application_message, async_client_factory, encryption_context, server_response_id, system_auth_header, shard_id): device_id = py23.b64encode_to_str(message_sender) encryption_context = encryption_context if client_application_message.HasField(CLIENT_SINGLE_REQUEST): request_object = client_application_message.clientSingleRequest response_object = server_application_message.serverSingleResponse response_object.replyToMessageId = request_object.requestId response_object.serverVersion = str(app_version()) processor = process_request request_type = CLIENT_SINGLE_REQUEST elif client_application_message.HasField(CLIENT_SUBSCRIPTION_MESSAGE): request_object = client_application_message.clientSubscriptionMessage # pings have a special case response, really they could be in their own modular input if client_application_message.clientSubscriptionMessage.HasField(RequestType.CLIENT_SUBSCRIPTION_PING.value): response_object = server_application_message.serverSubscriptionPing else: response_object = server_application_message.serverSubscriptionResponse response_object.replyToMessageId = request_object.requestId response_object.serverVersion = str(app_version()) processor = process_subscription request_type = CLIENT_SUBSCRIPTION_MESSAGE else: LOGGER.warn("No suitable message type found client application request") defer.returnValue("device_id={}".format(device_id)) response_object.requestId = server_response_id if request_object.HasField("runAs"): LOGGER.debug("run as credentials is present") auth_header = yield parse_run_as_credentials(encryption_context, system_auth_header, async_client_factory, request_object.runAs) else: auth_header = parse_session_token(encryption_context, request_object.sessionToken) request_context = RequestContext(auth_header, device_id=device_id, raw_device_id=message_sender, request_id=request_object.requestId, current_user=auth_header.username, system_auth_header=system_auth_header, client_version=request_object.clientVersion, user_agent=request_object.userAgent, shard_id=shard_id) try: validate_client_version(request_object, response_object) yield auth_header.validate(async_client_factory.splunk_client(), LOGGER, request_context) should_send_response = yield context.call({'request_context': request_context}, processor, request_context, encryption_context, request_object, response_object, async_client_factory) # If we aren't sending a response in this request clear the server_application_message if not should_send_response: server_application_message.Clear() except OperationHaltedError: server_application_message.ClearField('app_message') except SpacebridgeError as e: LOGGER.exception("SpacebridgeError during process_message") e.set_proto(response_object) except Exception as e: LOGGER.exception("Unhandled exception during process_message") response_object.error.code = common_pb2.Error.ERROR_UNKNOWN response_object.error.message = str(e) LOGGER.info('Finished processing message. {}'.format(request_context)) defer.returnValue(request_context)
def connectionMade(self): encoded = py23.b64encode_to_str(pickle.dumps(self.job_contexts)) self.transport.write(encoded) self.transport.write("\n")
async def process_message(message_sender, client_application_message, server_application_message, async_client_factory, encryption_context, server_response_id, system_auth_header, shard_id): device_id = py23.b64encode_to_str(message_sender) encryption_context = encryption_context if client_application_message.HasField(CLIENT_SINGLE_REQUEST): request_object = client_application_message.clientSingleRequest response_object = server_application_message.serverSingleResponse response_object.replyToMessageId = request_object.requestId response_object.serverVersion = str(app_version()) processor = process_request request_type = CLIENT_SINGLE_REQUEST elif client_application_message.HasField(CLIENT_SUBSCRIPTION_MESSAGE): request_object = client_application_message.clientSubscriptionMessage # pings have a special case response, really they could be in their own modular input if client_application_message.clientSubscriptionMessage.HasField(RequestType.CLIENT_SUBSCRIPTION_PING.value): response_object = server_application_message.serverSubscriptionPing else: response_object = server_application_message.serverSubscriptionResponse response_object.replyToMessageId = request_object.requestId response_object.serverVersion = str(app_version()) processor = process_subscription request_type = CLIENT_SUBSCRIPTION_MESSAGE else: LOGGER.warn("No suitable message type found client application request") return "device_id={}".format(device_id) response_object.requestId = server_response_id if request_object.HasField("runAs"): LOGGER.debug("run as credentials is present") auth_header = await parse_run_as_credentials(encryption_context, system_auth_header, async_client_factory, request_object.runAs) else: auth_header = parse_session_token(encryption_context, request_object.sessionToken) request_context = RequestContext(auth_header, device_id=device_id, raw_device_id=message_sender, request_id=request_object.requestId, current_user=auth_header.username, system_auth_header=system_auth_header, client_version=request_object.clientVersion, user_agent=request_object.userAgent, shard_id=shard_id) try: validate_client_version(request_object, response_object) await auth_header.validate(async_client_factory.splunk_client(), LOGGER, request_context) # Create a new ContextVar Context for this request to encapsulate the request_context to this specific request. # This is necessary otherwise the request_context variable will be set in the global namespace and different # might access each other's request context. request_ctx_var = contextvars.ContextVar(REQUEST_CONTEXT) request_ctx_var.set(request_context) ctx = contextvars.copy_context() should_send_response = await ctx.run(processor, request_context, encryption_context, request_object, response_object, async_client_factory) # If we aren't sending a response in this request clear the server_application_message if not should_send_response: server_application_message.Clear() except OperationHaltedError: server_application_message.ClearField('app_message') except SpacebridgeCompanionAppError as e: LOGGER.warn("CompanionAppError error=%s", e.message) e.set_proto(response_object) except SpacebridgeError as e: LOGGER.exception("SpacebridgeError during process_message") e.set_proto(response_object) except Exception as e: LOGGER.exception("Unhandled exception during process_message") response_object.error.code = common_pb2.Error.ERROR_UNKNOWN response_object.error.message = str(e) LOGGER.info('Finished processing message. {}'.format(request_context)) return request_context
def __repr__(self): user_pass = "******" % (self.username, self.password) return 'Basic {}'.format(py23.b64encode_to_str(user_pass.encode()))
async def handle_saml_mdm_request(self, user, request, system_authtoken, mdm_signing_bundle, body): """ Handles the MDM SAML Registration Request. Validates signature sent from client, validates session token, generates a JWT token, and sends it encrypted using splapp's keys and the client public key :param user: string provided by rest handler :param session_token: string :param system_authtoken: string :param mdm_signing_bundle: Object :param body: JSON :return: Reponse object with payload and status """ public_key = base64.b64decode( extract_parameter(body, PUBLIC_KEY_LABEL, BODY_LABEL)) mdm_signature = base64.b64decode( extract_parameter(body, MDM_SIGNATURE_LABEL, BODY_LABEL)) client_keys = EncryptionKeys(None, None, public_key, None) client_encryption_context = EncryptionContext(client_keys) try: valid_signature = sign_verify( SodiumClient(LOGGER.getChild("sodium_client")), base64.b64decode( mdm_signing_bundle['sign_public_key'].encode('utf8')), client_encryption_context.encrypt_public_key(), mdm_signature) except Exception as e: LOGGER.exception( "Exception verifying signature from client for user={}".format( user)) return { 'payload': { 'token': "", 'user': user, 'status': HTTPStatus.UNAUTHORIZED }, 'status': HTTPStatus.OK } async_splunk_client = self.async_client_factory.splunk_client() port_number = await get_http_port_number(async_splunk_client, system_authtoken) session_token = get_session_token_from_request(request, port_number) valid_request = await async_is_valid_session_token( user, session_token, async_splunk_client) LOGGER.info( "Received new mdm registration request by user={}".format(user)) if valid_signature and valid_request: try: credentials = SplunkJWTCredentials(user) credentials.load_jwt_token(SplunkAuthHeader(system_authtoken)) LOGGER.info("Successfully fetched jwt token") except Exception as e: LOGGER.exception( "Exception fetching jwt token for user={} with message={}". format(user, e)) return { 'payload': { 'token': "", 'user': user, 'status': HTTPStatus.UNPROCESSABLE_ENTITY }, 'status': HTTPStatus.OK } splapp_encryption_context = SplunkEncryptionContext( system_authtoken, constants.SPACEBRIDGE_APP_NAME, SodiumClient(LOGGER.getChild("sodium_client"))) jwt_credentials = credentials.get_credentials( ) if sys.version_info < (3, 0) else str.encode( credentials.get_credentials()) # Encrypt session token using splapp keys secured_session_token = splapp_encryption_context.secure_session_token( jwt_credentials) # Encrypt session token using client's given public key encrypted_jwt_token = encrypt_for_send( SodiumClient(LOGGER.getChild("sodium_client")), client_encryption_context.encrypt_public_key(), secured_session_token) base64_encrypted_jwt_token = py23.b64encode_to_str( encrypted_jwt_token) return { 'payload': { 'token': base64_encrypted_jwt_token, 'user': user, 'status': HTTPStatus.OK }, 'status': HTTPStatus.OK } else: LOGGER.info( "Error: Mismatched user={} and session token".format(user)) return { 'payload': { 'token': "", 'user': user, 'status': HTTPStatus.UNAUTHORIZED }, 'status': HTTPStatus.OK }
def post(self, request): """ Handler which generates and returns an mdm keypair """ # generate mdm credentials LOGGER.info("Generating MDM Credentials") system_authtoken = request[SYSTEM_AUTHTOKEN] key_bundle = _load_key_bundle(system_authtoken) [public_key, private_key] = self.sodium_client.sign_generate_keypair() now = int(datetime.now().strftime('%s')) response = {} response['message'] = [] status = HTTPStatus.OK try: # send public signing key to spacebridge send_mdm_signing_key_to_spacebridge(request[SESSION][AUTHTOKEN], public_key, key_bundle) except Exception as e: status = HTTPStatus.INTERNAL_SERVER_ERROR LOGGER.warn( "Failed to register mdm keys with spacebridge. error=%s", e) return { 'payload': { 'failed_save': True, 'message': e }, 'status': status, } # update key generation timestamp try: kvstore_service = kvstore(collection=USER_META_COLLECTION_NAME, session_key=request[SESSION][AUTHTOKEN], owner=request[SESSION][USER]) entry = {KEY: MDM_KEYPAIR_GENERATION_TIME, TIMESTAMP: now} kvstore_service.insert_or_update_item_containing_key(entry) except Exception as e: status = HTTPStatus.INTERNAL_SERVER_ERROR response['failed_timesave'] = True response['message'].append(e.message) # store to storage/passwords try: [_, created_public_key] = update_or_create_sensitive_data( request[SESSION][AUTHTOKEN], MDM_SIGN_PUBLIC_KEY, py23.b64encode_to_str(public_key)) except Exception as e: status = HTTPStatus.INTERNAL_SERVER_ERROR response['failed_public_localsave'] = True response['message'].append(str(e)) try: [_, created_private_key] = update_or_create_sensitive_data( request[SESSION][AUTHTOKEN], MDM_SIGN_PRIVATE_KEY, py23.b64encode_to_str(private_key)) except Exception as e: status = HTTPStatus.INTERNAL_SERVER_ERROR response['failed_private_localsave'] = True response['message'].append(str(e)) # don't pass back the message if we have no errors if not response['message']: del response['message'] response[SIGN_PUBLIC_KEY] = py23.b64encode_to_str(public_key) response[SIGN_PRIVATE_KEY] = py23.b64encode_to_str(private_key) response[TIMESTAMP] = now return { 'payload': response, 'status': status, }