ores.iam.role
Table of Contents
A named collection of permissions that can be assigned to accounts. Roles group related permissions for easier management: a "Trading" role might include permissions to read and execute trades, while a "Support" role might have read-only access to most resources.
The table is bi-temporal and audited (see
projects/ores.sql/create/iam/iam_roles_create.sql): it carries
version, the four audit columns and the valid_from=/=valid_to pair
with the GIST exclusion and the delete rule, so the model takes the
ordinary audited shape and needs no shape flag.
The model describes the table alone. The hand-written domain struct also
carried a std::vector<std::string> permission_codes that no column
backs – it is denormalised from ores_iam_role_permissions_tbl by an
RBAC join. A joined shape is a message or a query result, never an entity
member, so the member is not modelled and the generated role.hpp
replaces it; the join itself stays in the hand-written authorization
layer.
The entity's canonical CRUD subjects are generated: role_registrar owns
list/get/get-many/put/put-many/delete on iam.v1.roles.*. The
authorization operation model declares a different set under the same
prefix – iam.v1.ops.assign_role, .revoke, .by-account,
.permissions and .suggest-commands – so the two coexist rather than
compete, and no handler is suppressed here.
The list and the get are open to every signed-in member: a member reads the
tenant's roles to choose the one to ask for. The model opens them by subject
with :open_reads:, and Authorised
reads allow-lists both. The other reads, such as the versions, need
iam::roles:read.
1. Flags
2. Natural keys
3. Columns
3.1. name
Unique name for the role within its tenant (for example "Trading",
"Sales", "Admin"). It is the natural key, so the generated table adds
the partial unique index on (tenant_id, name) the hand-written table
already had.
std::string("Role_") + ctx.alphanumeric(6)
3.2. id
Unique identifier for the role.
3.3. description
Human-readable description of the role's purpose and scope.
std::string("Synthetic test role")
3.4. is_registration_default
Whether a self-registered account in this tenant receives this role. At most one live role per tenant may hold it.
The flag replaces the hard-coded name the registration path used to assign. A flag on the row cannot misspell a role that does not exist, which is the failure mode a stored name has.
false
3.5. is_requestable
Whether a member may ask for this role. A member asks for a role beyond the one registration gave them, and the tenant administrator answers; this flag is what the tenant says its members may ask for.
A service's role and the role that grants everything are not roles a person asks for, so the seed clears the flag on them. The flag replaces the name suffix and the permission count the screens used to filter by, which stated the rule twice and stated it nowhere the tenant could change it.
The flag is read with the role, because a member picks from the roles it allows, and it is written with the role, because the tenant administrator owns the tenant's roles.
true
4. SQL
4.1. Flags
4.2. Indexes
| name | columns | unique | current_only | where_extra |
|---|---|---|---|---|
| registration_default_role_uniq | tenant_id | true | false | is_registration_default and valid_to = ores_utility_infinity_timestamp_fn() |
5. C++
5.1. Flags
5.2. Repository
5.2.1. read_latest_by_name
Reads the latest active role with the given natural-key name, scoped to
the caller's tenant. The generated CRUD set reads by primary key only and
its by-column finder is named for a code column, so the natural-key
read this entity's consumers already use stays a declared paste block
rather than a second, differently-named method on the repository.
std::vector<domain::role> read_latest_by_name(context ctx, const std::string& name);
std::vector<domain::role> role_repository::read_latest_by_name(context ctx, const std::string& name) { static constexpr auto max = db_timestamp::max(); const auto tid = ctx.tenant_id().to_string(); const auto query = query_builder::read<std::vector<role_entity>> | query_builder::where("tenant_id"_c == tid && "name"_c == name && "valid_to"_c == max); return query_builder::execute_read_query<role_entity, domain::role>( ctx, query, [](const auto& entities) { return role_mapper::map(entities); }, lg(), "Reading latest role by name."); }
5.2.2. read_registration_default
Reads the tenant's role flagged is_registration_default, when one is set.
At most one live role per tenant may hold the flag, so this is one row or
none. The registration path grants the role this read names rather than a
role name it hard-codes.
std::optional<domain::role> read_registration_default(context ctx);
std::optional<domain::role> role_repository::read_registration_default(context ctx) { static constexpr auto max = db_timestamp::max(); const auto tid = ctx.tenant_id().to_string(); const auto query = query_builder::read<std::vector<role_entity>> | query_builder::where("tenant_id"_c == tid && "is_registration_default"_c == true && "valid_to"_c == max) | query_builder::limit(1); const auto rows = query_builder::execute_read_query<role_entity, domain::role>( ctx, query, [](const auto& entities) { return role_mapper::map(entities); }, lg(), "Reading the registration default role."); if (rows.empty()) return std::nullopt; return rows.front(); }
5.3. Domain includes
#include <boost/uuid/uuid.hpp> #include <string>
5.4. Entity includes
#include <string>
5.5. Conventions
5.6. Table display
| column | header |
|---|---|
| id | ID (UUID) |
| name | Name |
| description | Description |
| modified_by | Modified By |
| version | Version |
5.7. Presentation
The screen's declaration. A role is addressed by its name, the natural key,
which is what the route's path segment carries and what the BFF asks the entity
by; the surrogate id is generated and the form does not offer it.
5.7.1. Detail fields
| field | label | widget | type | is_key | is_required | placeholder |
|---|---|---|---|---|---|---|
| name | Name | nameEdit | line_edit | true | true | Enter a role name |
| description | Description | descriptionEdit | text_edit | Enter a description |
5.7.2. Columns
| enum_name | field | header | type | width |
|---|---|---|---|---|
| Name | name | Name | string | 200 |
| Description | description | Description | string | 480 |
5.8. Paste blocks
6. See also
- ores.iam — component group overview.