ores.refdata.sandbox
Table of Contents
A sandbox is a space where actions have no effect outside it: users import samples, try what-ifs and run reports there without touching official data (see Sandbox).
The anchor is the node of the official portfolio tree the sandbox belongs to. It decides who may open the sandbox and who may see it; it does not place the sandbox in the official tree. The sandbox's own portfolios carry its id, and a portfolio always shares its parent's sandbox, so no official portfolio contains a sandbox portfolio.
Opening a sandbox needs the open_sandbox portfolio right at
an official anchor; the check runs again when the owner changes.
ores_refdata_account_sees_sandbox_fn answers whether an account may see a
sandbox: its owner always may; anyone with read at the anchor may when it
is shared; its members may when it is shared with members. A restrictive
row-level security policy on portfolios applies it to the session's actor
through ores_refdata_actor_sees_sandbox_fn, so a sandbox's portfolios are
read and written only by those who may see the sandbox, and a session with
no actor sees none of them.
Closing a sandbox does not close its portfolios, as for every soft foreign key in the schema. Handing a sandbox to a new owner needs the right at the anchor on the new owner's side; the new owner's consent is not recorded.
1. Flags
2. Columns
2.1. id
UUID uniquely identifying this sandbox.
Surrogate key for the sandbox record.
2.2. name
The sandbox's name.
Unique within the tenant.
std::string("SB-") + std::string(faker::string::alphanumeric(10))
2.3. purpose
What the sandbox is for.
experiment, sample or import.
std::string("experiment")
2.4. anchor_portfolio_id
The official portfolio node the sandbox belongs to.
Fixed for the sandbox's life.
ctx.generate_uuid()
2.5. owner_account_id
The account answerable for the sandbox.
It must hold open_sandbox at the anchor.
ctx.generate_uuid()
2.6. visibility
Who may see the sandbox.
private to the owner; shared with everyone who may read the anchor;
members for the named members.
std::string("private")
2.7. status
Whether the sandbox is in use.
open, or archived and read-only.
std::string("open")
2.8. review_date
When the owner must confirm the sandbox is still needed.
std::chrono::year_month_day{std::chrono::floor<std::chrono::days>(ctx.past_timepoint())}
2.9. description
Optional description of the sandbox.
std::optional<std::string>("Test sandbox")
3. SQL
3.1. Flags
3.2. Checks
| expression |
|---|
| "purpose" in ('experiment', 'sample', 'import') |
| "visibility" in ('private', 'shared', 'members') |
| "status" in ('open', 'archived') |
4. Foreign keys
4.1. anchor_portfolio_id
4.2. owner_account_id
The seed is a service account granted open_sandbox at the seeded anchor:
the insert trigger refuses an owner without the right, and a user account
would end the test tenant's bootstrap.
#include "ores.iam.api/generators/account_generator.hpp" #include "ores.iam.core/repository/account_repository.hpp" #include "ores.refdata.api/generators/portfolio_right_generator.hpp" #include "ores.refdata.core/repository/portfolio_right_repository.hpp"
{
auto owner = ores::iam::generators::generate_synthetic_account(ctx);
owner.account_type = "service";
owner.change_reason_code = "system.test";
ores::iam::repository::account_repository owner_repo;
owner_repo.write(party_ctx, owner);
v.owner_account_id = owner.id;
auto right = ores::refdata::generators::generate_synthetic_portfolio_right(ctx);
right.account_id = owner.id;
right.portfolio_id = v.anchor_portfolio_id;
right.right_code = "open_sandbox";
right.change_reason_code = "system.test";
ores::refdata::repository::portfolio_right_repository right_repo;
right_repo.write(party_ctx, right);
}
5. Insert trigger
-- The anchor is a node of the official tree, never a sandbox portfolio. if exists ( select 1 from ores_refdata_portfolios_tbl where tenant_id = NEW.tenant_id and id = NEW.anchor_portfolio_id and valid_to = ores_utility_infinity_timestamp_fn() and sandbox_id is not null ) then raise exception 'Invalid anchor_portfolio_id: %. A sandbox is anchored at an official portfolio.', NEW.anchor_portfolio_id using errcode = '23514'; end if; -- Opening a sandbox, or handing it to a new owner, needs the right to -- open sandboxes at the anchor. A version that keeps the owner, such as -- archiving, does not ask again. if not exists ( select 1 from ores_refdata_sandboxes_tbl where tenant_id = NEW.tenant_id and id = NEW.id and owner_account_id = NEW.owner_account_id and valid_to = ores_utility_infinity_timestamp_fn() ) and not ores_refdata_account_holds_portfolio_right_fn( NEW.tenant_id, NEW.owner_account_id, NEW.anchor_portfolio_id, 'open_sandbox' ) then raise exception 'Account % may not open a sandbox at portfolio %: it holds no open_sandbox right there.', NEW.owner_account_id, NEW.anchor_portfolio_id using errcode = '42501'; end if;
-- Whether an account may see a sandbox: its owner always may; anyone with -- read at the anchor may when it is shared; its members may when it is -- shared with members. Runs with the caller's rights, so row-level security -- confines it to the session's tenant. PL/pgSQL, so the members table, which -- is created after this one, is resolved when the function runs. create or replace function ores_refdata_account_sees_sandbox_fn( p_tenant_id uuid, p_account_id uuid, p_sandbox_id uuid ) returns boolean as $$ declare v_sandbox record; begin select owner_account_id, anchor_portfolio_id, visibility into v_sandbox from ores_refdata_sandboxes_tbl where tenant_id = p_tenant_id and id = p_sandbox_id and valid_to = ores_utility_infinity_timestamp_fn(); if not found then return false; end if; if v_sandbox.owner_account_id = p_account_id then return true; end if; if v_sandbox.visibility = 'shared' then return ores_refdata_account_holds_portfolio_right_fn( p_tenant_id, p_account_id, v_sandbox.anchor_portfolio_id, 'read'); end if; if v_sandbox.visibility = 'members' then return exists ( select 1 from ores_refdata_sandbox_members_tbl where tenant_id = p_tenant_id and sandbox_id = p_sandbox_id and account_id = p_account_id and valid_to = ores_utility_infinity_timestamp_fn() ); end if; return false; end; $$ language plpgsql stable set search_path = public, pg_temp; -- The account of the user the session acts for, from app.current_actor. -- Security definer and narrow, for the same reason as the liveness check: -- it answers one id, for the session's own actor. create or replace function ores_refdata_actor_account_id_fn( p_tenant_id uuid ) returns uuid as $$ declare v_id uuid; begin select id into v_id from ores_iam_accounts_tbl where username = ores_iam_current_actor_fn() and tenant_id in (p_tenant_id, ores_utility_system_tenant_id_fn()) and valid_to = ores_utility_infinity_timestamp_fn() order by (tenant_id = p_tenant_id) desc limit 1; return v_id; end; $$ language plpgsql stable security definer set search_path = public, pg_temp; -- Whether the session's actor may see a sandbox. A session with no actor, -- such as a system process, sees none. The portfolio policy uses it to keep -- a sandbox's portfolios out of every read and write by anyone else. create or replace function ores_refdata_actor_sees_sandbox_fn( p_tenant_id uuid, p_sandbox_id uuid ) returns boolean as $$ declare v_account uuid := ores_refdata_actor_account_id_fn(p_tenant_id); begin return v_account is not null and ores_refdata_account_sees_sandbox_fn(p_tenant_id, v_account, p_sandbox_id); end; $$ language plpgsql stable set search_path = public, pg_temp;
6. C++
6.1. Flags
6.2. Repository
6.3. Domain includes
#include <optional> #include <string> #include <boost/uuid/uuid.hpp>
6.4. Entity includes
#include <optional> #include <string>
6.5. Conventions
6.6. Table display
| column | header |
|---|---|
| name | Name |
| purpose | Purpose |
| visibility | Visibility |
| status | Status |
| modified_by | Modified By |
| version | Version |
6.7. Presentation
6.7.1. Detail fields
| field | label | widget | type | is_key | is_required | placeholder |
|---|---|---|---|---|---|---|
| name | Name | nameEdit | line_edit | true | true | Sandbox name |
| purpose | Purpose | purposeEdit | line_edit | false | true | experiment, sample or import |
| visibility | Visibility | visibilityEdit | line_edit | false | true | private, shared or members |
| status | Status | statusEdit | line_edit | false | true | open or archived |
| description | Description | descriptionEdit | line_edit | false | false | Enter description |
6.7.2. Columns
| enum_name | field | header | type | width |
|---|---|---|---|---|
| Name | name | Name | string | 160 |
| Purpose | purpose | Purpose | string | 100 |
| Visibility | visibility | Visibility | string | 100 |
| Status | status | Status | string | 80 |
| Version | version | Version | int | 80 |
| ModifiedBy | modified_by | Modified By | string | 120 |
| RecordedAt | recorded_at | Recorded At | timestamp | 150 |
6.8. Paste blocks
6.8.1. Extra drop statements
drop function if exists ores_refdata_actor_sees_sandbox_fn; drop function if exists ores_refdata_actor_account_id_fn; drop function if exists ores_refdata_account_sees_sandbox_fn;
7. See also
- ores.refdata — component group overview.
- ores.refdata.portfolio — the tree a sandbox is anchored on and owns a part of.
- ores.refdata.portfolio_right — the rights that govern opening and seeing a sandbox.
- ores.refdata.sandbox_member — the accounts a sandbox is shared with.
- Sandbox — the analysis this entity follows.