Update RBAC on console

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Bryan Frimin
2025-12-23 09:04:26 +01:00
parent 72831d4c2b
commit e9ac50d91c
26 changed files with 2123 additions and 1444 deletions

View File

@@ -1,359 +0,0 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package coredata
import (
"context"
"fmt"
"maps"
"time"
"github.com/jackc/pgx/v5"
"go.gearno.de/kit/pg"
"go.probo.inc/probo/pkg/gid"
)
type (
PersonalAPIKeyMembership struct {
ID gid.GID `db:"id"`
PersonalAPIKeyID gid.GID `db:"personal_api_key_id"`
MembershipID gid.GID `db:"membership_id"`
Role APIRole `db:"role"`
OrganizationID gid.GID `db:"organization_id"`
OrganizationName string `db:"organization_name"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
PersonalAPIKeyMemberships []*PersonalAPIKeyMembership
)
func (a *PersonalAPIKeyMembership) Insert(
ctx context.Context,
conn pg.Conn,
scope Scoper,
) error {
q := `
INSERT INTO
iam_personal_api_key_memberships (id, tenant_id, personal_api_key_id, membership_id, role, organization_id, created_at, updated_at)
VALUES (
@id,
@tenant_id,
@personal_api_key_id,
@membership_id,
@role,
@organization_id,
@created_at,
@updated_at
)
`
args := pgx.StrictNamedArgs{
"id": a.ID,
"tenant_id": scope.GetTenantID(),
"personal_api_key_id": a.PersonalAPIKeyID,
"membership_id": a.MembershipID,
"role": a.Role,
"organization_id": a.OrganizationID,
"created_at": a.CreatedAt,
"updated_at": a.UpdatedAt,
}
_, err := conn.Exec(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot insert personal api key membership: %w", err)
}
return nil
}
func (a *PersonalAPIKeyMemberships) LoadByPersonalAPIKeyID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
personalAPIKeyID gid.GID,
) error {
q := `
SELECT
akm.id,
akm.personal_api_key_id,
akm.membership_id,
akm.role,
akm.created_at,
akm.updated_at,
m.organization_id,
o.name as organization_name
FROM
iam_personal_api_key_memberships akm
JOIN
iam_memberships m ON akm.membership_id = m.id
JOIN
organizations o ON m.organization_id = o.id
WHERE
akm.personal_api_key_id = @personal_api_key_id
AND m.%s
ORDER BY akm.created_at DESC
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{
"personal_api_key_id": personalAPIKeyID,
}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query personal api key memberships: %w", err)
}
memberships, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[PersonalAPIKeyMembership])
if err != nil {
return fmt.Errorf("cannot collect personal api key memberships: %w", err)
}
*a = memberships
return nil
}
// LoadRoleByAPIKeyAndEntityID loads an API key's role by querying any entity to extract its organization_id
func (a *PersonalAPIKeyMembership) LoadRoleByAPIKeyAndEntityID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
apiKeyID gid.GID,
entityID gid.GID,
) error {
entityType := entityID.EntityType()
// For organization, the entity ID is the organization ID
if entityType == OrganizationEntityType {
return a.LoadByAPIKeyIDAndOrganizationID(ctx, conn, scope, apiKeyID, entityID)
}
tableName, ok := EntityTable(entityType)
if !ok {
return fmt.Errorf("unsupported entity type for API key role lookup: %d", entityType)
}
query := fmt.Sprintf(`
SELECT
akm.id,
akm.personal_api_key_id,
akm.membership_id,
akm.role,
akm.created_at,
akm.updated_at
FROM
iam_personal_api_key_memberships akm
INNER JOIN iam_memberships m ON m.id = akm.membership_id
INNER JOIN %s e ON e.id = @entity_id
WHERE
%s
AND akm.personal_api_key_id = @api_key_id
AND m.organization_id = e.organization_id
LIMIT 1;
`, tableName, scope.SQLFragment())
args := pgx.NamedArgs{
"api_key_id": apiKeyID,
"entity_id": entityID,
}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, query, args)
if err != nil {
return fmt.Errorf("cannot query API key membership by entity: %w", err)
}
defer rows.Close()
if !rows.Next() {
return fmt.Errorf("API key membership not found for key %s and entity %s", apiKeyID, entityID)
}
var membership PersonalAPIKeyMembership
err = rows.Scan(
&membership.ID,
&membership.PersonalAPIKeyID,
&membership.MembershipID,
&membership.Role,
&membership.CreatedAt,
&membership.UpdatedAt,
)
if err != nil {
return fmt.Errorf("cannot scan API key membership: %w", err)
}
*a = membership
return nil
}
func (a *PersonalAPIKeyMembership) LoadByAPIKeyIDAndOrganizationID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
apiKeyID gid.GID,
organizationID gid.GID,
) error {
q := `
SELECT
akm.id,
akm.personal_api_key_id,
akm.membership_id,
akm.role,
akm.created_at,
akm.updated_at,
m.organization_id,
o.name as organization_name
FROM
iam_personal_api_key_memberships akm
JOIN
iam_memberships m ON akm.membership_id = m.id
JOIN
organizations o ON m.organization_id = o.id
WHERE
akm.personal_api_key_id = @api_key_id
AND m.organization_id = @organization_id
AND m.%s
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{
"api_key_id": apiKeyID,
"organization_id": organizationID,
}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query personal api key membership: %w", err)
}
membership, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[PersonalAPIKeyMembership])
if err != nil {
if err == pgx.ErrNoRows {
return fmt.Errorf("API key does not have access to organization")
}
return fmt.Errorf("cannot collect personal api key membership: %w", err)
}
*a = membership
return nil
}
func (a *PersonalAPIKeyMembership) Delete(
ctx context.Context,
conn pg.Conn,
scope Scoper,
) error {
q := `
DELETE FROM
iam_personal_api_key_memberships
WHERE
id = @id
AND %s
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{
"id": a.ID,
}
maps.Copy(args, scope.SQLArguments())
_, err := conn.Exec(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot delete personal api key membership: %w", err)
}
return nil
}
func (a *PersonalAPIKeyMemberships) LoadByMembershipID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
membershipID gid.GID,
) error {
q := `
SELECT
akm.id,
akm.personal_api_key_id,
akm.membership_id,
akm.role,
akm.created_at,
akm.updated_at,
m.organization_id,
o.name as organization_name
FROM
iam_personal_api_key_memberships akm
JOIN
iam_memberships m ON akm.membership_id = m.id
JOIN
organizations o ON m.organization_id = o.id
WHERE
akm.membership_id = @membership_id
AND m.%s
ORDER BY akm.created_at DESC
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{
"membership_id": membershipID,
}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query personal api key memberships by membership id: %w", err)
}
memberships, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[PersonalAPIKeyMembership])
if err != nil {
return fmt.Errorf("cannot collect personal api key memberships: %w", err)
}
*a = memberships
return nil
}
func DeleteAllPersonalAPIKeyMembershipsByPersonalAPIKeyID(
ctx context.Context,
conn pg.Conn,
personalAPIKeyID gid.GID,
) error {
q := `
DELETE FROM
iam_personal_api_key_memberships
WHERE
personal_api_key_id = @personal_api_key_id
`
args := pgx.StrictNamedArgs{
"personal_api_key_id": personalAPIKeyID,
}
_, err := conn.Exec(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot delete personal api key memberships: %w", err)
}
return nil
}

View File

@@ -1,54 +0,0 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package coredata
import (
"database/sql/driver"
"fmt"
)
type APIRole string
const (
APIRoleFull APIRole = "FULL"
)
func (r APIRole) String() string {
return string(r)
}
func (r *APIRole) Scan(value any) error {
var s string
switch v := value.(type) {
case string:
s = v
case []byte:
s = string(v)
default:
return fmt.Errorf("unsupported type for APIRole: %T", value)
}
switch s {
case "FULL":
*r = APIRoleFull
default:
return fmt.Errorf("invalid APIRole value: %q", s)
}
return nil
}
func (r APIRole) Value() (driver.Value, error) {
return r.String(), nil
}

View File

@@ -65,7 +65,7 @@ const (
TrustCenterFileEntityType uint16 = 41
SAMLConfigurationEntityType uint16 = 42
PersonalAPIKeyEntityType uint16 = 43
PersonalAPIKeyMembershipEntityType uint16 = 44
_ uint16 = 44 // PersonalAPIKeyMembershipEntityType - removed
MeetingEntityType uint16 = 45
DataProtectionImpactAssessmentEntityType uint16 = 46
TransferImpactAssessmentEntityType uint16 = 47
@@ -257,10 +257,6 @@ var entityRegistry = map[uint16]EntityInfo{
Model: "PersonalAPIKey",
Table: "iam_personal_api_keys",
},
PersonalAPIKeyMembershipEntityType: {
Model: "PersonalAPIKeyMembership",
Table: "iam_personal_api_key_memberships",
},
MeetingEntityType: {
Model: "Meeting",
Table: "meetings",

View File

@@ -0,0 +1,3 @@
DELETE FROM iam_personal_api_key_memberships;
DROP TABLE iam_personal_api_key_memberships;

View File

@@ -12,9 +12,20 @@
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
// LEGACY: This is the legacy access management service that is used to authorize actions on entities.
// It is deprecated and will be removed in the future.
// Use the Authorizer instead.
// LEGACY ACCESS MANAGEMENT SERVICE - DEPRECATED
//
// This service implements the legacy authorization model that uses the Permissions
// map from permissions.go to check if a principal can perform an action.
//
// It is being replaced by Authorizer which uses a policy-based evaluation system.
// During migration, this service is still used for:
// - API key authorization (intersection semantics between user and API key roles)
// - Fallback for any unmapped legacy actions
//
// Once all actions are migrated and API key authorization is implemented in the
// new system, this service will be removed.
//
// Deprecated: Use Authorizer.Authorize() instead for new code.
package iam
import (
@@ -148,18 +159,21 @@ func (s *AccessManagementService) loadAPIKeyRoleForEntity(
apiKeyID gid.GID,
entityID gid.GID,
) (Role, error) {
var akm coredata.PersonalAPIKeyMembership
if err := akm.LoadRoleByAPIKeyAndEntityID(ctx, conn, scope, apiKeyID, entityID); err != nil {
return "", err
// Load the API key to get the identity
apiKey := &coredata.PersonalAPIKey{}
if err := apiKey.LoadByID(ctx, conn, apiKeyID); err != nil {
return "", fmt.Errorf("cannot load api key: %w", err)
}
// Strict API key semantics: FULL only matches RoleFull explicitly.
switch akm.Role {
case coredata.APIRoleFull:
return RoleFull, nil
default:
return "", fmt.Errorf("unsupported api key role: %s", akm.Role)
// Use the Identity's membership role for authorization
var m coredata.Membership
if err := m.LoadRoleByIdentityAndEntityID(ctx, conn, scope, apiKey.IdentityID, entityID); err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return "", err
}
return "", err
}
return Role(m.Role.String()), nil
}
// requiredRoleNamesContain is a temporary evaluator for the current in-code permissions registry

View File

@@ -17,7 +17,9 @@ package iam
import (
"context"
"errors"
"fmt"
"maps"
"slices"
"go.gearno.de/kit/pg"
"go.probo.inc/probo/pkg/coredata"
@@ -32,24 +34,28 @@ type Authorizer struct {
policySet *PolicySet
}
// NewAuthorizer creates a new authorizer with the given PolicySet.
// The PolicySet should contain all role-based and self-management policies
// from all services that need authorization.
// NewAuthorizer creates a new authorizer.
// Services register their policies by calling RegisterPolicySet.
//
// Example:
//
// policySet := iam.IAMPolicySet().
// Merge(documents.DocumentPolicySet()).
// Merge(risks.RiskPolicySet())
// authorizer := iam.NewAuthorizer(pgClient, policySet)
func NewAuthorizer(pgClient *pg.Client, policySet *PolicySet) *Authorizer {
// authorizer := iam.NewAuthorizer(pgClient)
// authorizer.RegisterPolicySet(iam.IAMPolicySet())
// authorizer.RegisterPolicySet(probo.ProboPolicySet())
func NewAuthorizer(pgClient *pg.Client) *Authorizer {
return &Authorizer{
pg: pgClient,
evaluator: policy.NewEvaluator(),
policySet: policySet,
policySet: NewPolicySet(),
}
}
// RegisterPolicySet merges policies from another service into this authorizer.
// Services call this method to register their policies.
func (a *Authorizer) RegisterPolicySet(policySet *PolicySet) {
a.policySet.Merge(policySet)
}
// AuthorizeParams contains all parameters for an authorization check.
type AuthorizeParams struct {
// Principal is the user requesting access.
@@ -66,6 +72,53 @@ type AuthorizeParams struct {
ResourceAttributes map[string]string
}
func (a *Authorizer) GetPermissionsForMembership(ctx context.Context, identityID gid.GID, membershipID gid.GID) (map[string]map[Action]bool, error) {
var (
scope = coredata.NewScopeFromObjectID(membershipID)
membership = &coredata.Membership{}
)
err := a.pg.WithConn(
ctx,
func(conn pg.Conn) error {
err := membership.LoadByID(ctx, conn, scope, membershipID)
if err != nil {
if err == coredata.ErrResourceNotFound {
return NewMembershipNotFoundError(membershipID)
}
return fmt.Errorf("cannot load membership: %w", err)
}
return nil
},
)
if err != nil {
return nil, err
}
permissions := make(map[string]map[Action]bool)
for entityType, actions := range Permissions {
entityTypeName, ok := coredata.EntityModel(entityType)
if !ok {
continue
}
if permissions[entityTypeName] == nil {
permissions[entityTypeName] = make(map[Action]bool)
}
for action, allowedRoles := range actions {
if slices.Contains(allowedRoles, Role(membership.Role)) {
permissions[entityTypeName][action] = true
}
}
}
return permissions, nil
}
// Authorize checks if the principal can perform the action on the resource.
// It combines self-management policies with role-based policies.
func (a *Authorizer) Authorize(ctx context.Context, params AuthorizeParams) error {

View File

@@ -32,12 +32,13 @@ const (
ActionIAMOrganizationListInvitations = "iam:organization:list-invitations"
// Identity actions
ActionIAMIdentityGet = "iam:identity:get"
ActionIAMIdentityUpdate = "iam:identity:update"
ActionIAMIdentityDelete = "iam:identity:delete"
ActionIAMIdentityListMemberships = "iam:identity:list-memberships"
ActionIAMIdentityListInvitations = "iam:identity:list-invitations"
ActionIAMIdentityListSessions = "iam:identity:list-sessions"
ActionIAMIdentityGet = "iam:identity:get"
ActionIAMIdentityUpdate = "iam:identity:update"
ActionIAMIdentityDelete = "iam:identity:delete"
ActionIAMIdentityListMemberships = "iam:identity:list-memberships"
ActionIAMIdentityListInvitations = "iam:identity:list-invitations"
ActionIAMIdentityListSessions = "iam:identity:list-sessions"
ActionIAMIdentityListPersonalAPIKeys = "iam:identity:list-personal-api-keys"
// Session actions
ActionIAMSessionGet = "iam:session:get"
@@ -52,4 +53,17 @@ const (
// Membership actions
ActionIAMMembershipGet = "iam:membership:get"
ActionIAMMembershipUpdate = "iam:membership:update"
// Personal API Key actions
ActionIAMPersonalAPIKeyCreate = "iam:personal-api-key:create"
ActionIAMPersonalAPIKeyGet = "iam:personal-api-key:get"
ActionIAMPersonalAPIKeyUpdate = "iam:personal-api-key:update"
ActionIAMPersonalAPIKeyDelete = "iam:personal-api-key:delete"
// SAML Configuration actions
ActionIAMSAMLConfigurationCreate = "iam:saml-configuration:create"
ActionIAMSAMLConfigurationGet = "iam:saml-configuration:get"
ActionIAMSAMLConfigurationUpdate = "iam:saml-configuration:update"
ActionIAMSAMLConfigurationDelete = "iam:saml-configuration:delete"
ActionIAMSAMLConfigurationList = "iam:saml-configuration:list"
)

View File

@@ -34,14 +34,15 @@ var IAMSelfManageIdentityPolicy = policy.NewPolicy(
).WithSID("manage-own-identity").
When(policy.Equals("principal.id", "resource.id")),
// Users can list their own memberships and invitations
// Users can list their own memberships, invitations, sessions, and API keys
policy.Allow(
ActionIAMIdentityListMemberships,
ActionIAMIdentityListInvitations,
ActionIAMIdentityListSessions,
ActionIAMIdentityListPersonalAPIKeys,
).WithSID("list-own-associations").
When(policy.Equals("principal.id", "resource.id")),
).WithDescription("Allows users to manage their own identity, sessions, and view their memberships")
).WithDescription("Allows users to manage their own identity, sessions, API keys, and view their memberships")
// IAMSelfManageSessionPolicy allows users to manage their own sessions.
var IAMSelfManageSessionPolicy = policy.NewPolicy(
@@ -79,6 +80,20 @@ var IAMSelfManageMembershipPolicy = policy.NewPolicy(
When(policy.Equals("principal.id", "resource.user_id")),
).WithDescription("Allows users to view their organization memberships")
// IAMSelfManagePersonalAPIKeyPolicy allows users to manage their own API keys.
var IAMSelfManagePersonalAPIKeyPolicy = policy.NewPolicy(
"iam:self-manage-personal-api-key",
"Self-Manage Personal API Keys",
// Users can create, view, update, and delete their own API keys
policy.Allow(
ActionIAMPersonalAPIKeyCreate,
ActionIAMPersonalAPIKeyGet,
ActionIAMPersonalAPIKeyUpdate,
ActionIAMPersonalAPIKeyDelete,
).WithSID("manage-own-api-keys").
When(policy.Equals("principal.id", "resource.user_id")),
).WithDescription("Allows users to manage their own personal API keys")
// IAMOwnerPolicy defines permissions for organization owners.
var IAMOwnerPolicy = policy.NewPolicy(
"iam:owner",
@@ -92,6 +107,8 @@ var IAMOwnerPolicy = policy.NewPolicy(
ActionIAMInvitationGet,
ActionIAMInvitationDelete,
).WithSID("manage-invitations"),
// Full access to SAML configuration management
policy.Allow("iam:saml-configuration:*").WithSID("full-saml-access"),
).WithDescription("Full IAM access for organization owners")
// IAMAdminPolicy defines permissions for organization admins.
@@ -116,11 +133,22 @@ var IAMAdminPolicy = policy.NewPolicy(
ActionIAMInvitationGet,
ActionIAMInvitationDelete,
).WithSID("invitation-admin-access"),
// Can view SAML configurations
policy.Allow(
ActionIAMSAMLConfigurationGet,
ActionIAMSAMLConfigurationList,
).WithSID("saml-viewer-access"),
// Cannot delete organization
policy.Deny(ActionIAMOrganizationDelete).WithSID("deny-org-delete"),
// Cannot remove members (only owner can)
policy.Deny(ActionIAMOrganizationRemoveMember).WithSID("deny-remove-member"),
).WithDescription("IAM admin access - can manage members but cannot delete organization")
// Cannot manage SAML configurations (only owner can)
policy.Deny(
ActionIAMSAMLConfigurationCreate,
ActionIAMSAMLConfigurationUpdate,
ActionIAMSAMLConfigurationDelete,
).WithSID("deny-saml-management"),
).WithDescription("IAM admin access - can manage members but cannot delete organization or manage SAML")
// IAMViewerPolicy defines permissions for organization viewers.
var IAMViewerPolicy = policy.NewPolicy(

View File

@@ -12,9 +12,25 @@
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
// LEGACY: This is the legacy access management service that is used to authorize actions on entities.
// It is deprecated and will be removed in the future.
// Use the Authorizer instead.
// LEGACY PERMISSION SYSTEM - DEPRECATED
//
// This file contains the legacy permission system that maps entity types and actions
// to allowed roles. It is being replaced by a policy-based authorization system.
//
// Migration path:
// - New actions are defined in core_actions.go with namespaced format (e.g., "core:asset:get")
// - New policies are defined in core_policies.go and iam_policies.go
// - The action_mapping.go file provides a bridge between legacy and new actions
// - The Authorizer in authorizer.go evaluates policies using the new system
//
// During the migration period, the MustBeAuthorized function in resolvers will:
// 1. Attempt to map legacy actions to new namespaced actions
// 2. Use the new Authorizer if mapping succeeds
// 3. Fall back to LegacyAccessManagementService for unmapped actions or API key requests
//
// Once migration is complete, this file and access_management_service.go will be removed.
//
// Deprecated: Use Authorizer.Authorize() with new namespaced actions instead.
package iam
import (

View File

@@ -67,5 +67,6 @@ func IAMPolicySet() *PolicySet {
IAMSelfManageSessionPolicy,
IAMSelfManageInvitationPolicy,
IAMSelfManageMembershipPolicy,
IAMSelfManagePersonalAPIKeyPolicy,
)
}

View File

@@ -60,7 +60,6 @@ type (
Certificate *x509.Certificate
PrivateKey *rsa.PrivateKey
Logger *log.Logger
PolicySet *PolicySet
TracerProvider trace.TracerProvider
DomainVerificationInterval time.Duration
DomainVerificationResolverAddr string
@@ -113,15 +112,8 @@ func NewService(
svc.APIKeyService = NewAPIKeyService(svc)
svc.LegacyAccessManagementService = NewAccessManagementService(svc)
// Use provided PolicySet or default to IAM-only policies
policySet := NewPolicySet()
if cfg.PolicySet != nil {
policySet = cfg.PolicySet
}
policySet.Merge(IAMPolicySet())
svc.Authorizer = NewAuthorizer(pgClient, policySet)
svc.Authorizer = NewAuthorizer(pgClient)
svc.Authorizer.RegisterPolicySet(IAMPolicySet())
samlService, err := saml.NewService(svc.pg, svc.encryptionKey, svc.baseURL, svc.certificate, svc.privateKey, cfg.Logger)
if err != nil {

394
pkg/probo/action_mapping.go Normal file
View File

@@ -0,0 +1,394 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package probo
import (
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/iam"
)
// legacyActionMapping maps entity types to a mapping of legacy actions to new namespaced actions.
// This is used during the migration period to translate old action strings to new policy actions.
var legacyActionMapping = map[uint16]map[iam.Action]iam.Action{
coredata.OrganizationEntityType: {
iam.ActionGet: ActionOrganizationGet,
iam.ActionGetLogoUrl: ActionOrganizationGetLogoUrl,
iam.ActionGetHorizontalLogoUrl: ActionOrganizationGetHorizontalLogoUrl,
iam.ActionListDocuments: ActionDocumentList,
iam.ActionListSignableDocuments: ActionDocumentList,
iam.ActionPeoples: ActionPeopleList,
iam.ActionTotalCount: ActionOrganizationGet,
iam.ActionListFrameworks: ActionFrameworkList,
iam.ActionListControls: ActionControlList,
iam.ActionListVendors: ActionVendorList,
iam.ActionListPeople: ActionPeopleList,
iam.ActionListMeasures: ActionMeasureList,
iam.ActionListRisks: ActionRiskList,
iam.ActionListAssets: ActionAssetList,
iam.ActionListData: ActionDatumList,
iam.ActionListAudits: ActionAuditList,
iam.ActionListNonconformities: ActionNonconformityList,
iam.ActionListObligations: ActionObligationList,
iam.ActionListContinualImprovements: ActionContinualImprovementList,
iam.ActionListProcessingActivities: ActionProcessingActivityList,
iam.ActionListSnapshots: ActionSnapshotList,
iam.ActionAcceptInvitation: iam.ActionIAMInvitationAccept,
iam.ActionListTrustCenterFiles: ActionTrustCenterFileList,
iam.ActionGetTrustCenter: ActionTrustCenterGet,
iam.ActionMemberships: iam.ActionIAMOrganizationListMembers,
iam.ActionListMembers: iam.ActionIAMOrganizationListMembers,
iam.ActionListInvitations: iam.ActionIAMOrganizationListInvitations,
iam.ActionListSlackConnections: ActionSlackConnectionList,
iam.ActionGetCustomDomain: ActionCustomDomainGet,
iam.ActionListMeetings: ActionMeetingList,
iam.ActionListTasks: ActionTaskList,
iam.ActionUpdateOrganization: ActionOrganizationGet,
iam.ActionDeleteOrganizationHorizontalLogo: ActionOrganizationGet,
iam.ActionCreateTrustCenter: ActionTrustCenterGet,
iam.ActionInviteUser: iam.ActionIAMOrganizationInviteMember,
iam.ActionUpdateMembership: iam.ActionIAMMembershipUpdate,
iam.ActionCreatePeople: ActionPeopleCreate,
iam.ActionCreateVendor: ActionVendorCreate,
iam.ActionCreateFramework: ActionFrameworkCreate,
iam.ActionImportFramework: ActionFrameworkImport,
iam.ActionCreateControl: ActionControlCreate,
iam.ActionCreateMeasure: ActionMeasureCreate,
iam.ActionImportMeasure: ActionMeasureImport,
iam.ActionCreateMeeting: ActionMeetingCreate,
iam.ActionCreateTask: ActionTaskCreate,
iam.ActionCreateRisk: ActionRiskCreate,
iam.ActionCreateDocument: ActionDocumentCreate,
iam.ActionCreateAsset: ActionAssetCreate,
iam.ActionCreateDatum: ActionDatumCreate,
iam.ActionCreateAudit: ActionAuditCreate,
iam.ActionCreateNonconformity: ActionNonconformityCreate,
iam.ActionCreateObligation: ActionObligationCreate,
iam.ActionCreateContinualImprovement: ActionContinualImprovementCreate,
iam.ActionCreateProcessingActivity: ActionProcessingActivityCreate,
iam.ActionCreateSnapshot: ActionSnapshotCreate,
iam.ActionCreateTrustCenterFile: ActionTrustCenterFileCreate,
iam.ActionSendSigningNotifications: ActionDocumentSendSigningNotifications,
iam.ActionRemoveMember: iam.ActionIAMOrganizationRemoveMember,
iam.ActionCreateCustomDomain: ActionCustomDomainCreate,
iam.ActionDeleteCustomDomain: ActionCustomDomainDelete,
},
coredata.TrustCenterEntityType: {
iam.ActionGet: ActionTrustCenterGet,
iam.ActionGetOrganization: ActionOrganizationGet,
iam.ActionUpdateTrustCenter: ActionTrustCenterUpdate,
iam.ActionUploadTrustCenterNDA: ActionTrustCenterNonDisclosureAgreementUpload,
iam.ActionDeleteTrustCenterNDA: ActionTrustCenterNonDisclosureAgreementDelete,
iam.ActionCreateTrustCenterAccess: ActionTrustCenterAccessCreate,
iam.ActionCreateTrustCenterReference: ActionTrustCenterReferenceCreate,
},
coredata.TrustCenterAccessEntityType: {
iam.ActionUpdateTrustCenterAccess: ActionTrustCenterAccessUpdate,
iam.ActionDeleteTrustCenterAccess: ActionTrustCenterAccessDelete,
},
coredata.TrustCenterReferenceEntityType: {
iam.ActionUpdateTrustCenterReference: ActionTrustCenterReferenceUpdate,
iam.ActionDeleteTrustCenterReference: ActionTrustCenterReferenceDelete,
},
coredata.TrustCenterFileEntityType: {
iam.ActionUpdateTrustCenterFile: ActionTrustCenterFileUpdate,
iam.ActionDeleteTrustCenterFile: ActionTrustCenterFileDelete,
},
coredata.IdentityEntityType: {
iam.ActionGet: iam.ActionIAMIdentityGet,
},
coredata.MembershipEntityType: {
iam.ActionGet: iam.ActionIAMMembershipGet,
iam.ActionGetAuthMethod: iam.ActionIAMMembershipGet,
},
coredata.InvitationEntityType: {
iam.ActionGet: iam.ActionIAMInvitationGet,
iam.ActionGetOrganization: iam.ActionIAMInvitationGet,
iam.ActionDeleteInvitation: iam.ActionIAMInvitationDelete,
},
coredata.PeopleEntityType: {
iam.ActionGet: ActionPeopleGet,
iam.ActionUpdatePeople: ActionPeopleUpdate,
iam.ActionDeletePeople: ActionPeopleDelete,
},
coredata.VendorEntityType: {
iam.ActionGet: ActionVendorList,
iam.ActionGetOrganization: ActionOrganizationGet,
iam.ActionGetBusinessOwner: ActionPeopleGet,
iam.ActionGetSecurityOwner: ActionPeopleGet,
iam.ActionUpdateVendor: ActionVendorUpdate,
iam.ActionDeleteVendor: ActionVendorDelete,
iam.ActionCreateVendorContact: ActionVendorContactCreate,
iam.ActionCreateVendorService: ActionVendorServiceCreate,
iam.ActionUploadVendorComplianceReport: ActionVendorComplianceReportUpload,
iam.ActionUploadVendorBusinessAssociateAgreement: ActionVendorBusinessAssociateAgreementUpload,
iam.ActionDeleteVendorBusinessAssociateAgreement: ActionVendorBusinessAssociateAgreementDelete,
iam.ActionUploadVendorDataPrivacyAgreement: ActionVendorDataPrivacyAgreementUpload,
iam.ActionCreateVendorRiskAssessment: ActionVendorRiskAssessmentCreate,
iam.ActionAssessVendor: ActionVendorAssess,
},
coredata.VendorComplianceReportEntityType: {
iam.ActionGet: ActionVendorList,
iam.ActionGetVendor: ActionVendorList,
iam.ActionDeleteVendorComplianceReport: ActionVendorComplianceReportDelete,
},
coredata.VendorBusinessAssociateAgreementEntityType: {
iam.ActionGet: ActionVendorList,
iam.ActionGetVendor: ActionVendorList,
iam.ActionGetFileUrl: ActionFileDownloadUrl,
iam.ActionUpdateVendorBusinessAssociateAgreement: ActionVendorBusinessAssociateAgreementUpdate,
iam.ActionDeleteVendorBusinessAssociateAgreement: ActionVendorBusinessAssociateAgreementDelete,
},
coredata.VendorContactEntityType: {
iam.ActionGet: ActionVendorList,
iam.ActionGetVendor: ActionVendorList,
iam.ActionUpdateVendorContact: ActionVendorContactUpdate,
iam.ActionDeleteVendorContact: ActionVendorContactDelete,
},
coredata.VendorServiceEntityType: {
iam.ActionGet: ActionVendorList,
iam.ActionGetVendor: ActionVendorList,
iam.ActionUpdateVendorService: ActionVendorServiceUpdate,
iam.ActionDeleteVendorService: ActionVendorServiceDelete,
},
coredata.VendorDataPrivacyAgreementEntityType: {
iam.ActionGet: ActionVendorList,
iam.ActionGetVendor: ActionVendorList,
iam.ActionGetFileUrl: ActionFileDownloadUrl,
iam.ActionUpdateVendorDataPrivacyAgreement: ActionVendorDataPrivacyAgreementUpdate,
iam.ActionDeleteVendorDataPrivacyAgreement: ActionVendorDataPrivacyAgreementDelete,
},
coredata.VendorRiskAssessmentEntityType: {
iam.ActionGet: ActionVendorList,
},
coredata.FrameworkEntityType: {
iam.ActionGet: ActionFrameworkGet,
iam.ActionGetOrganization: ActionOrganizationGet,
iam.ActionListControls: ActionControlList,
iam.ActionCreateControl: ActionControlCreate,
iam.ActionUpdateFramework: ActionFrameworkUpdate,
iam.ActionDeleteFramework: ActionFrameworkDelete,
iam.ActionGenerateFrameworkStateOfApplicability: ActionFrameworkStateOfApplicabilityGenerate,
iam.ActionExportFramework: ActionFrameworkExport,
},
coredata.ControlEntityType: {
iam.ActionGet: ActionControlList,
iam.ActionGetFramework: ActionFrameworkGet,
iam.ActionListMeasures: ActionMeasureList,
iam.ActionListDocuments: ActionDocumentList,
iam.ActionListAudits: ActionAuditList,
iam.ActionListSnapshots: ActionSnapshotList,
iam.ActionUpdateControl: ActionControlUpdate,
iam.ActionDeleteControl: ActionControlDelete,
iam.ActionCreateControlMeasureMapping: ActionControlMeasureMappingCreate,
iam.ActionCreateControlDocumentMapping: ActionControlDocumentMappingCreate,
iam.ActionDeleteControlMeasureMapping: ActionControlMeasureMappingDelete,
iam.ActionDeleteControlDocumentMapping: ActionControlDocumentMappingDelete,
iam.ActionCreateControlAuditMapping: ActionControlAuditMappingCreate,
iam.ActionDeleteControlAuditMapping: ActionControlAuditMappingDelete,
iam.ActionCreateControlSnapshotMapping: ActionControlSnapshotMappingCreate,
iam.ActionDeleteControlSnapshotMapping: ActionControlSnapshotMappingDelete,
},
coredata.MeasureEntityType: {
iam.ActionGet: ActionMeasureGet,
iam.ActionListTasks: ActionTaskList,
iam.ActionListEvidences: ActionEvidenceList,
iam.ActionListRisks: ActionRiskList,
iam.ActionListControls: ActionControlList,
iam.ActionTotalCount: ActionMeasureList,
iam.ActionUpdateMeasure: ActionMeasureUpdate,
iam.ActionDeleteMeasure: ActionMeasureDelete,
iam.ActionUploadMeasureEvidence: ActionMeasureEvidenceUpload,
},
coredata.TaskEntityType: {
iam.ActionGet: ActionTaskGet,
iam.ActionGetAssignedTo: ActionPeopleGet,
iam.ActionGetOrganization: ActionOrganizationGet,
iam.ActionGetMeasure: ActionMeasureGet,
iam.ActionListEvidences: ActionEvidenceList,
iam.ActionUpdateTask: ActionTaskUpdate,
iam.ActionDeleteTask: ActionTaskDelete,
iam.ActionAssignTask: ActionTaskAssign,
iam.ActionUnassignTask: ActionTaskUnassign,
},
coredata.EvidenceEntityType: {
iam.ActionGet: ActionEvidenceList,
iam.ActionGetFile: ActionFileGet,
iam.ActionGetTask: ActionTaskGet,
iam.ActionGetMeasure: ActionMeasureList,
iam.ActionDeleteEvidence: ActionEvidenceDelete,
},
coredata.DocumentEntityType: {
iam.ActionGet: ActionDocumentGet,
iam.ActionGetOwner: ActionPeopleGet,
iam.ActionGetOrganization: ActionOrganizationGet,
iam.ActionGetSigned: ActionDocumentList,
iam.ActionGetSignableDocument: ActionDocumentList,
iam.ActionListSignableDocumentVersion: ActionDocumentList,
iam.ActionBulkExportDocuments: ActionDocumentList,
iam.ActionTotalCount: ActionDocumentList,
iam.ActionListControls: ActionControlList,
iam.ActionListVersions: ActionDocumentVersionList,
iam.ActionUpdateDocument: ActionDocumentUpdate,
iam.ActionDeleteDocument: ActionDocumentDelete,
iam.ActionBulkDeleteDocuments: ActionDocumentDelete,
iam.ActionPublishDocumentVersion: ActionDocumentVersionPublish,
iam.ActionBulkPublishDocumentVersions: ActionDocumentVersionPublish,
iam.ActionGenerateDocumentChangelog: ActionDocumentChangelogGenerate,
iam.ActionCreateDraftDocumentVersion: ActionDocumentDraftVersionCreate,
iam.ActionDeleteDraftDocumentVersion: ActionDocumentVersionDeleteDraft,
iam.ActionUpdateDocumentVersion: ActionDocumentVersionUpdate,
iam.ActionRequestSignature: ActionDocumentVersionSignatureRequest,
iam.ActionBulkRequestSignatures: ActionDocumentVersionSignatureRequest,
iam.ActionSendSigningNotifications: ActionDocumentSendSigningNotifications,
iam.ActionCancelSignatureRequest: ActionDocumentVersionCancelSignature,
},
coredata.DocumentVersionEntityType: {
iam.ActionGet: ActionDocumentVersionGet,
iam.ActionGetFile: ActionFileGet,
iam.ActionGetOwner: ActionPeopleGet,
iam.ActionGetDocument: ActionDocumentGet,
iam.ActionGetSigned: ActionDocumentVersionGet,
iam.ActionSignatures: ActionDocumentVersionSignatureList,
iam.ActionExportDocumentVersionPDF: ActionDocumentVersionExportPDF,
iam.ActionExportSignableVersionDocumentPDF: ActionDocumentVersionExportSignable,
iam.ActionSignDocument: ActionDocumentVersionSign,
iam.ActionUpdateDocumentVersion: ActionDocumentVersionUpdate,
iam.ActionRequestSignature: ActionDocumentVersionSignatureRequest,
iam.ActionDeleteDraftDocumentVersion: ActionDocumentVersionDeleteDraft,
},
coredata.DocumentVersionSignatureEntityType: {
iam.ActionGet: ActionDocumentVersionSignatureList,
iam.ActionDocumentVersion: ActionDocumentVersionGet,
iam.ActionSignedBy: ActionPeopleGet,
},
coredata.RiskEntityType: {
iam.ActionGet: ActionRiskList,
iam.ActionGetOwner: ActionPeopleGet,
iam.ActionGetOrganization: ActionOrganizationGet,
iam.ActionTotalCount: ActionRiskList,
iam.ActionListControls: ActionControlList,
iam.ActionListMeasures: ActionMeasureList,
iam.ActionListDocuments: ActionDocumentList,
iam.ActionListObligations: ActionObligationList,
iam.ActionUpdateRisk: ActionRiskUpdate,
iam.ActionDeleteRisk: ActionRiskDelete,
iam.ActionCreateRiskMeasureMapping: ActionRiskMeasureMappingCreate,
iam.ActionDeleteRiskMeasureMapping: ActionRiskMeasureMappingDelete,
iam.ActionCreateRiskDocumentMapping: ActionRiskDocumentMappingCreate,
iam.ActionDeleteRiskDocumentMapping: ActionRiskDocumentMappingDelete,
iam.ActionCreateRiskObligationMapping: ActionRiskObligationMappingCreate,
iam.ActionDeleteRiskObligationMapping: ActionRiskObligationMappingDelete,
},
coredata.AssetEntityType: {
iam.ActionGet: ActionAssetList,
iam.ActionGetOwner: ActionPeopleGet,
iam.ActionListVendors: ActionVendorList,
iam.ActionGetAssetType: ActionAssetList,
iam.ActionGetOrganization: ActionOrganizationGet,
iam.ActionUpdateAsset: ActionAssetUpdate,
iam.ActionDeleteAsset: ActionAssetDelete,
},
coredata.DatumEntityType: {
iam.ActionGet: ActionDatumList,
iam.ActionGetOwner: ActionPeopleGet,
iam.ActionGetOrganization: ActionOrganizationGet,
iam.ActionListVendors: ActionVendorList,
iam.ActionUpdateDatum: ActionDatumUpdate,
iam.ActionDeleteDatum: ActionDatumDelete,
},
coredata.AuditEntityType: {
iam.ActionGet: ActionAuditGet,
iam.ActionGetFile: ActionFileGet,
iam.ActionGetFramework: ActionFrameworkGet,
iam.ActionGetOrganization: ActionOrganizationGet,
iam.ActionReport: ActionAuditList,
iam.ActionReportUrl: ActionAuditList,
iam.ActionListControls: ActionControlList,
iam.ActionUpdateAudit: ActionAuditUpdate,
iam.ActionDeleteAudit: ActionAuditDelete,
iam.ActionUploadAuditReport: ActionAuditReportUpload,
iam.ActionDeleteAuditReport: ActionAuditReportDelete,
},
coredata.ReportEntityType: {
iam.ActionGet: ActionReportGet,
iam.ActionGetAudit: ActionAuditGet,
iam.ActionGetFile: ActionFileGet,
iam.ActionGetOrganization: ActionOrganizationGet,
iam.ActionGetSnapshot: ActionSnapshotList,
iam.ActionDownloadUrl: ActionReportDownloadUrlGet,
},
coredata.NonconformityEntityType: {
iam.ActionGet: ActionNonconformityList,
iam.ActionGetOwner: ActionPeopleGet,
iam.ActionGetOrganization: ActionOrganizationGet,
iam.ActionAudit: ActionAuditList,
iam.ActionUpdateNonconformity: ActionNonconformityUpdate,
iam.ActionDeleteNonconformity: ActionNonconformityDelete,
},
coredata.ObligationEntityType: {
iam.ActionGet: ActionObligationList,
iam.ActionGetOrganization: ActionOrganizationGet,
iam.ActionGetOwner: ActionPeopleGet,
iam.ActionListRisks: ActionRiskList,
iam.ActionUpdateObligation: ActionObligationUpdate,
iam.ActionDeleteObligation: ActionObligationDelete,
},
coredata.ContinualImprovementEntityType: {
iam.ActionGet: ActionContinualImprovementList,
iam.ActionGetOwner: ActionPeopleGet,
iam.ActionGetOrganization: ActionOrganizationGet,
iam.ActionUpdateContinualImprovement: ActionContinualImprovementUpdate,
iam.ActionDeleteContinualImprovement: ActionContinualImprovementDelete,
},
coredata.ProcessingActivityEntityType: {
iam.ActionGet: ActionProcessingActivityList,
iam.ActionGetOrganization: ActionOrganizationGet,
iam.ActionListVendors: ActionVendorList,
iam.ActionUpdateProcessingActivity: ActionProcessingActivityUpdate,
iam.ActionDeleteProcessingActivity: ActionProcessingActivityDelete,
},
coredata.SnapshotEntityType: {
iam.ActionGet: ActionSnapshotList,
iam.ActionGetOrganization: ActionOrganizationGet,
iam.ActionListControls: ActionControlList,
iam.ActionDeleteSnapshot: ActionSnapshotDelete,
},
coredata.CustomDomainEntityType: {
iam.ActionGet: ActionCustomDomainGet,
iam.ActionDeleteCustomDomain: ActionCustomDomainDelete,
},
coredata.FileEntityType: {
iam.ActionGet: ActionFileGet,
iam.ActionDownloadUrl: ActionFileDownloadUrl,
},
coredata.MeetingEntityType: {
iam.ActionGet: ActionMeetingList,
iam.ActionGetOrganization: ActionOrganizationGet,
iam.ActionTotalCount: ActionMeetingList,
iam.ActionUpdateMeeting: ActionMeetingUpdate,
iam.ActionDeleteMeeting: ActionMeetingDelete,
},
}
// MapLegacyAction converts a legacy action string to a new namespaced action.
// Returns the new action and true if found, or empty string and false if not mapped.
func MapLegacyAction(entityType uint16, legacyAction iam.Action) (iam.Action, bool) {
if entityActions, ok := legacyActionMapping[entityType]; ok {
if newAction, ok := entityActions[legacyAction]; ok {
return newAction, true
}
}
return "", false
}

281
pkg/probo/actions.go Normal file
View File

@@ -0,0 +1,281 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package probo
// Probo Service Actions
// Format: core:<entity>:<action>
const (
// Organization actions
ActionOrganizationGet = "core:organization:get"
ActionOrganizationGetLogoUrl = "core:organization:get-logo-url"
ActionOrganizationGetHorizontalLogoUrl = "core:organization:get-horizontal-logo-url"
// OrganizationContext actions
ActionOrganizationContextGet = "core:organization-context:get"
ActionOrganizationContextUpdate = "core:organization-context:update"
// TrustCenter actions
ActionTrustCenterGet = "core:trust-center:get"
ActionTrustCenterUpdate = "core:trust-center:update"
ActionTrustCenterNonDisclosureAgreementUpload = "core:trust-center:upload-nda"
ActionTrustCenterNonDisclosureAgreementDelete = "core:trust-center:delete-nda"
ActionTrustCenterAccessCreate = "core:trust-center:create-access"
ActionTrustCenterReferenceCreate = "core:trust-center:create-reference"
// TrustCenterAccess actions
ActionTrustCenterAccessUpdate = "core:trust-center-access:update"
ActionTrustCenterAccessDelete = "core:trust-center-access:delete"
// TrustCenterReference actions
ActionTrustCenterReferenceUpdate = "core:trust-center-reference:update"
ActionTrustCenterReferenceDelete = "core:trust-center-reference:delete"
// TrustCenterFile actions
ActionTrustCenterFileGet = "core:trust-center-file:get"
ActionTrustCenterFileList = "core:trust-center-file:list"
ActionTrustCenterFileUpdate = "core:trust-center-file:update"
ActionTrustCenterFileDelete = "core:trust-center-file:delete"
ActionTrustCenterFileCreate = "core:trust-center-file:create"
// People actions
ActionPeopleGet = "core:people:get"
ActionPeopleList = "core:people:list"
ActionPeopleCreate = "core:people:create"
ActionPeopleUpdate = "core:people:update"
ActionPeopleDelete = "core:people:delete"
// Vendor actions
ActionVendorList = "core:vendor:list"
ActionVendorGet = "core:vendor:get"
ActionVendorCreate = "core:vendor:create"
ActionVendorUpdate = "core:vendor:update"
ActionVendorDelete = "core:vendor:delete"
ActionVendorAssess = "core:vendor:assess"
// VendorContact actions
ActionVendorContactCreate = "core:vendor-contact:create"
ActionVendorContactUpdate = "core:vendor-contact:update"
ActionVendorContactDelete = "core:vendor-contact:delete"
// VendorService actions
ActionVendorServiceCreate = "core:vendor-service:create"
ActionVendorServiceUpdate = "core:vendor-service:update"
ActionVendorServiceDelete = "core:vendor-service:delete"
// VendorComplianceReport actions
ActionVendorComplianceReportUpload = "core:vendor-compliance-report:upload"
ActionVendorComplianceReportDelete = "core:vendor-compliance-report:delete"
// VendorBusinessAssociateAgreement actions
ActionVendorBusinessAssociateAgreementUpload = "core:vendor-business-associate-agreement:upload"
ActionVendorBusinessAssociateAgreementUpdate = "core:vendor-business-associate-agreement:update"
ActionVendorBusinessAssociateAgreementDelete = "core:vendor-business-associate-agreement:delete"
// VendorDataPrivacyAgreement actions
ActionVendorDataPrivacyAgreementUpload = "core:vendor-data-privacy-agreement:upload"
ActionVendorDataPrivacyAgreementUpdate = "core:vendor-data-privacy-agreement:update"
ActionVendorDataPrivacyAgreementDelete = "core:vendor-data-privacy-agreement:delete"
// VendorRiskAssessment actions
ActionVendorRiskAssessmentCreate = "core:vendor-risk-assessment:create"
// Framework actions
ActionFrameworkGet = "core:framework:get"
ActionFrameworkList = "core:framework:list"
ActionFrameworkCreate = "core:framework:create"
ActionFrameworkUpdate = "core:framework:update"
ActionFrameworkDelete = "core:framework:delete"
ActionFrameworkStateOfApplicabilityGenerate = "core:framework:generate-state-of-applicability"
ActionFrameworkExport = "core:framework:export"
ActionFrameworkImport = "core:framework:import"
// Control actions
ActionControlList = "core:control:list"
ActionControlCreate = "core:control:create"
ActionControlUpdate = "core:control:update"
ActionControlDelete = "core:control:delete"
ActionControlMeasureMappingCreate = "core:control:create-measure-mapping"
ActionControlMeasureMappingDelete = "core:control:delete-measure-mapping"
ActionControlDocumentMappingCreate = "core:control:create-document-mapping"
ActionControlDocumentMappingDelete = "core:control:delete-document-mapping"
ActionControlAuditMappingCreate = "core:control:create-audit-mapping"
ActionControlAuditMappingDelete = "core:control:delete-audit-mapping"
ActionControlSnapshotMappingCreate = "core:control:create-snapshot-mapping"
ActionControlSnapshotMappingDelete = "core:control:delete-snapshot-mapping"
// Measure actions
ActionMeasureGet = "core:measure:get"
ActionMeasureList = "core:measure:list"
ActionMeasureCreate = "core:measure:create"
ActionMeasureUpdate = "core:measure:update"
ActionMeasureDelete = "core:measure:delete"
ActionMeasureEvidenceUpload = "core:measure:upload-evidence"
ActionMeasureImport = "core:measure:import"
// Task actions
ActionTaskGet = "core:task:get"
ActionTaskList = "core:task:list"
ActionTaskCreate = "core:task:create"
ActionTaskUpdate = "core:task:update"
ActionTaskDelete = "core:task:delete"
ActionTaskAssign = "core:task:assign"
ActionTaskUnassign = "core:task:unassign"
// Evidence actions
ActionEvidenceList = "core:evidence:list"
ActionEvidenceDelete = "core:evidence:delete"
// Document actions
ActionDocumentGet = "core:document:get"
ActionDocumentList = "core:document:list"
ActionDocumentCreate = "core:document:create"
ActionDocumentUpdate = "core:document:update"
ActionDocumentDelete = "core:document:delete"
ActionDocumentChangelogGenerate = "core:document:generate-changelog"
ActionDocumentDraftVersionCreate = "core:document:create-draft-version"
ActionDocumentSendSigningNotifications = "core:document:send-signing-notifications"
// DocumentVersion actions
ActionDocumentVersionGet = "core:document-version:get"
ActionDocumentVersionList = "core:document-version:list"
ActionDocumentVersionExportPDF = "core:document-version:export-pdf"
ActionDocumentVersionExportSignable = "core:document-version:export-signable-pdf"
ActionDocumentVersionSign = "core:document-version:sign"
ActionDocumentVersionUpdate = "core:document-version:update"
ActionDocumentVersionSignatureRequest = "core:document-version:request-signature"
ActionDocumentVersionDeleteDraft = "core:document-version:delete-draft"
ActionDocumentVersionPublish = "core:document-version:publish"
ActionDocumentVersionCancelSignature = "core:document-version:cancel-signature"
ActionDocumentVersionExport = "core:document-version:export"
// DocumentVersionSignature actions
ActionDocumentVersionSignatureList = "core:document-version-signature:list"
// Risk actions
ActionRiskList = "core:risk:list"
ActionRiskCreate = "core:risk:create"
ActionRiskUpdate = "core:risk:update"
ActionRiskDelete = "core:risk:delete"
ActionRiskMeasureMappingCreate = "core:risk:create-measure-mapping"
ActionRiskMeasureMappingDelete = "core:risk:delete-measure-mapping"
ActionRiskDocumentMappingCreate = "core:risk:create-document-mapping"
ActionRiskDocumentMappingDelete = "core:risk:delete-document-mapping"
ActionRiskObligationMappingCreate = "core:risk:create-obligation-mapping"
ActionRiskObligationMappingDelete = "core:risk:delete-obligation-mapping"
// Asset actions
ActionAssetList = "core:asset:list"
ActionAssetCreate = "core:asset:create"
ActionAssetUpdate = "core:asset:update"
ActionAssetDelete = "core:asset:delete"
// Datum actions
ActionDatumList = "core:datum:list"
ActionDatumCreate = "core:datum:create"
ActionDatumUpdate = "core:datum:update"
ActionDatumDelete = "core:datum:delete"
// Audit actions
ActionAuditGet = "core:audit:get"
ActionAuditList = "core:audit:list"
ActionAuditCreate = "core:audit:create"
ActionAuditUpdate = "core:audit:update"
ActionAuditDelete = "core:audit:delete"
ActionAuditReportUpload = "core:audit:upload-report"
ActionAuditReportDelete = "core:audit:delete-report"
// Report actions
ActionReportGet = "core:report:get"
ActionReportGetReportUrl = "core:report:get-report-url"
ActionReportDownloadUrlGet = "core:report:get-download-url"
// Nonconformity actions
ActionNonconformityList = "core:nonconformity:list"
ActionNonconformityCreate = "core:nonconformity:create"
ActionNonconformityUpdate = "core:nonconformity:update"
ActionNonconformityDelete = "core:nonconformity:delete"
// Obligation actions
ActionObligationList = "core:obligation:list"
ActionObligationCreate = "core:obligation:create"
ActionObligationUpdate = "core:obligation:update"
ActionObligationDelete = "core:obligation:delete"
// ContinualImprovement actions
ActionContinualImprovementList = "core:continual-improvement:list"
ActionContinualImprovementCreate = "core:continual-improvement:create"
ActionContinualImprovementUpdate = "core:continual-improvement:update"
ActionContinualImprovementDelete = "core:continual-improvement:delete"
// ProcessingActivity actions
ActionProcessingActivityList = "core:processing-activity:list"
ActionProcessingActivityGet = "core:processing-activity:get"
ActionProcessingActivityCreate = "core:processing-activity:create"
ActionProcessingActivityUpdate = "core:processing-activity:update"
ActionProcessingActivityDelete = "core:processing-activity:delete"
ActionProcessingActivityExport = "core:processing-activity:export"
// Snapshot actions
ActionSnapshotList = "core:snapshot:list"
ActionSnapshotCreate = "core:snapshot:create"
ActionSnapshotDelete = "core:snapshot:delete"
// CustomDomain actions
ActionCustomDomainGet = "core:custom-domain:get"
ActionCustomDomainCreate = "core:custom-domain:create"
ActionCustomDomainDelete = "core:custom-domain:delete"
// File actions
ActionFileGet = "core:file:get"
ActionFileDownloadUrl = "core:file:download-url"
// Meeting actions
ActionMeetingList = "core:meeting:list"
ActionMeetingCreate = "core:meeting:create"
ActionMeetingUpdate = "core:meeting:update"
ActionMeetingDelete = "core:meeting:delete"
// SlackConnection actions
ActionSlackConnectionList = "core:slack-connection:list"
ActionDataProtectionImpactAssessmentList = "core:data-protection-impact-assessment:list"
ActionDataProtectionImpactAssessmentCreate = "core:data-protection-impact-assessment:create"
ActionDataProtectionImpactAssessmentUpdate = "core:data-protection-impact-assessment:update"
ActionDataProtectionImpactAssessmentDelete = "core:data-protection-impact-assessment:delete"
ActionTransferImpactAssessmentList = "core:transfer-impact-assessment:list"
ActionTransferImpactAssessmentCreate = "core:transfer-impact-assessment:create"
ActionTransferImpactAssessmentUpdate = "core:transfer-impact-assessment:update"
ActionTransferImpactAssessmentDelete = "core:transfer-impact-assessment:delete"
ActionDataProtectionOfficerList = "core:data-protection-officer:list"
ActionRightsRequesList = "core:rights-request:list"
ActionStateOfApplicabilityList = "core:state-of-applicability:list"
ActionStateOfApplicabilityGet = "core:state-of-applicability:get"
ActionStateOfApplicabilityCreate = "core:state-of-applicability:create"
ActionStateOfApplicabilityUpdate = "core:state-of-applicability:update"
ActionStateOfApplicabilityDelete = "core:state-of-applicability:delete"
ActionStateOfApplicabilityExport = "core:state-of-applicability:export"
ActionStateOfApplicabilityControlMappingList = "core:state-of-applicability-control-mapping:list"
ActionStateOfApplicabilityControlMappingCreate = "core:state-of-applicability-control-mapping:create"
ActionStateOfApplicabilityControlMappingDelete = "core:state-of-applicability-control-mapping:delete"
ActionControlObligationMappingList = "core:control-obligation-mapping:list"
ActionControlObligationMappingCreate = "core:control-obligation-mapping:create"
ActionControlObligationMappingDelete = "core:control-obligation-mapping:delete"
)

169
pkg/probo/policies.go Normal file
View File

@@ -0,0 +1,169 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package probo
import (
"go.probo.inc/probo/pkg/iam"
"go.probo.inc/probo/pkg/iam/policy"
)
// OwnerPolicy defines permissions for organization owners.
var OwnerPolicy = policy.NewPolicy(
"probo:owner",
"Probo Owner",
// Full access to all probo resources
policy.Allow("core:*").WithSID("full-core-access"),
).WithDescription("Full probo access for organization owners")
// AdminPolicy defines permissions for organization admins.
var AdminPolicy = policy.NewPolicy(
"probo:admin",
"Probo Admin",
// Full access to all probo resources (same as owner for core entities)
policy.Allow("core:*").WithSID("full-core-access"),
).WithDescription("Probo admin access - can manage core entities")
// ViewerPolicy defines read-only permissions for organization viewers.
var ViewerPolicy = policy.NewPolicy(
"probo:viewer",
"Probo Viewer",
// Organization read actions
policy.Allow(
ActionOrganizationGet,
ActionOrganizationGetLogoUrl,
ActionOrganizationGetHorizontalLogoUrl,
).WithSID("org-read-access"),
// Entity read actions
policy.Allow(
ActionPeopleGet, ActionPeopleList,
ActionVendorList,
ActionFrameworkGet, ActionFrameworkList,
ActionControlList,
ActionMeasureGet, ActionMeasureList,
ActionTaskGet, ActionTaskList,
ActionEvidenceList,
ActionDocumentGet, ActionDocumentList,
ActionDocumentVersionGet, ActionDocumentVersionList,
ActionDocumentVersionSignatureList,
ActionRiskList,
ActionAssetList,
ActionDatumList,
ActionAuditGet, ActionAuditList,
ActionReportGet, ActionReportGetReportUrl, ActionReportDownloadUrlGet,
ActionNonconformityList,
ActionObligationList,
ActionContinualImprovementList,
ActionProcessingActivityList,
ActionSnapshotList,
ActionMeetingList,
ActionFileGet, ActionFileDownloadUrl,
ActionSlackConnectionList,
).WithSID("entity-read-access"),
// TrustCenter read actions
policy.Allow(
ActionTrustCenterGet,
ActionTrustCenterFileGet, ActionTrustCenterFileList,
).WithSID("trust-center-read-access"),
// CustomDomain read actions
policy.Allow(ActionCustomDomainGet).WithSID("custom-domain-read"),
// OrganizationContext read actions
policy.Allow(ActionOrganizationContextGet).WithSID("organization-context-read"),
// Document signing actions
policy.Allow(
ActionDocumentVersionExportPDF, ActionDocumentVersionExportSignable, ActionDocumentVersionSign,
).WithSID("document-signing"),
).WithDescription("Read-only probo access for organization viewers")
// AuditorPolicy defines permissions for auditor role.
// Auditors have read access to non-employee content plus some specific auditor features.
var AuditorPolicy = policy.NewPolicy(
"probo:auditor",
"Probo Auditor",
// Same as viewer but without employee-specific content
policy.Allow(
ActionOrganizationGet,
ActionOrganizationGetLogoUrl,
ActionOrganizationGetHorizontalLogoUrl,
).WithSID("org-read-access"),
// Entity read access (same as viewer)
policy.Allow(
ActionPeopleGet, ActionPeopleList,
ActionVendorList,
ActionFrameworkGet, ActionFrameworkList,
ActionControlList,
ActionMeasureGet, ActionMeasureList,
ActionEvidenceList,
ActionDocumentGet, ActionDocumentList,
ActionDocumentVersionGet, ActionDocumentVersionList,
ActionDocumentVersionSignatureList,
ActionRiskList,
ActionAssetList,
ActionDatumList,
ActionAuditGet, ActionAuditList,
ActionReportGet, ActionReportGetReportUrl, ActionReportDownloadUrlGet,
ActionNonconformityList,
ActionObligationList,
ActionContinualImprovementList,
ActionProcessingActivityList,
ActionSnapshotList,
ActionFileGet, ActionFileDownloadUrl,
).WithSID("entity-read-access"),
// Document signing actions
policy.Allow(
ActionDocumentVersionExportPDF, ActionDocumentVersionExportSignable, ActionDocumentVersionSign,
).WithSID("document-signing"),
).WithDescription("Read-only probo access for auditors (excludes internal/employee content)")
// EmployeePolicy defines permissions for employee role.
// Employees have access to internal documents and some limited read access.
var EmployeePolicy = policy.NewPolicy(
"probo:employee",
"Probo Employee",
// Basic organization access
policy.Allow(
ActionOrganizationGet,
ActionOrganizationGetLogoUrl,
).WithSID("org-basic-access"),
// Document signing access
policy.Allow(
ActionDocumentGet, ActionDocumentList,
).WithSID("document-signing-access"),
// Document version signing
policy.Allow(
ActionDocumentVersionGet, ActionDocumentVersionList,
ActionDocumentVersionSign,
ActionDocumentVersionExportSignable,
).WithSID("document-version-signing"),
).WithDescription("Employee access - can sign documents and view internal content")
// ProboPolicySet returns the PolicySet for the probo service.
// This is registered with the IAM Authorizer when probo.Service is created.
func ProboPolicySet() *iam.PolicySet {
return iam.NewPolicySet().
AddRolePolicy("OWNER", OwnerPolicy).
AddRolePolicy("ADMIN", AdminPolicy).
AddRolePolicy("VIEWER", ViewerPolicy).
AddRolePolicy("AUDITOR", AuditorPolicy).
AddRolePolicy("EMPLOYEE", EmployeePolicy)
}

View File

@@ -31,6 +31,7 @@ import (
"go.probo.inc/probo/pkg/filevalidation"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/html2pdf"
"go.probo.inc/probo/pkg/iam"
"go.probo.inc/probo/pkg/mail"
"go.probo.inc/probo/pkg/slack"
)
@@ -135,11 +136,14 @@ func NewService(
fileManagerService *filemanager.Service,
logger *log.Logger,
slackService *slack.Service,
iamService *iam.Service,
) (*Service, error) {
if bucket == "" {
return nil, fmt.Errorf("bucket is required")
}
iamService.Authorizer.RegisterPolicySet(ProboPolicySet())
svc := &Service{
pg: pgClient,
s3: s3Client,

View File

@@ -388,6 +388,7 @@ func (impl *Implm) Run(
fileManagerService,
l.Named("probo"),
slackService,
iamService,
)
if err != nil {
return fmt.Errorf("cannot create probo service: %w", err)

View File

@@ -230,7 +230,7 @@ type Membership implements Node {
profile: MembershipProfile @goField(forceResolver: true) @isViewer
organization: Organization @goField(forceResolver: true)
role: MembershipRole!
permissions: [Permission!]!
permissions: [Permission!] @goField(forceResolver: true)
lastSession: Session @goField(forceResolver: true) @isViewer
}
@@ -276,11 +276,6 @@ type Permission implements Node {
principalId: ID!
}
type PermissionGrant {
application: Application!
accessLevel: AccessLevel!
}
type Application {
id: ApplicationId!
name: String!
@@ -303,7 +298,6 @@ type SAMLConfiguration implements Node {
spMetadataUrl: String!
testLoginUrl: String!
attributeMappings: SAMLAttributeMappings!
defaultPermissions: [PermissionGrant!]!
}
type SAMLAttributeMappings {

View File

@@ -267,11 +267,6 @@ type ComplexityRoot struct {
PrincipalType func(childComplexity int) int
}
PermissionGrant struct {
AccessLevel func(childComplexity int) int
Application func(childComplexity int) int
}
PersonalAPIKey struct {
CreatedAt func(childComplexity int) int
ExpiresAt func(childComplexity int) int
@@ -335,7 +330,6 @@ type ComplexityRoot struct {
AttributeMappings func(childComplexity int) int
AutoSignupEnabled func(childComplexity int) int
CreatedAt func(childComplexity int) int
DefaultPermissions func(childComplexity int) int
DomainVerificationToken func(childComplexity int) int
DomainVerifiedAt func(childComplexity int) int
EmailDomain func(childComplexity int) int
@@ -438,6 +432,7 @@ type MembershipResolver interface {
Profile(ctx context.Context, obj *types.Membership) (*types.MembershipProfile, error)
Organization(ctx context.Context, obj *types.Membership) (*types.Organization, error)
Permissions(ctx context.Context, obj *types.Membership) ([]*types.Permission, error)
LastSession(ctx context.Context, obj *types.Membership) (*types.Session, error)
}
type MembershipConnectionResolver interface {
@@ -1385,19 +1380,6 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.Permission.PrincipalType(childComplexity), true
case "PermissionGrant.accessLevel":
if e.complexity.PermissionGrant.AccessLevel == nil {
break
}
return e.complexity.PermissionGrant.AccessLevel(childComplexity), true
case "PermissionGrant.application":
if e.complexity.PermissionGrant.Application == nil {
break
}
return e.complexity.PermissionGrant.Application(childComplexity), true
case "PersonalAPIKey.createdAt":
if e.complexity.PersonalAPIKey.CreatedAt == nil {
break
@@ -1593,12 +1575,6 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
}
return e.complexity.SAMLConfiguration.CreatedAt(childComplexity), true
case "SAMLConfiguration.defaultPermissions":
if e.complexity.SAMLConfiguration.DefaultPermissions == nil {
break
}
return e.complexity.SAMLConfiguration.DefaultPermissions(childComplexity), true
case "SAMLConfiguration.domainVerificationToken":
if e.complexity.SAMLConfiguration.DomainVerificationToken == nil {
break
@@ -2219,7 +2195,7 @@ type Membership implements Node {
profile: MembershipProfile @goField(forceResolver: true) @isViewer
organization: Organization @goField(forceResolver: true)
role: MembershipRole!
permissions: [Permission!]!
permissions: [Permission!] @goField(forceResolver: true)
lastSession: Session @goField(forceResolver: true) @isViewer
}
@@ -2265,11 +2241,6 @@ type Permission implements Node {
principalId: ID!
}
type PermissionGrant {
application: Application!
accessLevel: AccessLevel!
}
type Application {
id: ApplicationId!
name: String!
@@ -2292,7 +2263,6 @@ type SAMLConfiguration implements Node {
spMetadataUrl: String!
testLoginUrl: String!
attributeMappings: SAMLAttributeMappings!
defaultPermissions: [PermissionGrant!]!
}
type SAMLAttributeMappings {
@@ -5132,12 +5102,12 @@ func (ec *executionContext) _Membership_permissions(ctx context.Context, field g
field,
ec.fieldContext_Membership_permissions,
func(ctx context.Context) (any, error) {
return obj.Permissions, nil
return ec.resolvers.Membership().Permissions(ctx, obj)
},
nil,
ec.marshalNPermission2ᚕᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐPermissionᚄ,
true,
ec.marshalOPermission2ᚕᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐPermissionᚄ,
true,
false,
)
}
@@ -5145,8 +5115,8 @@ func (ec *executionContext) fieldContext_Membership_permissions(_ context.Contex
fc = &graphql.FieldContext{
Object: "Membership",
Field: field,
IsMethod: false,
IsResolver: false,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "id":
@@ -8107,74 +8077,6 @@ func (ec *executionContext) fieldContext_Permission_principalId(_ context.Contex
return fc, nil
}
func (ec *executionContext) _PermissionGrant_application(ctx context.Context, field graphql.CollectedField, obj *types.PermissionGrant) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_PermissionGrant_application,
func(ctx context.Context) (any, error) {
return obj.Application, nil
},
nil,
ec.marshalNApplication2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐApplication,
true,
true,
)
}
func (ec *executionContext) fieldContext_PermissionGrant_application(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "PermissionGrant",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "id":
return ec.fieldContext_Application_id(ctx, field)
case "name":
return ec.fieldContext_Application_name(ctx, field)
case "description":
return ec.fieldContext_Application_description(ctx, field)
case "availableAccessLevels":
return ec.fieldContext_Application_availableAccessLevels(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type Application", field.Name)
},
}
return fc, nil
}
func (ec *executionContext) _PermissionGrant_accessLevel(ctx context.Context, field graphql.CollectedField, obj *types.PermissionGrant) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_PermissionGrant_accessLevel,
func(ctx context.Context) (any, error) {
return obj.AccessLevel, nil
},
nil,
ec.marshalNAccessLevel2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐAccessLevel,
true,
true,
)
}
func (ec *executionContext) fieldContext_PermissionGrant_accessLevel(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "PermissionGrant",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type AccessLevel does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _PersonalAPIKey_id(ctx context.Context, field graphql.CollectedField, obj *types.PersonalAPIKey) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
@@ -9623,41 +9525,6 @@ func (ec *executionContext) fieldContext_SAMLConfiguration_attributeMappings(_ c
return fc, nil
}
func (ec *executionContext) _SAMLConfiguration_defaultPermissions(ctx context.Context, field graphql.CollectedField, obj *types.SAMLConfiguration) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_SAMLConfiguration_defaultPermissions,
func(ctx context.Context) (any, error) {
return obj.DefaultPermissions, nil
},
nil,
ec.marshalNPermissionGrant2ᚕᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐPermissionGrantᚄ,
true,
true,
)
}
func (ec *executionContext) fieldContext_SAMLConfiguration_defaultPermissions(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "SAMLConfiguration",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "application":
return ec.fieldContext_PermissionGrant_application(ctx, field)
case "accessLevel":
return ec.fieldContext_PermissionGrant_accessLevel(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type PermissionGrant", field.Name)
},
}
return fc, nil
}
func (ec *executionContext) _SAMLConfigurationConnection_edges(ctx context.Context, field graphql.CollectedField, obj *types.SAMLConfigurationConnection) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
@@ -9813,8 +9680,6 @@ func (ec *executionContext) fieldContext_SAMLConfigurationEdge_node(_ context.Co
return ec.fieldContext_SAMLConfiguration_testLoginUrl(ctx, field)
case "attributeMappings":
return ec.fieldContext_SAMLConfiguration_attributeMappings(ctx, field)
case "defaultPermissions":
return ec.fieldContext_SAMLConfiguration_defaultPermissions(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type SAMLConfiguration", field.Name)
},
@@ -10736,8 +10601,6 @@ func (ec *executionContext) fieldContext_UpdateSAMLConfigurationPayload_samlConf
return ec.fieldContext_SAMLConfiguration_testLoginUrl(ctx, field)
case "attributeMappings":
return ec.fieldContext_SAMLConfiguration_attributeMappings(ctx, field)
case "defaultPermissions":
return ec.fieldContext_SAMLConfiguration_defaultPermissions(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type SAMLConfiguration", field.Name)
},
@@ -14543,10 +14406,38 @@ func (ec *executionContext) _Membership(ctx context.Context, sel ast.SelectionSe
atomic.AddUint32(&out.Invalids, 1)
}
case "permissions":
out.Values[i] = ec._Membership_permissions(ctx, field, obj)
if out.Values[i] == graphql.Null {
atomic.AddUint32(&out.Invalids, 1)
field := field
innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Membership_permissions(ctx, field, obj)
return res
}
if field.Deferrable != nil {
dfs, ok := deferred[field.Deferrable.Label]
di := 0
if ok {
dfs.AddField(field)
di = len(dfs.Values) - 1
} else {
dfs = graphql.NewFieldSet([]graphql.CollectedField{field})
deferred[field.Deferrable.Label] = dfs
}
dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler {
return innerFunc(ctx, dfs)
})
// don't run the out.Concurrently() call below
out.Values[i] = graphql.Null
continue
}
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
case "lastSession":
field := field
@@ -15387,50 +15278,6 @@ func (ec *executionContext) _Permission(ctx context.Context, sel ast.SelectionSe
return out
}
var permissionGrantImplementors = []string{"PermissionGrant"}
func (ec *executionContext) _PermissionGrant(ctx context.Context, sel ast.SelectionSet, obj *types.PermissionGrant) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, permissionGrantImplementors)
out := graphql.NewFieldSet(fields)
deferred := make(map[string]*graphql.FieldSet)
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("PermissionGrant")
case "application":
out.Values[i] = ec._PermissionGrant_application(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
}
case "accessLevel":
out.Values[i] = ec._PermissionGrant_accessLevel(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch(ctx)
if out.Invalids > 0 {
return graphql.Null
}
atomic.AddInt32(&ec.deferred, int32(len(deferred)))
for label, dfs := range deferred {
ec.processDeferredGroup(graphql.DeferredGroup{
Label: label,
Path: graphql.GetPath(ctx),
FieldSet: dfs,
Context: ctx,
})
}
return out
}
var personalAPIKeyImplementors = []string{"PersonalAPIKey", "Node"}
func (ec *executionContext) _PersonalAPIKey(ctx context.Context, sel ast.SelectionSet, obj *types.PersonalAPIKey) graphql.Marshaler {
@@ -16096,11 +15943,6 @@ func (ec *executionContext) _SAMLConfiguration(ctx context.Context, sel ast.Sele
if out.Values[i] == graphql.Null {
out.Invalids++
}
case "defaultPermissions":
out.Values[i] = ec._SAMLConfiguration_defaultPermissions(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
@@ -17767,50 +17609,6 @@ func (ec *executionContext) marshalNPageInfo2goᚗproboᚗincᚋproboᚋpkgᚋse
return ec._PageInfo(ctx, sel, &v)
}
func (ec *executionContext) marshalNPermission2ᚕᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐPermissionᚄ(ctx context.Context, sel ast.SelectionSet, v []*types.Permission) graphql.Marshaler {
ret := make(graphql.Array, len(v))
var wg sync.WaitGroup
isLen1 := len(v) == 1
if !isLen1 {
wg.Add(len(v))
}
for i := range v {
i := i
fc := &graphql.FieldContext{
Index: &i,
Result: &v[i],
}
ctx := graphql.WithFieldContext(ctx, fc)
f := func(i int) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = nil
}
}()
if !isLen1 {
defer wg.Done()
}
ret[i] = ec.marshalNPermission2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐPermission(ctx, sel, v[i])
}
if isLen1 {
f(i)
} else {
go f(i)
}
}
wg.Wait()
for _, e := range ret {
if e == graphql.Null {
return graphql.Null
}
}
return ret
}
func (ec *executionContext) marshalNPermission2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐPermission(ctx context.Context, sel ast.SelectionSet, v *types.Permission) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
@@ -17821,60 +17619,6 @@ func (ec *executionContext) marshalNPermission2ᚖgoᚗproboᚗincᚋproboᚋpkg
return ec._Permission(ctx, sel, v)
}
func (ec *executionContext) marshalNPermissionGrant2ᚕᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐPermissionGrantᚄ(ctx context.Context, sel ast.SelectionSet, v []*types.PermissionGrant) graphql.Marshaler {
ret := make(graphql.Array, len(v))
var wg sync.WaitGroup
isLen1 := len(v) == 1
if !isLen1 {
wg.Add(len(v))
}
for i := range v {
i := i
fc := &graphql.FieldContext{
Index: &i,
Result: &v[i],
}
ctx := graphql.WithFieldContext(ctx, fc)
f := func(i int) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = nil
}
}()
if !isLen1 {
defer wg.Done()
}
ret[i] = ec.marshalNPermissionGrant2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐPermissionGrant(ctx, sel, v[i])
}
if isLen1 {
f(i)
} else {
go f(i)
}
}
wg.Wait()
for _, e := range ret {
if e == graphql.Null {
return graphql.Null
}
}
return ret
}
func (ec *executionContext) marshalNPermissionGrant2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐPermissionGrant(ctx context.Context, sel ast.SelectionSet, v *types.PermissionGrant) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
}
return graphql.Null
}
return ec._PermissionGrant(ctx, sel, v)
}
func (ec *executionContext) marshalNPersonalAPIKey2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐPersonalAPIKey(ctx context.Context, sel ast.SelectionSet, v *types.PersonalAPIKey) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
@@ -18878,6 +18622,53 @@ func (ec *executionContext) marshalOOrganization2ᚖgoᚗproboᚗincᚋproboᚋp
return ec._Organization(ctx, sel, v)
}
func (ec *executionContext) marshalOPermission2ᚕᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐPermissionᚄ(ctx context.Context, sel ast.SelectionSet, v []*types.Permission) graphql.Marshaler {
if v == nil {
return graphql.Null
}
ret := make(graphql.Array, len(v))
var wg sync.WaitGroup
isLen1 := len(v) == 1
if !isLen1 {
wg.Add(len(v))
}
for i := range v {
i := i
fc := &graphql.FieldContext{
Index: &i,
Result: &v[i],
}
ctx := graphql.WithFieldContext(ctx, fc)
f := func(i int) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = nil
}
}()
if !isLen1 {
defer wg.Done()
}
ret[i] = ec.marshalNPermission2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐPermission(ctx, sel, v[i])
}
if isLen1 {
f(i)
} else {
go f(i)
}
}
wg.Wait()
for _, e := range ret {
if e == graphql.Null {
return graphql.Null
}
}
return ret
}
func (ec *executionContext) marshalOPersonalAPIKey2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐPersonalAPIKey(ctx context.Context, sel ast.SelectionSet, v *types.PersonalAPIKey) graphql.Marshaler {
if v == nil {
return graphql.Null

View File

@@ -200,7 +200,7 @@ type Membership struct {
Profile *MembershipProfile `json:"profile,omitempty"`
Organization *Organization `json:"organization,omitempty"`
Role coredata.MembershipRole `json:"role"`
Permissions []*Permission `json:"permissions"`
Permissions []*Permission `json:"permissions,omitempty"`
LastSession *Session `json:"lastSession,omitempty"`
}
@@ -278,11 +278,6 @@ type Permission struct {
func (Permission) IsNode() {}
func (this Permission) GetID() gid.GID { return this.ID }
type PermissionGrant struct {
Application *Application `json:"application"`
AccessLevel AccessLevel `json:"accessLevel"`
}
type PersonalAPIKey struct {
ID gid.GID `json:"id"`
Name string `json:"name"`
@@ -378,7 +373,6 @@ type SAMLConfiguration struct {
SpMetadataURL string `json:"spMetadataUrl"`
TestLoginURL string `json:"testLoginUrl"`
AttributeMappings *SAMLAttributeMappings `json:"attributeMappings"`
DefaultPermissions []*PermissionGrant `json:"defaultPermissions"`
}
func (SAMLConfiguration) IsNode() {}

View File

@@ -233,6 +233,11 @@ func (r *membershipResolver) Organization(ctx context.Context, obj *types.Member
return types.NewOrganization(organization), nil
}
// Permissions is the resolver for the permissions field.
func (r *membershipResolver) Permissions(ctx context.Context, obj *types.Membership) ([]*types.Permission, error) {
panic("not implemented")
}
// LastSession is the resolver for the lastSession field.
func (r *membershipResolver) LastSession(ctx context.Context, obj *types.Membership) (*types.Session, error) {
session := SessionFromContext(ctx)

View File

@@ -316,16 +316,23 @@ func GetTenantService(ctx context.Context, proboSvc *probo.Service, tenantID gid
return proboSvc.WithTenant(tenantID)
}
func (r *Resolver) MustBeAuthorized(ctx context.Context, entityID gid.GID, action iam.Action) {
func (r *Resolver) MustAuthorize(ctx context.Context, entityID gid.GID, action iam.Action) {
user := connect_v1.IdentityFromContext(ctx)
apiKey := connect_v1.APIKeyFromContext(ctx)
// apiKey := connect_v1.APIKeyFromContext(ctx)
var credentialID *gid.GID
if apiKey != nil {
credentialID = &apiKey.ID
}
// var credentialID *gid.GID
// if apiKey != nil {
// credentialID = &apiKey.ID
// }
err := r.iam.LegacyAccessManagementService.Authorize(ctx, user.ID, credentialID, entityID, action)
err := r.iam.Authorizer.Authorize(
ctx,
iam.AuthorizeParams{
Principal: user.ID,
Resource: entityID,
Action: action,
},
)
if err != nil {
panic(err)
}

View File

@@ -252,6 +252,7 @@ type ComplexityRoot struct {
Measures func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeasureOrderBy, filter *types.MeasureFilter) int
Name func(childComplexity int) int
Obligations func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ObligationOrderBy, filter *types.ObligationFilter) int
Organization func(childComplexity int) int
SectionTitle func(childComplexity int) int
Snapshots func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.SnapshotOrderBy) int
StateOfApplicabilityControls func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.StateOfApplicabilityOrderBy) int
@@ -1932,6 +1933,8 @@ type ContinualImprovementConnectionResolver interface {
TotalCount(ctx context.Context, obj *types.ContinualImprovementConnection) (int, error)
}
type ControlResolver interface {
Organization(ctx context.Context, obj *types.Control) (*types.Organization, error)
Framework(ctx context.Context, obj *types.Control) (*types.Framework, error)
Measures(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeasureOrderBy, filter *types.MeasureFilter) (*types.MeasureConnection, error)
Documents(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy, filter *types.DocumentFilter) (*types.DocumentConnection, error)
@@ -2926,6 +2929,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
}
return e.complexity.Control.Obligations(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey), args["orderBy"].(*types.ObligationOrderBy), args["filter"].(*types.ObligationFilter)), true
case "Control.organization":
if e.complexity.Control.Organization == nil {
break
}
return e.complexity.Control.Organization(childComplexity), true
case "Control.sectionTitle":
if e.complexity.Control.SectionTitle == nil {
break
@@ -11762,6 +11771,7 @@ type Framework implements Node {
type Control implements Node {
id: ID!
organization: Organization @goField(forceResolver: true)
sectionTitle: String!
name: String!
description: String
@@ -21218,6 +21228,109 @@ func (ec *executionContext) fieldContext_Control_id(_ context.Context, field gra
return fc, nil
}
func (ec *executionContext) _Control_organization(ctx context.Context, field graphql.CollectedField, obj *types.Control) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_Control_organization,
func(ctx context.Context) (any, error) {
return ec.resolvers.Control().Organization(ctx, obj)
},
nil,
ec.marshalOOrganization2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐOrganization,
true,
false,
)
}
func (ec *executionContext) fieldContext_Control_organization(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Control",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "id":
return ec.fieldContext_Organization_id(ctx, field)
case "name":
return ec.fieldContext_Organization_name(ctx, field)
case "logoUrl":
return ec.fieldContext_Organization_logoUrl(ctx, field)
case "horizontalLogoUrl":
return ec.fieldContext_Organization_horizontalLogoUrl(ctx, field)
case "description":
return ec.fieldContext_Organization_description(ctx, field)
case "websiteUrl":
return ec.fieldContext_Organization_websiteUrl(ctx, field)
case "email":
return ec.fieldContext_Organization_email(ctx, field)
case "headquarterAddress":
return ec.fieldContext_Organization_headquarterAddress(ctx, field)
case "context":
return ec.fieldContext_Organization_context(ctx, field)
case "slackConnections":
return ec.fieldContext_Organization_slackConnections(ctx, field)
case "frameworks":
return ec.fieldContext_Organization_frameworks(ctx, field)
case "controls":
return ec.fieldContext_Organization_controls(ctx, field)
case "vendors":
return ec.fieldContext_Organization_vendors(ctx, field)
case "peoples":
return ec.fieldContext_Organization_peoples(ctx, field)
case "documents":
return ec.fieldContext_Organization_documents(ctx, field)
case "meetings":
return ec.fieldContext_Organization_meetings(ctx, field)
case "statesOfApplicability":
return ec.fieldContext_Organization_statesOfApplicability(ctx, field)
case "measures":
return ec.fieldContext_Organization_measures(ctx, field)
case "risks":
return ec.fieldContext_Organization_risks(ctx, field)
case "tasks":
return ec.fieldContext_Organization_tasks(ctx, field)
case "assets":
return ec.fieldContext_Organization_assets(ctx, field)
case "data":
return ec.fieldContext_Organization_data(ctx, field)
case "audits":
return ec.fieldContext_Organization_audits(ctx, field)
case "nonconformities":
return ec.fieldContext_Organization_nonconformities(ctx, field)
case "obligations":
return ec.fieldContext_Organization_obligations(ctx, field)
case "continualImprovements":
return ec.fieldContext_Organization_continualImprovements(ctx, field)
case "rightsRequests":
return ec.fieldContext_Organization_rightsRequests(ctx, field)
case "processingActivities":
return ec.fieldContext_Organization_processingActivities(ctx, field)
case "dataProtectionImpactAssessments":
return ec.fieldContext_Organization_dataProtectionImpactAssessments(ctx, field)
case "transferImpactAssessments":
return ec.fieldContext_Organization_transferImpactAssessments(ctx, field)
case "snapshots":
return ec.fieldContext_Organization_snapshots(ctx, field)
case "trustCenterFiles":
return ec.fieldContext_Organization_trustCenterFiles(ctx, field)
case "trustCenter":
return ec.fieldContext_Organization_trustCenter(ctx, field)
case "customDomain":
return ec.fieldContext_Organization_customDomain(ctx, field)
case "createdAt":
return ec.fieldContext_Organization_createdAt(ctx, field)
case "updatedAt":
return ec.fieldContext_Organization_updatedAt(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type Organization", field.Name)
},
}
return fc, nil
}
func (ec *executionContext) _Control_sectionTitle(ctx context.Context, field graphql.CollectedField, obj *types.Control) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
@@ -21951,6 +22064,8 @@ func (ec *executionContext) fieldContext_ControlEdge_node(_ context.Context, fie
switch field.Name {
case "id":
return ec.fieldContext_Control_id(ctx, field)
case "organization":
return ec.fieldContext_Control_organization(ctx, field)
case "sectionTitle":
return ec.fieldContext_Control_sectionTitle(ctx, field)
case "name":
@@ -51012,6 +51127,8 @@ func (ec *executionContext) fieldContext_UpdateControlPayload_control(_ context.
switch field.Name {
case "id":
return ec.fieldContext_Control_id(ctx, field)
case "organization":
return ec.fieldContext_Control_organization(ctx, field)
case "sectionTitle":
return ec.fieldContext_Control_sectionTitle(ctx, field)
case "name":
@@ -67994,6 +68111,39 @@ func (ec *executionContext) _Control(ctx context.Context, sel ast.SelectionSet,
if out.Values[i] == graphql.Null {
atomic.AddUint32(&out.Invalids, 1)
}
case "organization":
field := field
innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Control_organization(ctx, field, obj)
return res
}
if field.Deferrable != nil {
dfs, ok := deferred[field.Deferrable.Label]
di := 0
if ok {
dfs.AddField(field)
di = len(dfs.Values) - 1
} else {
dfs = graphql.NewFieldSet([]graphql.CollectedField{field})
deferred[field.Deferrable.Label] = dfs
}
dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler {
return innerFunc(ctx, dfs)
})
// don't run the out.Concurrently() call below
out.Values[i] = graphql.Null
continue
}
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
case "sectionTitle":
out.Values[i] = ec._Control_sectionTitle(ctx, field, obj)
if out.Values[i] == graphql.Null {

View File

@@ -51,6 +51,9 @@ func NewControlEdge(control *coredata.Control, orderField coredata.ControlOrderF
func NewControl(control *coredata.Control) *Control {
return &Control{
ID: control.ID,
Organization: &Organization{
ID: control.OrganizationID,
},
Framework: &Framework{
ID: control.FrameworkID,
},

View File

@@ -168,6 +168,7 @@ type ContinualImprovementFilter struct {
type Control struct {
ID gid.GID `json:"id"`
Organization *Organization `json:"organization,omitempty"`
SectionTitle string `json:"sectionTitle"`
Name string `json:"name"`
Description *string `json:"description,omitempty"`

File diff suppressed because it is too large Load Diff

View File

@@ -30,7 +30,34 @@ func (r *Resolver) MustBeAuthorized(ctx context.Context, entityID gid.GID, actio
credentialID = &apiKey.ID
}
err := r.iamSvc.LegacyAccessManagementService.Authorize(ctx, user.ID, credentialID, entityID, action)
// When API key is used, fall back to legacy system for intersection semantics.
// The legacy system handles API key role checking properly.
// TODO: Migrate API key authorization to new system.
if credentialID != nil {
err := r.iamSvc.LegacyAccessManagementService.Authorize(ctx, user.ID, credentialID, entityID, action)
if err != nil {
panic(err)
}
return
}
// Map legacy action to new namespaced action
newAction, ok := probo.MapLegacyAction(entityID.EntityType(), action)
if !ok {
// Fall back to legacy system for unmapped actions
err := r.iamSvc.LegacyAccessManagementService.Authorize(ctx, user.ID, credentialID, entityID, action)
if err != nil {
panic(err)
}
return
}
// Use new authorizer with mapped action
err := r.iamSvc.Authorizer.Authorize(ctx, iam.AuthorizeParams{
Principal: user.ID,
Resource: entityID,
Action: newAction,
})
if err != nil {
panic(err)
}