Exemple #1
0
def _load_wf_module_and_service(
        workflow: Workflow, wf_module_id: int,
        param: str) -> Tuple[WfModule, oauth.OAuthService]:
    """
    Load WfModule and OAuthService from the database, or raise.

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

    Raise ModuleVersion.DoesNotExist if the WfModule does not have the
    given param.

    Invoke this within a Workflow.cooperative_lock().
    """
    # raises WfModule.DoesNotExist
    wf_module = WfModule.live_in_workflow(workflow).get(pk=wf_module_id)

    # raise ModuleVersion.DoesNotExist if ModuleVersion was deleted
    module_version = wf_module.module_version
    if module_version is None:
        raise ModuleVersion.DoesNotExist
    for field in module_version.param_fields:
        if (isinstance(field, ParamSpec.Secret) and field.id_name == param and
            (isinstance(field.secret_logic, ParamSpec.Secret.Logic.Oauth1a) or
             isinstance(field.secret_logic, ParamSpec.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 wf_module, service
    else:
        raise SecretDoesNotExist(
            f"Param {param} does not point to an OAuth secret")
Exemple #2
0
    def get_workflow_as_delta_and_needs_render(self):
        """
        Return (apply-delta dict, needs_render), or raise Workflow.DoesNotExist

        needs_render is a (workflow_id, delta_id) pair.
        """
        with Workflow.authorized_lookup_and_cooperative_lock(
                "read",
                self.scope["user"],
                self.scope["session"],
                pk=self.workflow_id) as workflow_lock:
            workflow = workflow_lock.workflow
            request = RequestWrapper(self.scope["user"], self.scope["session"])
            ret = {
                "updateWorkflow": (WorkflowSerializer(workflow,
                                                      context={
                                                          "request": request
                                                      }).data)
            }

            tabs = list(workflow.live_tabs)
            ret["updateTabs"] = dict(
                (tab.slug, TabSerializer(tab).data) for tab in tabs)
            wf_modules = list(WfModule.live_in_workflow(workflow.id))
            ret["updateWfModules"] = dict(
                (str(wfm.id), WfModuleSerializer(wfm).data)
                for wfm in wf_modules)

            if workflow.are_all_render_results_fresh():
                needs_render = None
            else:
                needs_render = (workflow.id, workflow.last_delta_id)

            return (ret, needs_render)
Exemple #3
0
def _load_wf_module_and_service(
        workflow: Workflow, wf_module_id: int,
        param: str) -> Tuple[WfModule, oauth.OAuthService]:
    """
    Load WfModule and OAuthService from the database, or raise.

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

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

    Invoke this within a Workflow.cooperative_lock().
    """
    # raises WfModule.DoesNotExist
    wf_module = WfModule.live_in_workflow(workflow).get(pk=wf_module_id)

    # raises KeyError, RuntimeError
    try:
        module_zipfile = MODULE_REGISTRY.latest(wf_module.module_id_name)
    except KeyError:
        raise SecretDoesNotExist(
            f"Module {wf_module.module_id_name} does not exist")
    module_spec = module_zipfile.get_spec()
    for field in module_spec.param_fields:
        if (isinstance(field, ParamSpec.Secret) and field.id_name == param and
            (isinstance(field.secret_logic, ParamSpec.Secret.Logic.Oauth1a) or
             isinstance(field.secret_logic, ParamSpec.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 wf_module, service
    else:
        raise SecretDoesNotExist(
            f"Param {param} does not point to an OAuth secret")
Exemple #4
0
def load_database_objects(workflow_id: int, wf_module_id: int) -> DatabaseObjects:
    """
    Query WfModule info or raise WfModule.DoesNotExist/Workflow.DoesNotExist.

    Return tuple containing:

        * wf_module: cjwstate.models.WfModule
        * module_version: Optional[cjwstate.models.ModuleVersion]
        * stored_object: Optional[cjwstate.models.StoredObject]
        * input_cached_render_result: Optional[cjwstate.models.CachedRenderResult]
                                      of previous module in tab
    """
    with Workflow.lookup_and_cooperative_lock(id=workflow_id) as workflow_lock:
        # raise WfModule.DoesNotExist
        wf_module = WfModule.live_in_workflow(workflow_lock.workflow).get(
            id=wf_module_id
        )
        try:
            module_version = ModuleVersion.objects.latest(wf_module.module_id_name)
        except ModuleVersion.DoesNotExist:
            module_version = None
        try:
            stored_object = wf_module.stored_objects.get(
                stored_at=wf_module.stored_data_version
            )
        except StoredObject.DoesNotExist:
            stored_object = None
        try:
            # raise WfModule.DoesNotExist -- but we'll catch this one
            prev_module = wf_module.tab.live_wf_modules.get(order=wf_module.order - 1)
            input_crr = prev_module.cached_render_result  # may be None
        except WfModule.DoesNotExist:
            input_crr = None
        return DatabaseObjects(wf_module, module_version, stored_object, input_crr)
Exemple #5
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 WfModule.live_in_workflow(workflow)
            },
        )
        return WorkflowUpdateData(update, workflow.last_delta_id)
    def are_all_render_results_fresh(self):
        """Query whether all live WfModules are rendered."""
        from .WfModule import WfModule

        for wf_module in WfModule.live_in_workflow(self):
            if wf_module.cached_render_result is None:
                return False
        return True
Exemple #7
0
def load_database_objects(workflow_id: int,
                          wf_module_id: int) -> DatabaseObjects:
    """
    Query WfModule info.
    
    Raise `WfModule.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_lock:
        # raise WfModule.DoesNotExist
        wf_module = WfModule.live_in_workflow(
            workflow_lock.workflow).get(id=wf_module_id)

        # module_zipfile
        try:
            module_zipfile = MODULE_REGISTRY.latest(wf_module.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(
                    wf_module,
                    module_zipfile=module_zipfile)  # raise ModuleError
            except ModuleError as err:
                migrated_params_or_error = err

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

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

        return DatabaseObjects(
            wf_module,
            module_zipfile,
            migrated_params_or_error,
            stored_object,
            input_crr,
        )
Exemple #8
0
    def wrapper(request: HttpRequest, workflow_id: int, wf_module_slug: str,
                *args, **kwargs):
        auth_header = request.headers.get("Authorization", "")
        auth_header_match = AuthTokenHeaderRegex.match(auth_header)
        if not auth_header_match:
            return ErrorResponse(403,
                                 "authorization-bearer-token-not-provided")
        bearer_token = auth_header_match.group(1)

        try:
            with Workflow.lookup_and_cooperative_lock(
                    id=workflow_id) as workflow_lock:
                workflow = workflow_lock.workflow
                try:
                    wf_module = WfModule.live_in_workflow(workflow).get(
                        slug=wf_module_slug)
                except WfModule.DoesNotExist:
                    return ErrorResponse(404, "step-not-found")

                try:
                    module_zipfile = MODULE_REGISTRY.latest(
                        wf_module.module_id_name)
                except KeyError:
                    return ErrorResponse(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:
                    return ErrorResponse(400, "step-has-no-file-param")

                api_token = wf_module.file_upload_api_token
                if not api_token:
                    return ErrorResponse(403, "step-has-no-api-token")

                bearer_token_hash = hashlib.sha256(
                    bearer_token.encode("utf-8")).digest()
                api_token_hash = hashlib.sha256(
                    api_token.encode("utf-8")).digest()
                if bearer_token_hash != api_token_hash or bearer_token != api_token:
                    return ErrorResponse(403,
                                         "authorization-bearer-token-invalid")

                return f(
                    request,
                    workflow_lock,
                    wf_module,
                    file_param_id_name,
                    *args,
                    **kwargs,
                )
        except Workflow.DoesNotExist:
            return ErrorResponse(404, "workflow-not-found")
def _write_wf_module_position(workflow: Workflow, wf_module_id: int) -> None:
    """Write position in DB, or raise (Workflow|Tab|WfModule).DoesNotExist."""
    with workflow.cooperative_lock():  # raises Workflow.DoesNotExist
        # Raises WfModule.DoesNotExist, e.g. if tab.is_deleted
        wf_module = WfModule.live_in_workflow(workflow).get(pk=wf_module_id)
        tab = wf_module.tab

        tab.selected_wf_module_position = wf_module.order
        tab.save(update_fields=["selected_wf_module_position"])

        workflow.selected_tab_position = tab.position
        workflow.save(update_fields=["selected_tab_position"])
Exemple #10
0
def make_init_state(request, workflow=None, modules=None):
    """
    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.
    """
    ret = {}

    if workflow:
        try:
            with workflow.cooperative_lock():  # raise DoesNotExist on race
                ret["workflowId"] = workflow.id
                ret["workflow"] = WorkflowSerializer(workflow,
                                                     context={
                                                         "request": request
                                                     }).data

                tabs = list(workflow.live_tabs)
                ret["tabs"] = dict(
                    (str(tab.slug), TabSerializer(tab).data) for tab in tabs)

                wf_modules = list(WfModule.live_in_workflow(workflow))

                ret["wfModules"] = {
                    str(wfm.id): WfModuleSerializer(wfm).data
                    for wfm in wf_modules
                }
                workflow.last_viewed_at = timezone.now()
                workflow.save(update_fields=["last_viewed_at"])
        except Workflow.DoesNotExist:
            raise Http404("Workflow was recently deleted")

    if modules:
        modules_data_list = ModuleSerializer(modules, many=True).data
        ret["modules"] = dict([(str(m["id_name"]), m)
                               for m in modules_data_list])

    if request.user.is_authenticated:
        ret["loggedInUser"] = UserSerializer(request.user).data

    return ret
Exemple #11
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
            workflow.last_viewed_at = timezone.now()
            workflow.save(update_fields=["last_viewed_at"])

            state = clientside.Init(
                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 WfModule.live_in_workflow(workflow)
                },
                modules={
                    module_id: clientside.Module(
                        spec=module.get_spec(),
                        js_module=module.get_optional_js_module(),
                    )
                    for module_id, module in modules.items()
                },
            )
    except Workflow.DoesNotExist:
        raise Http404("Workflow was recently deleted")

    ctx = JsonizeContext(request.user, request.session, request.locale_id,
                         modules)
    return jsonize_clientside_init(state, ctx)
Exemple #12
0
def _load_wf_module(workflow: Workflow, wf_module_id: int) -> WfModule:
    """Returns a WfModule or raises HandlerError."""
    try:
        return WfModule.live_in_workflow(workflow).get(id=wf_module_id)
    except WfModule.DoesNotExist:
        raise HandlerError("DoesNotExist: WfModule not found")
Exemple #13
0
def _workflow_has_notifications(workflow_id: int) -> bool:
    """Detect whether a workflow sends email on changes."""
    return WfModule.live_in_workflow(workflow_id).filter(
        notifications=True).exists()