Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion libraries/source-read-sdk/library.codefly.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ sources:
module: saas-starter
service: accounts
endpoint: authority
contract-digest: sha256:d6a4564228c0a8ee8957d48d0413c642500f03373d14a2ead4d64d0536bbcbe7
contract-digest: sha256:606e52170c2e55688ce163eb1d54c347f0d19ac40915d7fb6252f2034b787c0e
services:
- ModuleCapabilitiesService
generated-by:
Expand Down
Binary file modified module/contracts/api/accounts/authority/contract.binpb
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,14 @@ message InstallSolutionRequest {
string root_scope_label = 6 [(buf.validate.field).string.max_len = 512];
// The least-privilege role granted to the agent at the root node. This is
// deliberately NOT the full "<namespace>:catalog" role; widening is a separate,
// audited admin action.
string role_id = 7 [(buf.validate.field).string.uuid = true];
// audited admin action. Empty when the delivered module declares its own
// installation role: the host selects a named one, and "none" grants the
// agent no standing role at all. A role given for "none" is refused, and an
// empty one is refused for a target that declares no installation role.
string role_id = 7 [
(buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE,
(buf.validate.field).string.uuid = true
];
// The accountable human of record. Must be a current org admin. Empty defaults
// to the calling admin.
string owner_principal_id = 8 [
Expand Down
Binary file modified module/contracts/api/accounts/connect/contract.binpb
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,14 @@ message InstallSolutionRequest {
string root_scope_label = 6 [(buf.validate.field).string.max_len = 512];
// The least-privilege role granted to the agent at the root node. This is
// deliberately NOT the full "<namespace>:catalog" role; widening is a separate,
// audited admin action.
string role_id = 7 [(buf.validate.field).string.uuid = true];
// audited admin action. Empty when the delivered module declares its own
// installation role: the host selects a named one, and "none" grants the
// agent no standing role at all. A role given for "none" is refused, and an
// empty one is refused for a target that declares no installation role.
string role_id = 7 [
(buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE,
(buf.validate.field).string.uuid = true
];
// The accountable human of record. Must be a current org admin. Empty defaults
// to the calling admin.
string owner_principal_id = 8 [
Expand Down
4 changes: 2 additions & 2 deletions module/contracts/api/catalog.codefly.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"kind": "protobuf",
"package": "saas.accounts.v1",
"path": "contracts/api/accounts/authority/contract.binpb",
"digest": "sha256:d6a4564228c0a8ee8957d48d0413c642500f03373d14a2ead4d64d0536bbcbe7",
"digest": "sha256:606e52170c2e55688ce163eb1d54c347f0d19ac40915d7fb6252f2034b787c0e",
"services": [
{
"name": "ModuleCapabilitiesService",
Expand Down Expand Up @@ -66,7 +66,7 @@
"kind": "protobuf",
"package": "",
"path": "contracts/api/accounts/connect/contract.binpb",
"digest": "sha256:d6a4564228c0a8ee8957d48d0413c642500f03373d14a2ead4d64d0536bbcbe7",
"digest": "sha256:606e52170c2e55688ce163eb1d54c347f0d19ac40915d7fb6252f2034b787c0e",
"services": [
{
"name": "ArtifactService",
Expand Down
4 changes: 2 additions & 2 deletions module/module.package.codefly.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ services:
kind: protobuf
package: saas.accounts.v1
path: contracts/api/accounts/authority/contract.binpb
digest: sha256:d6a4564228c0a8ee8957d48d0413c642500f03373d14a2ead4d64d0536bbcbe7
digest: sha256:606e52170c2e55688ce163eb1d54c347f0d19ac40915d7fb6252f2034b787c0e
- endpoint: connect
kind: protobuf
package: ""
path: contracts/api/accounts/connect/contract.binpb
digest: sha256:d6a4564228c0a8ee8957d48d0413c642500f03373d14a2ead4d64d0536bbcbe7
digest: sha256:606e52170c2e55688ce163eb1d54c347f0d19ac40915d7fb6252f2034b787c0e
- name: auth-gateway
endpoints: [grpc, rest]
api-contracts:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1164,9 +1164,32 @@ func runHostInterfaceFixture(t *testing.T) {
AgentIdentifier: target.ReleasePublisher + "/" + target.ReleaseName + ":" + target.ReleaseVersion, DisplayName: selected, RootScopeLabel: selected,
RoleID: catalogue, OwnerPrincipalID: conformanceAlex})
require.ErrorContains(t, err, "cannot override "+selected+"'s installation_role declaration")
installation, _, err := svc.InstallFixtureTarget(ctx, conformanceAlex, fixtureOrganization, selected, "", conformanceAlex)
require.NoError(t, err, "the declared role installs it")
// The administrator's install with no role, as the module page review
// sends it: the signed installation_role selects the standing role.
installation, err := svc.InstallSolution(ctx, conformanceAlex, &business.InstallSolutionParams{OrgID: fixtureOrganization, TargetID: target.TargetID,
AgentIdentifier: target.ReleasePublisher + "/" + target.ReleaseName + ":" + target.ReleaseVersion, DisplayName: selected, RootScopeLabel: selected,
OwnerPrincipalID: conformanceAlex})
require.NoError(t, err, "an empty role installs a module page as its declared installation_role")
require.NoError(t, completeInstallStep(ctx, store, installation))
var standing []string
require.NoError(t, store.WithControlPlane(ctx, func(ctx context.Context) error {
rows, err := storetx.Tx(ctx).Query(ctx, `SELECT r.derived_from FROM public.scope_grants g JOIN public.roles r ON r.id = g.role_id
WHERE g.org_id = $1 AND g.subject_kind = 'principal' AND g.subject_id = $2`, fixtureOrganization, installation.GetAgentPrincipalId())
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var from string
if err := rows.Scan(&from); err != nil {
return err
}
standing = append(standing, from)
}
return rows.Err()
}))
require.Equal(t, []string{"org=" + fixtureOrganization + ";solution=" + target.TargetID + ";role=viewer"}, standing,
"the agent's one standing grant is the declared viewer role")
})

// Last, because the envelope it writes becomes every module's ceiling: a
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package adapters

import (
"testing"

gen "accounts/pkg/gen/saas/accounts/v1"

"github.com/google/uuid"
"github.com/stretchr/testify/require"
)

// A module that declares its own installation role is installed with no
// caller role: the host selects a named one, and "none" grants none. A role
// that is given must still be a role id.
func TestInstallSolutionRequestRoleIsOptionalButWellFormed(t *testing.T) {
request := func(role string) *gen.InstallSolutionRequest {
return &gen.InstallSolutionRequest{OrgId: uuid.NewString(), AgentIdentifier: "publisher/name:1.0.0", TargetId: uuid.NewString(), RoleId: role}
}
require.NoError(t, Validate(request("")), "a declared installation role leaves the caller's role out")
require.NoError(t, Validate(request(uuid.NewString())))
require.Error(t, Validate(request("viewer")), "a given role is a role id")
}
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ func TestDynamicInstallationTenancyFiltersBeforePaginationAndInstall(t *testing.
require.Empty(t, next)
_, err = s.prepareInstallation(context.Background(), &InstallSolutionParams{OrgID: "org-a", TargetID: "1"})
require.Equal(t, codes.PermissionDenied, status.Code(err))
_, err = s.prepareInstallation(context.Background(), &InstallSolutionParams{OrgID: "org-b", TargetID: "1"})
_, err = s.prepareInstallation(context.Background(), &InstallSolutionParams{OrgID: "org-b", TargetID: "1", RoleID: "role-id"})
require.NoError(t, err)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ func (s *Service) prepareInstallation(ctx context.Context, params *InstallSoluti
}
params.RoleID = role
}
// Only the delivered declaration may leave the caller's role out: a named
// installation_role is selected above, and "none" grants no standing role.
if !params.NoStandingAuthority && params.RoleID == "" {
return nil, NewStoreError(fmt.Errorf("role_id is required: %s declares no installation role for this host to select", target.RouteAlias), ErrTypeValidation)
}
if params.Kind == "module" && target.Installed && params.OwnerPrincipalID == "" {
existing, err := s.heldTargetInstallation(ctx, params.OrgID, params.TargetID)
if err != nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func TestModuleNoStandingSelectionRequiresCurrentPairedDeclaration(t *testing.T)
{"caller literal none refused", "none", "none", false, true, false},
{"old generation", "none", "", true, true, false},
{"legacy stays explicit", "", "role-id", false, false, false},
{"legacy without a role refused", "", "", false, true, false},
{"named module cannot become catalogue role", "reader", "catalogue-role-id", false, true, false},
} {
t.Run(tc.name, func(t *testing.T) {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -9341,7 +9341,7 @@
},
"roleId": {
"type": "string",
"description": "The least-privilege role granted to the agent at the root node. This is\ndeliberately NOT the full \"\u003cnamespace\u003e:catalog\" role; widening is a separate,\naudited admin action."
"description": "The least-privilege role granted to the agent at the root node. This is\ndeliberately NOT the full \"\u003cnamespace\u003e:catalog\" role; widening is a separate,\naudited admin action. Empty when the delivered module declares its own\ninstallation role: the host selects a named one, and \"none\" grants the\nagent no standing role at all. A role given for \"none\" is refused, and an\nempty one is refused for a target that declares no installation role."
},
"ownerPrincipalId": {
"type": "string",
Expand Down
2 changes: 1 addition & 1 deletion module/services/accounts/openapi/api.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -2552,7 +2552,7 @@
"type": "string"
},
"roleId": {
"description": "The least-privilege role granted to the agent at the root node. This is\ndeliberately NOT the full \"<namespace>:catalog\" role; widening is a separate,\naudited admin action.",
"description": "The least-privilege role granted to the agent at the root node. This is\ndeliberately NOT the full \"<namespace>:catalog\" role; widening is a separate,\naudited admin action. Empty when the delivered module declares its own\ninstallation role: the host selects a named one, and \"none\" grants the\nagent no standing role at all. A role given for \"none\" is refused, and an\nempty one is refused for a target that declares no installation role.",
"type": "string"
},
"rootScopeLabel": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,14 @@ message InstallSolutionRequest {
string root_scope_label = 6 [(buf.validate.field).string.max_len = 512];
// The least-privilege role granted to the agent at the root node. This is
// deliberately NOT the full "<namespace>:catalog" role; widening is a separate,
// audited admin action.
string role_id = 7 [(buf.validate.field).string.uuid = true];
// audited admin action. Empty when the delivered module declares its own
// installation role: the host selects a named one, and "none" grants the
// agent no standing role at all. A role given for "none" is refused, and an
// empty one is refused for a target that declares no installation role.
string role_id = 7 [
(buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE,
(buf.validate.field).string.uuid = true
];
// The accountable human of record. Must be a current org admin. Empty defaults
// to the calling admin.
string owner_principal_id = 8 [
Expand Down
14 changes: 13 additions & 1 deletion module/services/frontend/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,19 @@ not replace the cached operation; retries retain it, and Review again explicitly
requests new consent. Pending installation rows offer current-person continuation.
Supporting modules remain visible on `/admin/modules` and in installation
detail. That administrator path remains available, but is not a step in normal
solution installation.
solution installation. There, which form Install opens is the authority's
answer, not the target's kind (`useRolesDeclared`): a module page whose offer
names roles is reviewed from that offer (its machine access and what each role
permits), installed through `InstallSolution` with no agent role, because its
signed `installation_role` selects one (or none), and then opened at its Setup
page, where each role takes a team. The shared core installs only solutions, so
a module page stays this host's installation and carries no consent tuple. A
module whose offer the authority refuses NotFound (it declares no roles) keeps
the agent-role form; any other failure of that read is shown with a retry, never
taken as "no roles". The review's approval is the browser's: a module page's
offer carries no consent digest for the host to check it against. On Setup, a
module page's roles take teams through the shared role grant form; the staged
team-per-role picker binds to a solution's consent digest and is not offered.

The only installer configuration is a team for each role the solution's offer
declares: an existing organization team, or one created inline through
Expand Down
4 changes: 2 additions & 2 deletions module/services/frontend/code/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion module/services/frontend/code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"@bufbuild/protobuf": "^2.11.0",
"@codefly-dev/saas-plugin-contract": "2.4.4",
"@codefly-dev/saas-plugin-react": "0.4.5",
"@codefly-dev/saas-sdk": "0.3.41",
"@codefly-dev/saas-sdk": "0.3.42",
"@codefly-dev/saas-ui": "0.13.0",
"@codefly-dev/ui": "0.13.0",
"@codefly/saas-plugin-manifest": "0.1.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ sources:
module: saas-starter
service: accounts
endpoint: connect
contract-digest: sha256:d6a4564228c0a8ee8957d48d0413c642500f03373d14a2ead4d64d0536bbcbe7
contract-digest: sha256:606e52170c2e55688ce163eb1d54c347f0d19ac40915d7fb6252f2034b787c0e
services:
- AccessibleScopeService
- AuditService
Expand Down
Loading
Loading