Skip to content

Read several client tenants with one portfolio credential - #134

Merged
Gorka Gonzalo (d0ubl3g) merged 10 commits into
devfrom
feat/portfolio
Oct 12, 2026
Merged

Gorka Gonzalo (d0ubl3g) merged 10 commits into
devfrom
feat/portfolio

Conversation

@d0ubl3g

Copy link
Copy Markdown
Contributor

Read several client tenants with one MSSP portfolio credential (hsp_…), following the published /v1 portfolio contract (API version 2026-10-16).

  • API client: a portfolio module that lists the client tenants (GET /v1/portfolio/tenants) and reads one tenant per request with X-HS-Tenant. Every answer for a selected tenant must echo that tenant in X-HS-Tenant, or it is refused. Tenant scoping is a property of each generated operation.
  • CLI: hodeishield tenants list, a global --tenant <slug|id> for any read command (it prints Client tenant: <slug> (<id>) on standard error), and --all-tenants on list commands, which adds tenant_id, tenant_slug and tenant_name to each row (a TENANT column in the table). A client is resolved against the listing before any data is read: an unknown one fails with exit code 5. With --all-tenants, inactive clients are skipped with a note. A client whose licence status is unknown to this version, or that fails on its own account (exit code 8 or 5), is named on standard error; the others are printed and the command exits non-zero. Other failures stop the run, for example exit code 6 on an exhausted rate limit. Against an API without portfolio access these commands fail with exit code 7. Without these options nothing changes.
  • Errors: the tenant error codes (TENANT_REQUIRED, TENANT_SELECTOR_NOT_SUPPORTED, TENANT_NOT_FOUND), missing scopes and the portfolio rate-limit scopes get specific messages and hints. Exit codes do not change.
  • README: a new "Several tenants (MSSP)" section: the portfolio key, listing, selecting, partial results, limits and revocation.
  • CHANGELOG: entries in Unreleased (Added and Changed).

Checked against the live API: the listing, single-tenant reads by slug and by id (same output as the tenant's own key), --all-tenants, the negative cases and --verbose (no credential in the output).

Refs #54, #45

List the client tenants of a portfolio credential and read one of them on
data calls. The selection travels as one request header and the client
refuses any answer that does not confirm the same tenant. The module is
hand-written against a draft contract and isolated in portfolio.rs; the
generated client is untouched.

Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Add 'tenants list' and the global --tenant and --all-tenants selection on
read commands. A list across tenants labels every row, in the table and in
--json and --csv, with the tenant whose own request returned it. A client
outside the portfolio is reported as not found before any data is read, and
a credential without portfolio scope behaves as before.

Closes #54

Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Add the 'Several tenants (MSSP)' section to the README and stop pointing
MSSPs at one profile per client. The section is a draft until the portfolio
part of /v1 is published.

Closes #45

Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Read the client tenants as the API describes them: ask for the listing in
ascending name order, require the organisation, and treat a licence status
the CLI does not know as not readable. Accept a tenant confirmation only as
the single lowercase id that was asked for. An API with no listing route is
reported with exit code 7 instead of a generic failure.

A rate limit or a missing data scope now ends an all-tenants walk instead of
being repeated for every client, and the messages name the limit that was
hit and the tenant refusals the API distinguishes.

Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
- Tenants with a licence status this version does not know are not read,
  are named on stderr, and make an --all-tenants walk exit non-zero;
  "inactive" is said of inactive tenants only.
- A UUID-shaped input that matches one tenant's id and another's slug is
  ambiguous.
- Slugs with characters outside [a-z0-9-] are shown with the start of the
  tenant id in the table.
- The tenant listing is de-duplicated by id, and a changing total is
  reported.
- A 404/405 on the listing that carries a portfolio error code is the
  API's own error, not "portfolio not offered".
- The tenant rate-limit hint applies only when a client tenant is selected.
- Tenant scoping is a property of each operation (tenant_scoped), set by
  the code generator.
- Add tests for the echo on every operation, retried answers, redirects,
  204/304, partial walks, CSV formula guarding and tenant resolution.

Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Name the role that creates a portfolio key (an organisation administrator of an active MSSP
organisation), use the portfolio key name in the changelog, spell the listing column with the
same British spelling as the rest of the text, and show a client organisation in the example.

Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
…sation administrator

Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
The portfolio entry did not say what --all-tenants does when a client
fails while it is read. In the code, a client that fails with a licence
not in force (exit 8) or not found (exit 5) is named on standard error
and skipped, the other clients' data is printed, and the command exits
non-zero with the code of the first failure; any other failure stops the
run before anything is printed. Say so, mention that --tenant also works
on get and compliance posture and prints the "Client tenant" line, and
rewrap the long line.

Add the Changed entry for the errors that involve a client tenant: the
hints for an unknown client, a missing scope on a portfolio key, the
portfolio rate limits and a missing or unsupported tenant selection.

Refs #45, #54

Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Signed-off-by: d0ubl3g <48954024+d0ubl3g@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant