async def update_global(request: Request, updated_global: GlobalVariable, global_var: UUID,
                        global_col: AsyncIOMotorCollection = Depends(get_mongo_c)):
    """
    Updates a specific Global Variable (fetched by id) and returns it.
    """
    walkoff_db = get_mongo_d(request)
    curr_user_id = await get_jwt_identity(request)

    old_global = await mongo_helpers.get_item(global_col, GlobalVariable, global_var)
    if not old_global:
        raise DoesNotExistException("update", "Global Variable", global_var)
    global_id = old_global.id_

    new_permissions = updated_global.permissions
    access_level = new_permissions.access_level

    to_update = await auth_check(old_global, curr_user_id, "update", walkoff_db)
    if to_update:
        if access_level == AccessLevel.CREATOR_ONLY:
            updated_global.permissions = await creator_only_permissions(curr_user_id)
        elif access_level == AccessLevel.EVERYONE:
            updated_global.permissions = await default_permissions(curr_user_id, walkoff_db, "global_variables")
        elif access_level == AccessLevel.ROLE_BASED:
            await append_super_and_internal(updated_global.permissions)
            updated_global.permissions.creator = curr_user_id

        # try:
        key = config.get_from_file(config.ENCRYPTION_KEY_PATH, mode='rb')
        updated_global.value = fernet_encrypt(key, updated_global.value)
        return await mongo_helpers.update_item(global_col, GlobalVariable, global_id, updated_global)
        # except Exception as e:
        #     logger.info(e)
        #     raise UniquenessException("global_variable", "update", updated_global.name)
    else:
        raise UnauthorizedException("update data for", "Global Variable", old_global.name)
async def read_all_globals(request: Request, to_decrypt: str = False,
                           global_col: AsyncIOMotorCollection = Depends(get_mongo_c),
                           page: int = 1):
    """
    Returns a list of all Global Variables currently loaded in WALKOFF.
    Pagination is currently not supported.
    """
    walkoff_db = get_mongo_d(request)
    curr_user_id = await get_jwt_identity(request)

    # Pagination is currently not supported.
    if page > 1:
        return []

    key = config.get_from_file(config.ENCRYPTION_KEY_PATH, mode='rb')
    query = await mongo_helpers.get_all_items(global_col, GlobalVariable)

    ret = []
    if to_decrypt == "false":
        return query
    else:
        for global_var in query:
            to_read = await auth_check(global_var, curr_user_id, "read", walkoff_db)
            if to_read:
                temp_var = deepcopy(global_var)
                temp_var.value = fernet_decrypt(key, global_var.value)
                ret.append(temp_var)

        return ret
Exemple #3
0
async def read_global(
    request: Request,
    global_var: UUID,
    to_decrypt: str = "false",
    global_col: AsyncIOMotorCollection = Depends(get_mongo_c)):
    """
    Returns the Global Variable for the specified id.
    """
    walkoff_db = get_mongo_d(request)
    curr_user_id = await get_jwt_identity(request)

    global_variable = await mongo_helpers.get_item(global_col, GlobalVariable,
                                                   global_var)

    to_read = await auth_check(global_variable, curr_user_id, "read",
                               walkoff_db)
    if to_read:
        if to_decrypt == "false":
            return global_variable.value
        else:
            key = config.get_from_file(config.ENCRYPTION_KEY_PATH)  #, 'rb')
            # for testing
            try:
                key = key.encode('utf-8')
                key = base64.b64encode(key)
            except:
                key = key
            return fernet_decrypt(key, global_variable.value)
    else:
        raise UnauthorizedException("read data for", "Global Variable",
                                    global_variable.name)
Exemple #4
0
async def read_all_globals(
    request: Request,
    to_decrypt: str = False,
    global_col: AsyncIOMotorCollection = Depends(get_mongo_c)):
    """
    Returns a list of all Global Variables currently loaded in WALKOFF.
    """
    walkoff_db = get_mongo_d(request)
    curr_user_id = await get_jwt_identity(request)

    key = config.get_from_file(config.ENCRYPTION_KEY_PATH)  #, 'rb')
    # for testing
    try:
        key = key.encode('utf-8')
        key = base64.b64encode(key)
    except:
        key = key
    query = await mongo_helpers.get_all_items(global_col, GlobalVariable)

    ret = []
    if to_decrypt == "false":
        return query
    else:
        for global_var in query:
            to_read = await auth_check(global_var, curr_user_id, "read",
                                       walkoff_db)
            if to_read:
                temp_var = deepcopy(global_var)
                temp_var.value = fernet_decrypt(key, global_var.value)
                ret.append(temp_var)

        return ret
Exemple #5
0
async def create_global(
    request: Request,
    new_global: GlobalVariable,
    global_col: AsyncIOMotorCollection = Depends(get_mongo_c)):
    """
    Creates a new Global Variable in WALKOFF and returns it.
    """
    walkoff_db = get_mongo_d(request)
    curr_user_id = await get_jwt_identity(request)

    permissions = new_global.permissions
    access_level = permissions.access_level
    if access_level == AccessLevel.CREATOR_ONLY:
        new_global.permissions = await creator_only_permissions(curr_user_id)
    elif access_level == AccessLevel.EVERYONE:
        new_global.permissions = await default_permissions(
            curr_user_id, walkoff_db, "global_variables")
    elif access_level == AccessLevel.ROLE_BASED:
        await append_super_and_internal(new_global.permissions)
        new_global.permissions.creator = curr_user_id
    try:
        key = config.get_from_file(config.ENCRYPTION_KEY_PATH)  #, 'rb')
        # for testing
        try:
            key = key.encode('utf-8')
            key = base64.b64encode(key)
        except:
            key = key
        new_global.value = fernet_encrypt(key, new_global.value)
        return await mongo_helpers.create_item(global_col, GlobalVariable,
                                               new_global)
    except Exception as e:
        logger.info(e)
        raise UniquenessException("global_variable", "create", new_global.name)
async def delete_global(request: Request, global_var: UUID,
                        global_col: AsyncIOMotorCollection = Depends(get_mongo_c)):
    """
    Deletes a specific Global Variable (fetched by id).
    """
    walkoff_db = get_mongo_d(request)
    curr_user_id = await get_jwt_identity(request)

    global_variable = await mongo_helpers.get_item(global_col, GlobalVariable, global_var)
    if not global_variable:
        raise DoesNotExistException("delete", "Global Variable", global_var)
    global_id = global_variable.id_

    to_delete = await auth_check(global_variable, curr_user_id, "delete", walkoff_db)
    if to_delete:
        return await mongo_helpers.delete_item(global_col, GlobalVariable, global_id)
    else:
        raise UnauthorizedException("delete data for", "Global Variable", global_variable.name)
Exemple #7
0
async def control_workflow(
    request: Request,
    execution,
    workflow_to_control: ControlWorkflow,
    workflow_status_col: AsyncIOMotorCollection = Depends(get_mongo_c)):
    """
    Pause, resume, or abort a workflow currently executing in WALKOFF.
    """
    execution = await mongo_helpers.get_item(workflow_status_col,
                                             WorkflowStatus,
                                             execution,
                                             id_key="execution_id")

    walkoff_db = get_mongo_d(request)
    workflow_col = walkoff_db.workflows
    curr_user_id = await get_jwt_identity(request)

    workflow_id = execution.workflow_id
    data = dict(workflow_to_control)
    status = data['status']

    workflow: WorkflowModel = await mongo_helpers.get_item(
        workflow_col, WorkflowModel, workflow_id)
    # workflow = workflow_getter(execution.workflow_id, workflow_status_col)
    # The resource factory returns the WorkflowStatus model but we want the string of the execution ID
    execution_id = str(execution.execution_id)

    to_execute = await auth_check(workflow,
                                  curr_user_id,
                                  "execute",
                                  walkoff_db=walkoff_db)
    # TODO: add in pause/resume here. Workers need to store and recover state for this
    if to_execute:
        if status.lower() == 'abort':
            logger.info(
                f"User '{(await get_jwt_claims(request)).get('username', None)}' aborting workflow: {execution_id}"
            )
            message = {
                "execution_id": execution_id,
                "status": status,
                "workflow": dict(workflow)
            }
            async with connect_to_aioredis_pool(config.REDIS_URI) as conn:
                await conn.smove(static.REDIS_PENDING_WORKFLOWS,
                                 static.REDIS_ABORTING_WORKFLOWS, execution_id)
                await conn.xadd(static.REDIS_WORKFLOW_CONTROL, message)

            return None, HTTPStatus.NO_CONTENT
        elif status.lower() == 'trigger':
            if execution.status not in (StatusEnum.PENDING,
                                        StatusEnum.EXECUTING,
                                        StatusEnum.AWAITING_DATA):
                raise InvalidInputException(
                    "workflow",
                    "trigger",
                    execution_id,
                    errors=[
                        "Workflow must be in a running state to accept triggers."
                    ])

            trigger_id = data.get('trigger_id')
            if not trigger_id:
                raise InvalidInputException(
                    "workflow",
                    "trigger",
                    execution_id,
                    errors=[
                        "ID of the trigger must be specified in trigger_id."
                    ])
            seen = False
            for trigger in workflow.triggers:
                if str(trigger.id_) == trigger_id:
                    seen = True

            if not seen:
                raise InvalidInputException(
                    "workflow",
                    "trigger",
                    execution_id,
                    errors=[
                        f"trigger_id {trigger_id} was not found in this workflow."
                    ])

            trigger_stream = f"{execution_id}-{trigger_id}:triggers"

            try:
                async with connect_to_aioredis_pool(config.REDIS_URI) as conn:
                    info = await conn.xinfo_stream(trigger_stream)
                stream_length = info["length"]
            except Exception:
                stream_length = 0

            if stream_length > 0:
                return InvalidInputException(
                    "workflow",
                    "trigger",
                    execution_id,
                    errors=[f"This trigger has already received data."])

            trigger_data = data.get('trigger_data')
            logger.info(
                f"User '{(await get_jwt_claims(request)).get('username', None)}' triggering workflow: {execution_id} at trigger "
                f"{trigger_id} with data {trigger_data}")
            async with connect_to_aioredis_pool(config.REDIS_URI) as conn:
                await conn.xadd(trigger_stream, {
                    execution_id:
                    message_dumps({"trigger_data": trigger_data})
                })

            return ({"trigger_stream": trigger_stream})
    else:
        return None
Exemple #8
0
async def execute_workflow(
    workflow_to_execute: ExecuteWorkflow,
    request: Request,
    workflow_status_col: AsyncIOMotorCollection = Depends(get_mongo_c)):
    """
    Executes a WALKOFF workflow.
    """
    walkoff_db = get_mongo_d(request)
    workflow_col = walkoff_db.workflows

    workflow_id = workflow_to_execute.workflow_id
    execution_id = workflow_to_execute.execution_id
    workflow: WorkflowModel = await mongo_helpers.get_item(
        workflow_col, WorkflowModel, workflow_id)
    # workflow = workflow_getter(workflow_id, workflow_status_col)
    # data = dict(workflow_to_execute)

    curr_user_id = await get_jwt_identity(request)

    to_execute = await auth_check(workflow,
                                  curr_user_id,
                                  "execute",
                                  walkoff_db=walkoff_db)
    if to_execute:
        if not workflow:
            raise DoesNotExistException("workflow", "execute", workflow_id)

        if not workflow.is_valid:
            raise InvalidInputException("workflow",
                                        "execute",
                                        workflow.id_,
                                        errors=workflow.errors)

        # workflow = dict(workflow)

        actions_by_id = {a.id_: a for a in workflow.actions}
        triggers_by_id = {t.id_: t for t in workflow.triggers}

        # TODO: Add validation to all overrides
        if "start" in workflow_to_execute:
            if workflow_to_execute.start in actions_by_id or workflow_to_execute.start in triggers_by_id:
                workflow.start = workflow_to_execute.start
            else:
                raise InvalidInputException(
                    "execute",
                    "workflow",
                    workflow.id_,
                    errors=[
                        "Start override must be an action or a trigger in this workflow."
                    ])

        if "workflow_variables" in workflow and "workflow_variables" in workflow_to_execute:
            # TODO: change these on the db model to be keyed by ID
            # Get workflow variables keyed by ID

            current_wvs = {wv.id_: wv for wv in workflow.workflow_variables}
            new_wvs = {
                wv['id_']: wv
                for wv in workflow_to_execute.workflow_variables
            }

            # Update workflow variables with new values, ignore ids that didn't already exist
            override_wvs = {
                id_: new_wvs[id_] if id_ in new_wvs else current_wvs[id_]
                for id_ in current_wvs
            }
            workflow.workflow_variables = list(override_wvs.values())

        if "parameters" in workflow_to_execute:
            if workflow_to_execute.start is not None:
                start_id = workflow_to_execute.start
            else:
                start_id = workflow.start
            # start_id = data.get("start", workflow.start)
            if start_id in actions_by_id:
                parameters_by_name = {
                    p.name: p
                    for p in actions_by_id[start_id].parameters
                }
                for parameter in workflow_to_execute.parameters:
                    parameters_by_name[parameter.name] = parameter
                actions_by_id[start_id].parameters = list(
                    parameters_by_name.values())
                workflow.actions = list(actions_by_id.values())
            else:
                raise InvalidInputException(
                    "workflow",
                    "execute",
                    workflow.id_,
                    errors=[
                        "Cannot override starting parameters for anything but an action."
                    ])

        try:
            # TODO: add check for workflow_col VALIDATION
            execution_id = await execute_workflow_helper(
                request=request,
                workflow_id=workflow_id,
                workflow_col=workflow_col,
                execution_id=execution_id,
                workflow=workflow,
                workflow_status_col=workflow_status_col)
            return {'execution_id': execution_id}
        except ValidationError as e:
            raise ImproperJSONException('workflow_status', 'create',
                                        workflow.name, e)
            # raise e.messages
    else:
        raise HTTPException(status_code=403, detail="Forbidden")