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)} )
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) })
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)
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
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")
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")
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")
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"])
def _do_set_notifications(scope, step: Step, notifications: bool): step.notifications = notifications step.save(update_fields=["notifications"])
def _do_set_collapsed(step: Step, is_collapsed: bool): step.is_collapsed = is_collapsed step.save(update_fields=["is_collapsed"])
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")
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")
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")
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()