Beispiel #1
0
def authenticate_code(auth_code, encryption_context, resolve_app_name, config=SplunkConfig(), key_bundle=None):
    """
    Part 1/2 of the registration process
    Submit an auth code to space bridge, and retrieve the encryption credentials for the device associated to
    that auth code

    :param auth_code: auth code shown on mobile device
    :param encryption_context: EncryptionContext object. Can be a regular EncryptionContext or a subclass such
    as SplunkEncryptionContext depending on whether you want to run in standalone mode or not.
    :param resolve_app_name: A function that, given an app id, will return a human friendly app name
    :param config: CloudgatewaySdkConfig object
    :return: DeviceInfo object
    """

    raw_response = submit_auth_code(auth_code, encryption_context, config, key_bundle)
    sb_response_proto = parse_spacebridge_response(raw_response)

    encrypt_public_key = sb_response_proto.payload.publicKeyForEncryption
    sign_public_key = sb_response_proto.payload.publicKeyForSigning

    app_friendly_name = sb_response_proto.payload.appFriendlyName
    app_name = app_friendly_name if app_friendly_name else resolve_app_name(sb_response_proto.payload.appId)
    platform = sb_response_proto.payload.appPlatform

    device_encryption_info = DeviceInfo(encrypt_public_key,
                                        sign_public_key,
                                        sb_response_proto.payload.deviceId,
                                        encryption_context.generichash_hex(sign_public_key).upper()[:8],
                                        sb_response_proto.payload.appId,
                                        app_name=app_name,
                                        platform=platform)

    return device_encryption_info
Beispiel #2
0
async def fetch_device_info(device_id, async_kvstore_client,
                            system_auth_header):
    """
    Fetch the DeviceInfo for a particular device ic
    :param device_id:
    :param async_kvstore_client:
    :param system_auth_header:
    :return: cloudgateway.device.DeviceInfo
    """
    key_id = py23.urlsafe_b64encode_to_str(device_id)

    if key_id in __device_cache:
        return __device_cache[key_id]

    response = await async_kvstore_client.async_kvstore_get_request(
        DEVICE_PUBLIC_KEYS_COLLECTION_NAME,
        auth_header=system_auth_header,
        key_id=key_id)

    if response.code == HTTPStatus.OK:
        parsed = await response.json()
        result = DeviceInfo(base64.b64decode(parsed['encrypt_public_key']),
                            base64.b64decode(parsed['sign_public_key']),
                            device_id)

        __device_cache[key_id] = result
        return result
    else:
        raise KeyNotFoundError(key_id, response.code)
Beispiel #3
0
def fetch_server_credentials(auth_code, encryption_context, num_retries=10, config=SplunkConfig(), key_bundle=None):
    """Fetch server's encryption keys as well as session token. This is the last part of the registration process
    for the client side and needs to happen after the server side has finished the registration process on the
    server side. If the server side has not completed registration yet, Cloudgateway will wait up to 30s and
    return a 204. This api will continue to retry when it receives a 204 until it we exceed num_retries at which
    point it will return a max retries exceeded exception.

    Args:
        auth_code ([string]): 10-digit auth code
        encryption_context ([EncryptionContext]):
        num_retries ([int]): Number of times to retry. Only retries if Cloud gateway has not received registration
        from the server side.

    Returns:
        [(DeviceInfo, CredentialsBundle)]: Tuple where first element is a DeviceInfo object containing encryption
        information of the server side. The second element is a CredentialsBundle object which contains a session token
        and username which is used for authenticating with the server side and running splunk queries.
    """
    for i in range(num_retries):
        r = make_authentication_result_request(auth_code, encryption_context, config, key_bundle)
        try:
            payload = parse_authentication_result_response(r)
            break
        except CloudgatewayServerError as e:
            if e.status != 204:
                raise e

            if e.status == 204 and i == num_retries - 1:
                raise CloudgatewayMaxRetriesError("Server side device has not completed registration. Please try again",
                                                  204)

    signing_public_key = payload.deploymentPublicKeyForSigning
    encrypt_public_key = payload.deploymentPublicKeyForEncryption
    encrypted_credentials_bundle = payload.encryptedCredentialsBundle
    server_id = encryption_context.generichash_raw(signing_public_key)

    server_info = DeviceInfo(encrypt_public_key, signing_public_key, device_id=server_id)
    credentials_bundle = parse_credentials_bundle(encrypted_credentials_bundle, encryption_context)

    return server_info, credentials_bundle
Beispiel #4
0
def handle_confirmation(auth_code, user, session_token, system_authtoken, body):
    """
    Handler for the final DevicePairingConfirmationRequest call. This function:
        1. Authenticates the supplied username and password
        2. Retrieves temporary record from the kvstore
        3. Checks if app_type has been disabled since registration
        4. Makes the DevicePairingConfirmationRequest request to the server
        5. Creates a new permanent record for the device in the kvstore
        6. Deletes the temporary kvstore record

    :param auth_code: User-entered authorization code to be returned to Spacebridge
    :param body: Parsed JSON body of the incoming POST request
    :param kvstore_unconfirmed: Access object for the temporary registration kvstore
    :param system_authtoken: System-level access token for writing to the kvstore
    :return: Success message
    """

    # Authenticates the supplied username and password
    kvstore_temp = KvStore(constants.UNCONFIRMED_DEVICES_COLLECTION_NAME, system_authtoken, owner=user)
    encryption_context = SplunkEncryptionContext(system_authtoken, constants.SPACEBRIDGE_APP_NAME)
    username = extract_parameter(body, USERNAME_LABEL, BODY_LABEL)
    password = extract_parameter(body, PASSWORD_LABEL, BODY_LABEL)

    try:
        # use what Splunk thinks the username is to generate the session token
        auth =  BasicAuthHeader(username, password)
        content = get_current_context(auth)
        username = content[constants.ENTRY][0][constants.CONTENT][constants.USERNAME]
    except SpacebridgePermissionsError as e:
        LOGGER.exception('Invalid credentials passed to current-context API')
        raise e


    LOGGER.info('Received new registration confirmation request by user=%s for device_owner=%s' % (user, username))

    # Retrieves temporary record from the kvstore
    temp_key = extract_parameter(body, KVSTORE_TEMPORARY_ID_LABEL, BODY_LABEL)
    r, temp_record = kvstore_temp.get_item_by_key(temp_key)
    temp_record = json.loads(temp_record)

    device_id = temp_record[DEVICE_ID_LABEL]
    device_id_raw = base64.b64decode(device_id)

    device_registration = {'_key': py23.urlsafe_b64encode_to_str(device_id_raw)}
    device_public_keys = {'_key': py23.urlsafe_b64encode_to_str(device_id_raw)}

    for k in temp_record.keys():
        if k in DEVICE_REGISTRATION_ATTRS:
            device_registration[k] = temp_record[k]
        if k in DEVICE_PUBLIC_KEYS_ATTRS:
            device_public_keys[k] = temp_record[k]


    # Checks if app_type has been disabled since registration
    app_name = temp_record[DEVICE_TYPE_LABEL]

    if not retrieve_state_of_app(app_name, system_authtoken):
        disabled_message = 'Registration Error: Application type app_name="%s" is disabled' % app_name
        LOGGER.info(disabled_message)
        return {
            'payload': {
                'message': disabled_message,
                'app_name': app_name,
            },
            'status': 422,
        }

    device_encryption_info = DeviceInfo(
        base64.b64decode(temp_record['encrypt_public_key']),
        base64.b64decode(temp_record['sign_public_key']),
        base64.b64decode(temp_record['device_id']),
        "NA",
        app_id=temp_record['app_id'],
        app_name=temp_record['device_type']
    )

    deployment_friendly_name = get_deployment_friendly_name(system_authtoken)

    try:
        credentials = SplunkJWTCredentials(username, password=password)
        credentials.load_jwt_token(SplunkAuthHeader(session_token))
        LOGGER.info("Successfully fetched jwt token")
    except Exception as e:
        LOGGER.info("Failed to fetch jwt token with message={}. Using basic credentials instead.".format(e))
        credentials = SimpleUserCredentials(username, password)

    pair_device(auth_code, credentials, device_encryption_info, encryption_context,
                server_name=deployment_friendly_name, config=config, server_app_id=constants.SPLAPP_APP_ID)

    # Creates a new permanent record for the device in the kvstore
    kvstore_user = KvStore(constants.REGISTERED_DEVICES_COLLECTION_NAME, system_authtoken, owner=username)
    kvstore_user.insert_single_item(device_registration)

    # Adds the user to the list of users with registered devices, if not already there
    kvstore_users = KvStore(constants.REGISTERED_USERS_COLLECTION_NAME, system_authtoken)
    kvstore_users.insert_or_update_item_containing_key({'_key': username})

    kvstore_nobody = KvStore(constants.DEVICE_PUBLIC_KEYS_COLLECTION_NAME, system_authtoken)
    kvstore_nobody.insert_single_item(device_public_keys)

    # Deletes the temporary kvstore record
    kvstore_temp.delete_item_by_key(temp_key)

    LOGGER.info('Device registration confirmed. Device with device_name=\"%s\" was recorded in the kvstore.' %
                temp_record[DEVICE_NAME_LABEL])

    return {
        'payload': 'Device registration successful',
        'status': 201,
    }
async def handle_mdm_authentication_request(mdm_auth_request_proto,
                                            encryption_context, server_context,
                                            logger, config, request_id):
    """
    Takes a MDM Auth Request proto, decrypts the encrypted credentials bundle, validates the credentials, persists
    device information to the server and sends cloudgateway a confirmation result message
    Args:
        mdm_auth_request_proto (MdmAuthenticationRequest proto): request from the client to perform MDM registration
        encryption_context (EncryptionContext):
        server_context (ServerContext): object which specifies how mdm registration should be validated and how
            credentials should be persisted to the server
        logger (Logger): logger class to handle logging

    Returns:

    """

    logger.info(
        "Parsing MDM Authentication Request, request_id={}".format(request_id))
    client_credentials = sb_common_pb2.MdmAuthenticationRequest.ClientCredentials(
    )
    client_credentials.ParseFromString(
        mdm_auth_request_proto.clientCredentials)
    mdm_signature = mdm_auth_request_proto.mdmSignature
    client_signature = mdm_auth_request_proto.clientSignature

    try:
        logger.debug(
            "Validating MDM signature MDM request message, request_id={}".
            format(request_id))
        mdm_signing_key = await server_context.get_mdm_signing_key()

        if not sign_verify(encryption_context.sodium_client, mdm_signing_key,
                           mdm_auth_request_proto.clientCredentials,
                           mdm_signature):
            raise CloudgatewayMdmRegistrationError(
                CloudgatewayMdmRegistrationError.errortype.unknown_error,
                "mdm signature validation failed")

        logger.debug(
            "Validating registration version={}, request_id={}".format(
                client_credentials.registrationVersion, request_id))
        if client_credentials.registrationVersion != MDM_REGISTRATION_VERSION:
            raise CloudgatewayMdmRegistrationError(
                CloudgatewayMdmRegistrationError.ErrorType.UNKNOWN_ERROR,
                "Incompatible Mdm Registration Version. Expected={}".format(
                    MDM_REGISTRATION_VERSION))

        encrypted_credentials_bundle = client_credentials.encryptedCredentialsBundle

        credentials_bundle = mdm.parse_mdm_encrypted_credentials_bundle(
            encrypted_credentials_bundle, encryption_context)
        client_id = credentials_bundle.registeringAppId
        username = credentials_bundle.username
        password = credentials_bundle.password
        encrypt_public_key = credentials_bundle.publicKeyForEncryption
        sign_public_key = credentials_bundle.publicKeyForSigning
        login_type = credentials_bundle.loginType
        user_session_token = credentials_bundle.sessionToken  # JWT session token sent by the client after MDM SAML
        friendly_name = credentials_bundle.registeringAppFriendlyName
        platform = credentials_bundle.registeringAppPlatform

        logger.debug(
            "Validating publicKey signature of MDM request message, request_id={}"
            .format(request_id))

        if not sign_verify(encryption_context.sodium_client, sign_public_key,
                           mdm_auth_request_proto.clientCredentials,
                           client_signature):
            raise CloudgatewayMdmRegistrationError(
                CloudgatewayMdmRegistrationError.ErrorType.UNKNOWN_ERROR,
                "client signature validation failed")

        device_info = DeviceInfo(encrypt_public_key,
                                 sign_public_key,
                                 device_id=make_device_id(
                                     encryption_context, sign_public_key),
                                 app_id=client_id,
                                 client_version="",
                                 app_name=friendly_name,
                                 platform=platform)

        if login_type == constants.SAML:
            encrypted_session_token = user_session_token
            raw_token = base64.b64decode(encrypted_session_token)
            decrypted_session_token = decrypt_session_token(
                encryption_context.sodium_client, raw_token,
                encryption_context.encrypt_public_key(),
                encryption_context.encrypt_private_key())
            session_jsn = json.loads(decrypted_session_token)
            token_type = http_pb2.TokenType.Value('JWT')
            token_expires_at = calculate_token_info(
                session_jsn['token'])['exp']
        else:
            await server_context.validate(username, password, device_info)

            logger.debug(
                "Server validated mdm registration request. request_id={}".
                format(request_id))

            session_token = await server_context.create_session_token(
                username, password)

            encrypted_session_token = encryption_context.secure_session_token(
                session_token)
            token_type = http_pb2.TokenType.Value('SESSION')
            token_expires_at = 0

        server_version = await server_context.get_server_version()

        logger.debug("Server returned server_version={}, request_id={}".format(
            server_version, request_id))

        deployment_name = await server_context.get_deployment_name()

        logger.debug(
            "Server returned deployment_name={}, request_id={}".format(
                deployment_name, request_id))

        server_type_id = await server_context.get_server_type()
        env_metadata = await server_context.get_environment_meta(
            device_info, username)

        pairing_info = mdm.build_pairing_info(encrypted_session_token,
                                              credentials_bundle.username,
                                              server_version,
                                              deployment_name,
                                              server_type_id,
                                              token_type,
                                              token_expires_at,
                                              env_metadata=env_metadata)
        confirmation_result = mdm.build_successful_confirmation_result(
            pairing_info)

        await server_context.persist_device_info(device_info, username)

        logger.info(
            "Successfully persisted device registration information, request_id={}"
            .format(request_id))

    except CloudgatewayMdmRegistrationError as e:
        logger.exception(
            "MDM registration error occurred={}, request_id={}".format(
                e, request_id))
        confirmation_result = mdm.build_error_confirmation_result(e.to_proto())
    except Exception as e:
        logger.exception(
            "Unexpected error occurred during MDM registration={}, request_id={}"
            .format(e, request_id))
        error = http_pb2.HttpError()
        error.code = http_pb2.HttpError.ERROR_UNKNOWN
        error.message = str(e)
        confirmation_result = mdm.build_error_confirmation_result(error)

    mdm_authentication_confirmation_request = mdm.build_mdm_authentication_confirmation_request(
        confirmation_result, encryption_context, device_info)

    r = await mdm.async_send_confirmation_result(
        mdm_authentication_confirmation_request, encryption_context,
        server_context.async_spacebridge_client)
    resp = await r.text()

    logger.info(
        "Completed MDM Authentication Request with response={}, code={}, request_id={}"
        .format(resp, r.code, request_id))

    return mdm_authentication_confirmation_request
def register_device(auth_code, user, system_authtoken, temp_key):
    """
    Handler for the final DevicePairingConfirmationRequest call. This function:
        2. Retrieves temporary record from the kvstore
        3. Checks if app_type has been disabled since registration
        4. Makes the DevicePairingConfirmationRequest request to the server
        5. Creates a new permanent record for the device in the kvstore
        6. Deletes the temporary kvstore record

    :param auth_code: User-entered authorization code to be returned to Spacebridge
    :param body: Parsed JSON body of the incoming POST request
    :param kvstore_unconfirmed: Access object for the temporary registration kvstore
    :param system_authtoken: System-level access token for writing to the kvstore
    :return: Success message
    """

    kvstore_temp = KvStore(constants.UNCONFIRMED_DEVICES_COLLECTION_NAME,
                           system_authtoken,
                           owner=user)
    encryption_context = SplunkEncryptionContext(
        system_authtoken, constants.SPACEBRIDGE_APP_NAME)

    LOGGER.info('Received new registration confirmation request by user=%s' %
                (user))

    # Retrieves temporary record from the kvstore
    r, temp_record = kvstore_temp.get_item_by_key(temp_key)
    temp_record = json.loads(temp_record)

    device_id = temp_record[DEVICE_ID_LABEL]
    device_id_raw = base64.b64decode(device_id)

    device_registration = {'_key': base64.urlsafe_b64encode(device_id_raw)}
    device_public_keys = {'_key': base64.urlsafe_b64encode(device_id_raw)}

    for k in temp_record.keys():
        if k in DEVICE_REGISTRATION_ATTRS:
            device_registration[k] = temp_record[k]
        if k in DEVICE_PUBLIC_KEYS_ATTRS:
            device_public_keys[k] = temp_record[k]

    # Checks if app_type has been disabled since registration
    app_name = temp_record[DEVICE_TYPE_LABEL]

    if not retrieve_state_of_app(app_name, system_authtoken):
        disabled_message = 'Registration Error: Application type app_name="%s" is disabled' % app_name
        LOGGER.info(disabled_message)
        return {
            'payload': {
                'message': disabled_message,
                'app_name': app_name,
            },
            'status': 422,
        }

    device_encryption_info = DeviceInfo(
        base64.b64decode(temp_record['encrypt_public_key']),
        base64.b64decode(temp_record['sign_public_key']),
        base64.b64decode(temp_record['device_id']),
        "NA",
        app_id=temp_record['app_id'],
        app_name=temp_record['device_type'])

    deployment_friendly_name = get_deployment_friendly_name(system_authtoken)
    pair_device(auth_code,
                SimpleUserCredentials("dummy", "dummy"),
                device_encryption_info,
                encryption_context,
                server_name=deployment_friendly_name,
                config=config)

    # Creates a new permanent record for the device in the kvstore
    kvstore_user = KvStore(constants.REGISTERED_DEVICES_COLLECTION_NAME,
                           system_authtoken,
                           owner=user)
    kvstore_user.insert_single_item(device_registration)

    # Adds the user to the list of users with registered devices, if not already there
    kvstore_users = KvStore(constants.REGISTERED_USERS_COLLECTION_NAME,
                            system_authtoken)
    kvstore_users.insert_or_update_item_containing_key({'_key': user})

    kvstore_nobody = KvStore(constants.DEVICE_PUBLIC_KEYS_COLLECTION_NAME,
                             system_authtoken)
    kvstore_nobody.insert_single_item(device_public_keys)

    # Deletes the temporary kvstore record
    kvstore_temp.delete_item_by_key(temp_key)

    LOGGER.info(
        'Device registration confirmed. Device with device_name=\"%s\" was recorded in the kvstore.'
        % temp_record[DEVICE_NAME_LABEL])

    return {
        'payload': 'Device registration successful',
        'status': 201,
    }
Beispiel #7
0
def handle_mdm_authentication_request(mdm_auth_request_proto, encryption_context,
                                      server_context, logger, config, request_id):
    """
    Takes a MDM Auth Request proto, decrypts the encrypted credentials bundle, validates the credentials, persists
    device information to the server and sends cloudgateway a confirmation result message
    Args:
        mdm_auth_request_proto (MdmAuthenticationRequest proto): request from the client to perform MDM registration
        encryption_context (EncryptionContext):
        server_context (ServerContext): object which specifies how mdm registration should be validated and how
            credentials should be persisted to the server
        logger (Logger): logger class to handle logging

    Returns:

    """

    logger.info("Parsing MDM Authentication Request, request_id={}".format(request_id))
    client_credentials = sb_common_pb2.MdmAuthenticationRequest.ClientCredentials()
    client_credentials.ParseFromString(mdm_auth_request_proto.clientCredentials)
    mdm_signature = mdm_auth_request_proto.mdmSignature
    client_signature = mdm_auth_request_proto.clientSignature

    try:
        logger.debug("Validating MDM signature MDM request message, request_id={}".format(request_id))
        mdm_signing_key = yield add_error_back(defer.maybeDeferred(server_context.get_mdm_signing_key),
                                               logger=logger)
        if not sign_verify(encryption_context.sodium_client, mdm_signing_key,
                           mdm_auth_request_proto.clientCredentials, mdm_signature):
            raise CloudgatewayMdmRegistrationError(CloudgatewayMdmRegistrationError.errortype.unknown_error,
                                                   "mdm signature validation failed")

        logger.debug("Validating registration version={}, request_id={}"
                     .format(client_credentials.registrationVersion, request_id))
        if client_credentials.registrationVersion != MDM_REGISTRATION_VERSION:
            raise CloudgatewayMdmRegistrationError(CloudgatewayMdmRegistrationError.ErrorType.UNKNOWN_ERROR,
                                                   "Incompatible Mdm Registration Version. Expected={}"
                                                   .format(MDM_REGISTRATION_VERSION))

        encrypted_credentials_bundle = client_credentials.encryptedCredentialsBundle

        credentials_bundle = mdm.parse_mdm_encrypted_credentials_bundle(encrypted_credentials_bundle,
                                                                        encryption_context)
        client_id = credentials_bundle.registeringAppId
        username = credentials_bundle.username
        password = credentials_bundle.password
        encrypt_public_key = credentials_bundle.publicKeyForEncryption
        sign_public_key = credentials_bundle.publicKeyForSigning
        login_type = credentials_bundle.loginType
        user_session_token = credentials_bundle.sessionToken # JWT session token sent by the client after MDM SAML

        logger.debug("Validating publicKey signature of MDM request message, request_id={}".format(request_id))

        if not sign_verify(encryption_context.sodium_client, sign_public_key, mdm_auth_request_proto.clientCredentials,
                           client_signature):
            raise CloudgatewayMdmRegistrationError(CloudgatewayMdmRegistrationError.ErrorType.UNKNOWN_ERROR,
                                                   "client signature validation failed")

        device_info = DeviceInfo(encrypt_public_key, sign_public_key,
                                 device_id=make_device_id(encryption_context, sign_public_key),
                                 app_id=client_id, client_version="")

        encrypted_session_token = None
        if login_type == constants.SAML:
            encrypted_session_token = user_session_token
        else:
            yield add_error_back(defer.maybeDeferred(server_context.validate, username, password, device_info),
                                 logger=logger)

            logger.debug("Server validated mdm registration request. request_id={}".format(request_id))

            session_token = yield add_error_back(defer.maybeDeferred(server_context.create_session_token, username, password),
                logger=logger)

            encrypted_session_token = encryption_context.secure_session_token(session_token)

        server_version = yield add_error_back(defer.maybeDeferred(server_context.get_server_version),
                                              logger=logger)

        logger.debug("Server returned server_version={}, request_id={}".format(server_version, request_id))

        deployment_name = yield add_error_back(defer.maybeDeferred(server_context.get_deployment_name),
                                               logger=logger)

        logger.debug("Server returned deployment_name={}, request_id={}".format(deployment_name, request_id))

        server_type_id = yield add_error_back(defer.maybeDeferred(server_context.get_server_type),
                                               logger=logger)

        pairing_info = mdm.build_pairing_info(encrypted_session_token, credentials_bundle.username, server_version,
                                              deployment_name, server_type_id)
        confirmation_result = mdm.build_successful_confirmation_result(pairing_info)

        yield add_error_back(defer.maybeDeferred(server_context.persist_device_info, device_info, username),
                             logger=logger)

        logger.info("Successfully persisted device registration information, request_id={}".format(request_id))

    except CloudgatewayMdmRegistrationError as e:
        logger.exception("MDM registration error occurred={}, request_id={}".format(e, request_id))
        confirmation_result = mdm.build_error_confirmation_result(e.to_proto())
    except Exception as e:
        logger.exception("Unexpected error occurred during MDM registration={}, request_id={}".format(e, request_id))
        error = http_pb2.HttpError()
        error.code = http_pb2.HttpError.ERROR_UNKNOWN
        error.message = str(e)
        confirmation_result = mdm.build_error_confirmation_result(error)

    mdm_authentication_confirmation_request = mdm.build_mdm_authentication_confirmation_request(confirmation_result,
                                                                                                encryption_context,
                                                                                                device_info)

    r = yield mdm.async_send_confirmation_result(mdm_authentication_confirmation_request, encryption_context,
                                                 server_context.async_spacebridge_client)
    resp = yield r.content()

    logger.info("Completed MDM Authentication Request with response={}, code={}, request_id={}"
                .format(resp, r.code, request_id))

    defer.returnValue(mdm_authentication_confirmation_request)
Beispiel #8
0
def handle_saml_confirmation(auth_code, user, session_token, system_authtoken,
                             body):
    """
    Handler for the final DevicePairingConfirmationRequest call. This function:
        1. Authenticates the supplied user name
        2. Retrieves temporary record from the kvstore
        3. Checks if app_type has been disabled since registration
        4. Makes the DevicePairingConfirmationRequest request to the server
        5. Creates a new permanent record for the device in the kvstore
        6. Deletes the temporary kvstore record

    :param auth_code: User-entered authorization code to be returned to Spacebridge
    :param user: User provided by rest handler
    :param body: Parsed JSON body of the incoming POST request
    :param kvstore_unconfirmed: Access object for the temporary registration kvstore
    :param system_authtoken: System-level access token for writing to the kvstore
    :return: Success message
    """

    # Authenticates the supplied user name
    kvstore_temp = KvStore(constants.UNCONFIRMED_DEVICES_COLLECTION_NAME,
                           system_authtoken,
                           owner=user)
    encryption_context = SplunkEncryptionContext(
        system_authtoken, constants.SPACEBRIDGE_APP_NAME)

    LOGGER.info(
        'Received new registration confirmation request by user={}'.format(
            user))

    # Retrieves temporary record from the kvstore
    temp_key = extract_parameter(body, KVSTORE_TEMPORARY_ID_LABEL, BODY_LABEL)
    r, temp_record = kvstore_temp.get_item_by_key(temp_key)
    temp_record = json.loads(temp_record)

    device_id = temp_record[DEVICE_ID_LABEL]
    device_id_raw = base64.b64decode(device_id)

    device_registration = {
        constants.KEY: py23.urlsafe_b64encode_to_str(device_id_raw)
    }
    device_public_keys = {
        constants.KEY: py23.urlsafe_b64encode_to_str(device_id_raw)
    }

    for k in temp_record.keys():
        if k in DEVICE_REGISTRATION_ATTRS:
            device_registration[k] = temp_record[k]
        if k in DEVICE_PUBLIC_KEYS_ATTRS:
            device_public_keys[k] = temp_record[k]

    # Checks if app_type has been disabled since registration
    app_name = temp_record[DEVICE_TYPE_LABEL]

    device_encryption_info = DeviceInfo(
        base64.b64decode(temp_record['encrypt_public_key']),
        base64.b64decode(temp_record['sign_public_key']),
        base64.b64decode(temp_record['device_id']),
        "NA",
        app_id=temp_record['app_id'],
        app_name=temp_record['device_type'])

    deployment_friendly_name = get_deployment_friendly_name(system_authtoken)

    valid_request = is_valid_session_token(user, session_token)
    if valid_request:
        try:
            credentials = SplunkJWTCredentials(user)
            credentials.load_jwt_token(SplunkAuthHeader(system_authtoken))
            LOGGER.info(
                "Successfully fetched jwt token for SAML auth user with username={}"
                .format(user))
        except Exception as e:
            LOGGER.info(
                "Failed to fetch jwt token for user={} with message={}".format(
                    user, e.message))
            jwt_error_message = 'Registration Error: Failed to fetch jwt token for user={}'.format(
                user)
            return {
                'payload': {
                    'message': jwt_error_message,
                    'app_name': app_name,
                },
                'status': 422,
            }

        pair_device(auth_code,
                    credentials,
                    device_encryption_info,
                    encryption_context,
                    server_name=deployment_friendly_name,
                    config=config,
                    server_app_id=constants.SPLAPP_APP_ID)

        # Creates a new permanent record for the device in the kvstore
        kvstore_user = KvStore(constants.REGISTERED_DEVICES_COLLECTION_NAME,
                               system_authtoken,
                               owner=user)
        kvstore_user.insert_single_item(device_registration)

        # Adds the user to the list of users with registered devices, if not already there
        kvstore_users = KvStore(constants.REGISTERED_USERS_COLLECTION_NAME,
                                system_authtoken)
        kvstore_users.insert_or_update_item_containing_key(
            {constants.KEY: user})

        kvstore_nobody = KvStore(constants.DEVICE_PUBLIC_KEYS_COLLECTION_NAME,
                                 system_authtoken)
        kvstore_nobody.insert_single_item(device_public_keys)

        # Deletes the temporary kvstore record
        kvstore_temp.delete_item_by_key(temp_key)

        LOGGER.info(
            'Device registration confirmed. Device with device_name=\"%s\" was recorded in the kvstore.'
            % temp_record[DEVICE_NAME_LABEL])
    else:
        LOGGER.info("Error: Mismatched user={} and session token".format(user))
        jwt_error_message = 'Registration Error: Failed to fetch jwt token for user={}'.format(
            user)
        return {
            'payload': {
                'message': jwt_error_message,
                'app_name': app_name,
            },
            'status': 422,
        }

    return {
        'payload': 'Device registration successful',
        'status': 201,
    }