Example #1
0
def _step_delete_secret_and_build_delta(
    workflow: Workflow, step: Step, param: str
) -> Optional[clientside.Update]:
    """Write a new secret (or `None`) to `step`, or raise.

    Return a `clientside.Update`, or `None` if the database is not modified.

    Raise Workflow.DoesNotExist if the Workflow was deleted.
    """
    with workflow.cooperative_lock():  # raises Workflow.DoesNotExist
        try:
            step.refresh_from_db()
        except Step.DoesNotExist:
            return None  # no-op

        if step.secrets.get(param) is None:
            return None  # no-op

        step.secrets = dict(step.secrets)  # shallow copy
        del step.secrets[param]
        step.save(update_fields=["secrets"])

        return clientside.Update(
            steps={step.id: clientside.StepUpdate(secrets=step.secret_metadata)}
        )
Example #2
0
def _step_set_secret_and_build_delta(
        workflow: Workflow, step: Step, param: str,
        secret: str) -> Optional[clientside.Update]:
    """Write a new secret to `step`, or raise.

    Return a `clientside.Update`, or `None` if the database is not modified.

    Raise Workflow.DoesNotExist if the Workflow was deleted.
    """
    with workflow.cooperative_lock():  # raises Workflow.DoesNotExist
        try:
            step.refresh_from_db()
        except Step.DoesNotExist:
            return None  # no-op

        if step.secrets.get(param, {}).get("secret") == secret:
            return None  # no-op

        try:
            module_zipfile = MODULE_REGISTRY.latest(step.module_id_name)
        except KeyError:
            raise HandlerError(
                f"BadRequest: ModuleZipfile {step.module_id_name} does not exist"
            )
        module_spec = module_zipfile.get_spec()
        if not any(p.type == "secret" and p.secret_logic.provider == "string"
                   for p in module_spec.param_fields):
            raise HandlerError(
                "BadRequest: param is not a secret string parameter")

        created_at = datetime.datetime.now()
        created_at_str = (
            created_at.strftime("%Y-%m-%dT%H:%M:%S") + "." +
            created_at.strftime("%f")[0:3]  # milliseconds
            + "Z")

        step.secrets = {
            **step.secrets,
            param: {
                "name": created_at_str,
                "secret": secret
            },
        }
        step.save(update_fields=["secrets"])

        return clientside.Update(
            steps={
                step.id: clientside.StepUpdate(secrets=step.secret_metadata)
            })
Example #3
0
    def _get_workflow_as_clientside_update(self) -> WorkflowUpdateData:
        """Return (clientside.Update, delta_id).

        Raise Workflow.DoesNotExist if a race deletes the Workflow.
        """
        with self._lookup_requested_workflow_with_auth_and_cooperative_lock(
        ) as workflow:
            if self.scope["user"].is_anonymous:
                user = None
            else:
                user_id = self.scope["user"].id
                lock_user_by_id(user_id, for_write=False)
                user = query_clientside_user(user_id)

            update = clientside.Update(
                user=user,
                workflow=workflow.to_clientside(),
                tabs={
                    tab.slug: tab.to_clientside()
                    for tab in workflow.live_tabs
                },
                steps={
                    step.id: step.to_clientside()
                    for step in Step.live_in_workflow(workflow)
                },
            )
            return WorkflowUpdateData(update, workflow.last_delta_id)
Example #4
0
def write_to_rendercache(
    workflow: Workflow,
    step: Step,
    delta_id: int,
    table: pa.Table,
    errors: List[RenderError] = [],
    json: Dict[str, Any] = {},
) -> None:
    with arrow_table_context(table) as (path, table):
        result = LoadedRenderResult(
            path=path,
            table=table,
            columns=read_columns(table, full=False),
            errors=errors,
            json=json,
        )

        # use the caller-provided delta ID: no assertion
        old_last_relevant_delta_id = step.last_relevant_delta_id
        step.last_relevant_delta_id = delta_id
        try:
            cache_render_result(workflow, step, delta_id, result)
        finally:
            step.last_relevant_delta_id = old_last_relevant_delta_id
Example #5
0
def locked_and_loaded_step(
    workflow_id: int, step_slug: str
) -> ContextManager[Tuple[DbObjectCooperativeLock, Step, str]]:
    """Yield `WorkflowLock`, `step` and `file_param_id_name`.

    SECURITY: the caller may want to test the Step's `file_upload_api_token`.

    Raise UploadError(404, "workflow-not-found") on missing/deleted Workflow.

    Raise UploadError(404, "step-not-found") on missing/deleted Step.

    Raise UploadError(400, "step-module-deleted") on code-less Step.

    Raise UploadError(400, "step-has-no-file-param") on a Step with no File param.
    """
    try:
        with Workflow.lookup_and_cooperative_lock(
                id=workflow_id) as workflow_lock:
            workflow = workflow_lock.workflow
            try:
                step = Step.live_in_workflow(workflow).get(slug=step_slug)
            except Step.DoesNotExist:
                raise UploadError(404, "step-not-found")

            try:
                module_zipfile = MODULE_REGISTRY.latest(step.module_id_name)
            except KeyError:
                raise UploadError(400, "step-module-deleted")

            try:
                file_param_id_name = next(
                    iter(pf.id_name
                         for pf in module_zipfile.get_spec().param_fields
                         if pf.type == "file"))
            except StopIteration:
                raise UploadError(400, "step-has-no-file-param")

            yield workflow_lock, step, file_param_id_name
    except Workflow.DoesNotExist:
        raise UploadError(404, "workflow-not-found")
Example #6
0
def _load_step_by_slug_sync(workflow: Workflow, step_slug: str) -> Step:
    """Return a Step or raise HandlerError."""
    try:
        return Step.live_in_workflow(workflow).get(slug=step_slug)
    except Step.DoesNotExist:
        raise HandlerError("DoesNotExist: Step not found")
Example #7
0
def _load_step_by_id(workflow: Workflow, step_id: int) -> Step:
    """Return a Step or raises HandlerError."""
    try:
        return Step.live_in_workflow(workflow).get(id=step_id)
    except Step.DoesNotExist:
        raise HandlerError("DoesNotExist: Step not found")
Example #8
0
def _do_set_file_upload_api_token(step: Step, api_token: Optional[str]):
    step.file_upload_api_token = api_token
    step.save(update_fields=["file_upload_api_token"])
Example #9
0
def _do_set_notifications(scope, step: Step, notifications: bool):
    step.notifications = notifications
    step.save(update_fields=["notifications"])
Example #10
0
def _do_set_collapsed(step: Step, is_collapsed: bool):
    step.is_collapsed = is_collapsed
    step.save(update_fields=["is_collapsed"])
Example #11
0
 def test_step_has_same_api_token(self):
     step = Step(file_upload_api_token="good-api-token")
     raise_if_api_token_is_wrong(step, "good-api-token")
Example #12
0
 def test_step_has_different_api_token(self):
     step = Step(file_upload_api_token="good-api-token")
     with self.assertRaisesRegex(
             UploadError,
             "UploadError<403,authorization-bearer-token-invalid>"):
         raise_if_api_token_is_wrong(step, "bad-api-token")
Example #13
0
 def test_step_has_no_api_token(self):
     step = Step(file_upload_api_token=None)
     with self.assertRaisesRegex(UploadError,
                                 "UploadError<403,step-has-no-api-token>"):
         raise_if_api_token_is_wrong(step, "some-api-token")
Example #14
0
def _workflow_has_notifications(workflow_id: int) -> bool:
    """Detect whether a workflow sends email on changes."""
    return Step.live_in_workflow(workflow_id).filter(
        notifications=True).exists()