Пример #1
0
def _load_step_and_service(workflow: Workflow, step_id: int,
                           param: str) -> Tuple[Step, oauth.OAuthService]:
    """Load Step and OAuthService from the database, or raise.

    Raise Step.DoesNotExist if the Step is deleted or missing.

    Raise SecretDoesNotExist if the Step does not have the given param.

    Invoke this within a Workflow.cooperative_lock().
    """
    # raises Step.DoesNotExist
    step = Step.live_in_workflow(workflow).get(pk=step_id)

    # raises KeyError, RuntimeError
    try:
        module_zipfile = MODULE_REGISTRY.latest(step.module_id_name)
    except KeyError:
        raise SecretDoesNotExist(
            f"Module {step.module_id_name} does not exist")
    module_spec = module_zipfile.get_spec()
    for field in module_spec.param_fields:
        if (isinstance(field, ParamField.Secret) and field.id_name == param and
            (isinstance(field.secret_logic, ParamField.Secret.Logic.Oauth1a) or
             isinstance(field.secret_logic, ParamField.Secret.Logic.Oauth2))):
            service_name = field.secret_logic.service
            service = oauth.OAuthService.lookup_or_none(service_name)
            if service is None:
                raise OauthServiceNotConfigured(
                    f'OAuth not configured for "{service_name}" service')
            return step, service
    else:
        raise SecretDoesNotExist(
            f"Param {param} does not point to an OAuth secret")
Пример #2
0
def _get_workflow_as_clientside_update(user, session,
                                       workflow_id: int) -> WorkflowUpdateData:
    """
    Return (clientside.Update, delta_id).

    Raise Workflow.DoesNotExist if a race deletes the Workflow.

    The purpose of this method is to hide races from users who disconnect
    and reconnect while changes are being made. It's okay for things to be
    slightly off, as long as users don't notice. (Long-term, we can build
    better a more-correct synchronization strategy.)
    """
    with Workflow.authorized_lookup_and_cooperative_lock(
            "read", user, session, pk=workflow_id) as workflow_lock:
        workflow = workflow_lock.workflow
        update = clientside.Update(
            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)
Пример #3
0
def make_init_state(request, workflow: Workflow,
                    modules: Dict[str, ModuleZipfile]) -> Dict[str, Any]:
    """Build a dict to embed as JSON in `window.initState` in HTML.

    Raise Http404 if the workflow disappeared.

    Side-effect: update workflow.last_viewed_at.
    """
    try:
        with workflow.cooperative_lock():  # raise DoesNotExist on race
            if request.user.is_anonymous:
                user = None
            else:
                lock_user_by_id(request.user.id, for_write=False)
                user = query_clientside_user(request.user.id)

            workflow.last_viewed_at = datetime.datetime.now()
            workflow.save(update_fields=["last_viewed_at"])

            state = clientside.Init(
                user=user,
                workflow=workflow.to_clientside(),
                tabs={
                    tab.slug: tab.to_clientside()
                    for tab in workflow.live_tabs
                },
                steps={
                    step.id: step.to_clientside(
                        force_module_zipfile=modules.get(step.module_id_name))
                    for step in Step.live_in_workflow(
                        workflow).prefetch_related("tab")
                },
                modules={
                    module_id: clientside.Module(
                        spec=module.get_spec(),
                        js_module=module.get_optional_js_module(),
                    )
                    for module_id, module in modules.items()
                },
                blocks={
                    block.slug: block.to_clientside()
                    for block in workflow.blocks.all()
                },
                settings={
                    "bigTableRowsPerTile": settings.BIG_TABLE_ROWS_PER_TILE,
                    "bigTableColumnsPerTile":
                    settings.BIG_TABLE_COLUMNS_PER_TILE,
                },
            )
    except Workflow.DoesNotExist:
        raise Http404("Workflow was recently deleted")

    ctx = JsonizeContext(request.locale_id, modules)
    return jsonize_clientside_init(state, ctx)
Пример #4
0
def _write_step_position(workflow: Workflow, step_id: int) -> None:
    """Write position in DB, or raise (Workflow|Tab|Step).DoesNotExist."""
    with workflow.cooperative_lock():  # raises Workflow.DoesNotExist
        # Raises Step.DoesNotExist, e.g. if tab.is_deleted
        step = Step.live_in_workflow(workflow).get(pk=step_id)
        tab = step.tab

        tab.selected_step_position = step.order
        tab.save(update_fields=["selected_step_position"])

        workflow.selected_tab_position = tab.position
        workflow.save(update_fields=["selected_tab_position"])
Пример #5
0
def load_database_objects(workflow_id: int, step_id: int) -> DatabaseObjects:
    """Query Step info.

    Raise `Step.DoesNotExist` or `Workflow.DoesNotExist` if the step was
    deleted.

    Catch a `ModuleError` from migrate_params() and return it as part of the
    `DatabaseObjects`.
    """
    with Workflow.lookup_and_cooperative_lock(id=workflow_id) as workflow:
        # raise Step.DoesNotExist
        step = Step.live_in_workflow(workflow).get(id=step_id)

        # module_zipfile
        try:
            module_zipfile = MODULE_REGISTRY.latest(step.module_id_name)
        except KeyError:
            module_zipfile = None

        # migrated_params_or_error
        if module_zipfile is None:
            migrated_params_or_error = {}
        else:
            try:
                migrated_params_or_error = cjwstate.params.get_migrated_params(
                    step, module_zipfile=module_zipfile)  # raise ModuleError
            except ModuleError as err:
                migrated_params_or_error = err

        # stored_object
        try:
            stored_object = step.stored_objects.get(
                stored_at=step.stored_data_version)
        except StoredObject.DoesNotExist:
            stored_object = None

        # input_crr
        try:
            # raise Step.DoesNotExist -- but we'll catch this one
            prev_module = step.tab.live_steps.get(order=step.order - 1)
            input_crr = prev_module.cached_render_result  # may be None
        except Step.DoesNotExist:
            input_crr = None

        return DatabaseObjects(
            step,
            module_zipfile,
            migrated_params_or_error,
            stored_object,
            input_crr,
        )
Пример #6
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_lock:
            workflow = workflow_lock.workflow
            update = clientside.Update(
                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)
Пример #7
0
def _load_workflow_and_step_sync(
    request: HttpRequest,
    workflow_id_or_secret_id: Union[int, str],
    step_slug: str,
    accessing: Literal["all", "chart", "table"],
) -> Tuple[Workflow, Step]:
    """Load (Workflow, Step) from database, or raise Http404 or PermissionDenied.

    `Step.tab` will be loaded. (`Step.tab.workflow_id` is needed to access the render
    cache.)

    To avoid PermissionDenied:

    * The workflow must be public; OR
    * The user must be workflow owner, editor or viewer; OR
    * The user must be workflow report-viewer and the step must be a chart or
      table in the report.
    """
    try:
        if isinstance(workflow_id_or_secret_id, int):
            search = {"id": workflow_id_or_secret_id}
            has_secret = False
        else:
            search = {"secret_id": workflow_id_or_secret_id}
            has_secret = True

        with Workflow.lookup_and_cooperative_lock(**search) as workflow_lock:
            workflow = workflow_lock.workflow
            if (has_secret or workflow.public
                    or workflow.request_authorized_owner(request)):
                need_report_auth = False
            elif request.user is None or request.user.is_anonymous:
                raise PermissionDenied()
            else:
                try:
                    acl_entry = workflow.acl.filter(
                        email=request.user.email).get()
                except AclEntry.DoesNotExist:
                    raise PermissionDenied()
                if acl_entry.role in {Role.VIEWER, Role.EDITOR}:
                    need_report_auth = False
                elif acl_entry.role == Role.REPORT_VIEWER:
                    need_report_auth = True
                else:
                    raise PermissionDenied()  # role we don't handle yet

            step = (Step.live_in_workflow(
                workflow.id).select_related("tab").get(slug=step_slug)
                    )  # or Step.DoesNotExist

            if need_report_auth:  # user is report-viewer
                if workflow.has_custom_report:
                    if (accessing == "chart" and
                            workflow.blocks.filter(step_id=step.id).exists()):
                        pass  # the step is a chart
                    elif (accessing == "table" and
                          workflow.blocks.filter(tab_id=step.tab_id).exists()
                          and not step.tab.live_steps.filter(
                              order__gt=step.order)):
                        pass  # step is a table (last step of a report-included tab)
                    else:
                        raise PermissionDenied()
                else:
                    # Auto-report: all Charts are allowed; everything else is not
                    try:
                        if accessing == "chart" and (MODULE_REGISTRY.latest(
                                step.module_id_name).get_spec().html_output):
                            pass
                        else:
                            raise PermissionDenied()
                    except KeyError:  # not a module
                        raise PermissionDenied()

            return workflow, step
    except (Workflow.DoesNotExist, Step.DoesNotExist):
        raise Http404()
Пример #8
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()
Пример #9
0
def _load_step_by_slug(workflow: Workflow, step_slug: str) -> Step:
    """Return a Step or raises HandlerError."""
    try:
        return Step.live_in_workflow(workflow).get(slug=step_slug)
    except Step.DoesNotExist:
        raise HandlerError("DoesNotExist: Step not found")
Пример #10
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")