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)
Esempio n. 3
0
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)
Esempio n. 5
0
    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
Esempio n. 6
0
    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])
Esempio n. 8
0
    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))
Esempio n. 9
0
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)
Esempio n. 11
0
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)
Esempio n. 13
0
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)
Esempio n. 14
0
 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,
        }