Refactor policies document
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
@@ -16,7 +16,7 @@ package iam
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
|
||||
"go.gearno.de/kit/pg"
|
||||
@@ -25,12 +25,28 @@ import (
|
||||
"go.probo.inc/probo/pkg/iam/policy"
|
||||
)
|
||||
|
||||
// AuthorizationAttributer is implemented by entities that provide attributes
|
||||
// for policy condition evaluation.
|
||||
type AuthorizationAttributer interface {
|
||||
AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error)
|
||||
}
|
||||
|
||||
// AuthorizeParams contains the parameters for an authorization request.
|
||||
type AuthorizeParams struct {
|
||||
Principal gid.GID
|
||||
Resource gid.GID
|
||||
Action string
|
||||
ResourceAttributes map[string]string
|
||||
}
|
||||
|
||||
// Authorizer evaluates authorization requests against registered policies.
|
||||
type Authorizer struct {
|
||||
pg *pg.Client
|
||||
evaluator *policy.Evaluator
|
||||
policySet *PolicySet
|
||||
}
|
||||
|
||||
// NewAuthorizer creates a new Authorizer instance.
|
||||
func NewAuthorizer(pgClient *pg.Client) *Authorizer {
|
||||
return &Authorizer{
|
||||
pg: pgClient,
|
||||
@@ -39,87 +55,144 @@ func NewAuthorizer(pgClient *pg.Client) *Authorizer {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Authorizer) RegisterPolicySet(policySet *PolicySet) {
|
||||
a.policySet.Merge(policySet)
|
||||
}
|
||||
|
||||
type AuthorizeParams struct {
|
||||
Principal gid.GID
|
||||
Resource gid.GID
|
||||
Action string
|
||||
ResourceAttributes map[string]string
|
||||
// RegisterPolicySet merges the given policy set into the authorizer.
|
||||
func (a *Authorizer) RegisterPolicySet(ps *PolicySet) {
|
||||
a.policySet.Merge(ps)
|
||||
}
|
||||
|
||||
// Authorize checks if the principal is allowed to perform the action on the resource.
|
||||
func (a *Authorizer) Authorize(ctx context.Context, params AuthorizeParams) error {
|
||||
if params.Principal.EntityType() != coredata.IdentityEntityType {
|
||||
return NewUnsupportedPrincipalTypeError(params.Principal.EntityType())
|
||||
}
|
||||
|
||||
policies := a.buildPolicies(ctx, params)
|
||||
return a.pg.WithConn(ctx, func(conn pg.Conn) error { return a.authorize(ctx, conn, params) })
|
||||
}
|
||||
|
||||
// Pre-allocate Resource map with capacity for id + attributes
|
||||
resourceAttrs := make(map[string]string, 1+len(params.ResourceAttributes))
|
||||
resourceAttrs["id"] = params.Resource.String()
|
||||
maps.Copy(resourceAttrs, params.ResourceAttributes)
|
||||
|
||||
conditionCtx := policy.ConditionContext{
|
||||
Principal: map[string]string{
|
||||
"id": params.Principal.String(),
|
||||
},
|
||||
Resource: resourceAttrs,
|
||||
func (a *Authorizer) authorize(ctx context.Context, conn pg.Conn, params AuthorizeParams) error {
|
||||
memberships, err := a.loadMemberships(ctx, conn, params.Principal)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resourceAttrs, err := a.buildResourceAttributes(ctx, conn, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Find role for resource's organization
|
||||
resourceOrgID := resourceAttrs["organization_id"]
|
||||
role := findRoleForOrg(memberships, resourceOrgID)
|
||||
|
||||
// Only set principal.organization_id if they have a role in this org
|
||||
var principalOrgID string
|
||||
if role != "" {
|
||||
principalOrgID = resourceOrgID
|
||||
}
|
||||
|
||||
principalAttrs, err := a.buildPrincipalAttributes(ctx, conn, params.Principal, principalOrgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
policies := a.buildPoliciesForRole(role)
|
||||
|
||||
req := policy.AuthorizationRequest{
|
||||
Principal: params.Principal,
|
||||
Resource: params.Resource,
|
||||
Action: params.Action,
|
||||
ConditionContext: conditionCtx,
|
||||
Principal: params.Principal,
|
||||
Resource: params.Resource,
|
||||
Action: params.Action,
|
||||
ConditionContext: policy.ConditionContext{
|
||||
Principal: principalAttrs,
|
||||
Resource: resourceAttrs,
|
||||
},
|
||||
}
|
||||
|
||||
result := a.evaluator.Evaluate(req, policies)
|
||||
|
||||
if result.IsAllowed() {
|
||||
if a.evaluator.Evaluate(req, policies).IsAllowed() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return NewInsufficientPermissionsError(params.Principal, params.Resource, params.Action)
|
||||
}
|
||||
|
||||
func (a *Authorizer) buildPolicies(ctx context.Context, params AuthorizeParams) []*policy.Policy {
|
||||
selfManageCount := len(a.policySet.SelfManagePolicies)
|
||||
func (a *Authorizer) loadMemberships(ctx context.Context, conn pg.Conn, principalID gid.GID) (coredata.Memberships, error) {
|
||||
var memberships coredata.Memberships
|
||||
if err := memberships.LoadAllByIdentityID(ctx, conn, principalID); err != nil {
|
||||
return nil, fmt.Errorf("cannot load memberships: %w", err)
|
||||
}
|
||||
return memberships, nil
|
||||
}
|
||||
|
||||
var rolePolicies []*policy.Policy
|
||||
if params.Resource.TenantID() != gid.NilTenant {
|
||||
rolePolicies = a.loadRolePolicies(ctx, params.Principal, params.Resource)
|
||||
func (a *Authorizer) buildPrincipalAttributes(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
principalID gid.GID,
|
||||
organizationID string,
|
||||
) (map[string]string, error) {
|
||||
attrs := map[string]string{
|
||||
"id": principalID.String(),
|
||||
"organization_id": organizationID,
|
||||
}
|
||||
|
||||
totalCount := selfManageCount + len(rolePolicies)
|
||||
policies := make([]*policy.Policy, selfManageCount, totalCount)
|
||||
copy(policies, a.policySet.SelfManagePolicies)
|
||||
policies = append(policies, rolePolicies...)
|
||||
if entity, ok := coredata.NewEntityFromID(principalID); ok {
|
||||
if attributer, ok := entity.(AuthorizationAttributer); ok {
|
||||
entityAttrs, err := attributer.AuthorizationAttributes(ctx, conn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load principal attributes: %w", err)
|
||||
}
|
||||
maps.Copy(attrs, entityAttrs)
|
||||
}
|
||||
}
|
||||
|
||||
return attrs, nil
|
||||
}
|
||||
|
||||
func (a *Authorizer) buildResourceAttributes(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
params AuthorizeParams,
|
||||
) (map[string]string, error) {
|
||||
attrs := map[string]string{
|
||||
"id": params.Resource.String(),
|
||||
}
|
||||
|
||||
entity, ok := coredata.NewEntityFromID(params.Resource)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unsupported resource type: %d", params.Resource.EntityType())
|
||||
}
|
||||
|
||||
attributer, ok := entity.(AuthorizationAttributer)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("resource %d does not implement AuthorizationAttributer", params.Resource.EntityType())
|
||||
}
|
||||
|
||||
entityAttrs, err := attributer.AuthorizationAttributes(ctx, conn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load resource attributes: %w", err)
|
||||
}
|
||||
maps.Copy(attrs, entityAttrs)
|
||||
|
||||
if params.ResourceAttributes != nil {
|
||||
maps.Copy(attrs, params.ResourceAttributes)
|
||||
}
|
||||
|
||||
return attrs, nil
|
||||
}
|
||||
|
||||
func (a *Authorizer) buildPoliciesForRole(role string) []*policy.Policy {
|
||||
policies := append([]*policy.Policy{}, a.policySet.IdentityScopedPolicies...)
|
||||
|
||||
if role != "" {
|
||||
policies = append(policies, a.policySet.RolePolicies[role]...)
|
||||
}
|
||||
|
||||
return policies
|
||||
}
|
||||
|
||||
func (a *Authorizer) loadRolePolicies(ctx context.Context, principalID gid.GID, resourceID gid.GID) []*policy.Policy {
|
||||
var role coredata.MembershipRole
|
||||
|
||||
err := a.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) (err error) {
|
||||
scope := coredata.NewScopeFromObjectID(resourceID)
|
||||
role, err = coredata.LoadRoleByIdentityAndEntityIDOnly(ctx, conn, scope, principalID, resourceID)
|
||||
if errors.Is(err, coredata.ErrResourceNotFound) {
|
||||
return nil // No membership = no role-based policies
|
||||
}
|
||||
|
||||
return err
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil || role == "" {
|
||||
return nil
|
||||
func findRoleForOrg(memberships coredata.Memberships, orgID string) string {
|
||||
for _, m := range memberships {
|
||||
if m.OrganizationID.String() == orgID {
|
||||
return string(m.Role)
|
||||
}
|
||||
}
|
||||
|
||||
return a.policySet.RolePolicies[role.String()]
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ var IAMSelfManageSessionPolicy = policy.NewPolicy(
|
||||
ActionIAMSessionRevoke,
|
||||
ActionIAMSessionRevokeAll,
|
||||
).WithSID("manage-own-sessions").
|
||||
When(policy.Equals("principal.id", "resource.user_id")),
|
||||
When(policy.Equals("principal.id", "resource.identity_id")),
|
||||
).WithDescription("Allows users to view and revoke their own sessions")
|
||||
|
||||
// IAMSelfManageInvitationPolicy allows users to manage invitations sent to them.
|
||||
@@ -66,7 +66,7 @@ var IAMSelfManageInvitationPolicy = policy.NewPolicy(
|
||||
ActionIAMInvitationGet,
|
||||
ActionIAMInvitationAccept,
|
||||
).WithSID("manage-own-invitations").
|
||||
When(policy.Equals("principal.id", "resource.user_id")),
|
||||
When(policy.Equals("principal.email", "resource.email")),
|
||||
).WithDescription("Allows users to view and accept invitations sent to them")
|
||||
|
||||
// IAMSelfManageMembershipPolicy allows users to view their own memberships.
|
||||
@@ -77,7 +77,7 @@ var IAMSelfManageMembershipPolicy = policy.NewPolicy(
|
||||
policy.Allow(
|
||||
ActionIAMMembershipGet,
|
||||
).WithSID("view-own-memberships").
|
||||
When(policy.Equals("principal.id", "resource.user_id")),
|
||||
When(policy.Equals("principal.id", "resource.identity_id")),
|
||||
).WithDescription("Allows users to view their organization memberships")
|
||||
|
||||
// IAMSelfManagePersonalAPIKeyPolicy allows users to manage their own API keys.
|
||||
@@ -91,7 +91,7 @@ var IAMSelfManagePersonalAPIKeyPolicy = policy.NewPolicy(
|
||||
ActionIAMPersonalAPIKeyUpdate,
|
||||
ActionIAMPersonalAPIKeyDelete,
|
||||
).WithSID("manage-own-api-keys").
|
||||
When(policy.Equals("principal.id", "resource.user_id")),
|
||||
When(policy.Equals("principal.id", "resource.identity_id")),
|
||||
).WithDescription("Allows users to manage their own personal API keys")
|
||||
|
||||
// IAMOwnerPolicy defines permissions for organization owners.
|
||||
@@ -99,17 +99,20 @@ var IAMOwnerPolicy = policy.NewPolicy(
|
||||
"iam:owner",
|
||||
"Organization Owner",
|
||||
// Full access to organization management
|
||||
policy.Allow("iam:organization:*").WithSID("full-org-access"),
|
||||
// Full access to member management
|
||||
policy.Allow("iam:membership:*").WithSID("full-membership-access"),
|
||||
// Can manage invitations
|
||||
policy.Allow("iam:organization:*").WithSID("full-org-access").When(policy.Equals("principal.organization_id", "resource.id")),
|
||||
// Full access to member management (scoped to own organization)
|
||||
policy.Allow("iam:membership:*").WithSID("full-membership-access").
|
||||
When(policy.Equals("principal.organization_id", "resource.organization_id")),
|
||||
// Can manage invitations (scoped to own organization)
|
||||
policy.Allow(
|
||||
ActionIAMInvitationCreate,
|
||||
ActionIAMInvitationGet,
|
||||
ActionIAMInvitationDelete,
|
||||
).WithSID("manage-invitations"),
|
||||
// Full access to SAML configuration management
|
||||
policy.Allow("iam:saml-configuration:*").WithSID("full-saml-access"),
|
||||
).WithSID("manage-invitations").
|
||||
When(policy.Equals("principal.organization_id", "resource.organization_id")),
|
||||
// Full access to SAML configuration management (scoped to own organization)
|
||||
policy.Allow("iam:saml-configuration:*").WithSID("full-saml-access").
|
||||
When(policy.Equals("principal.organization_id", "resource.organization_id")),
|
||||
).WithDescription("Full IAM access for organization owners")
|
||||
|
||||
// IAMAdminPolicy defines permissions for organization admins.
|
||||
@@ -123,19 +126,22 @@ var IAMAdminPolicy = policy.NewPolicy(
|
||||
ActionIAMOrganizationListMembers,
|
||||
ActionIAMOrganizationListInvitations,
|
||||
ActionIAMOrganizationInviteMember,
|
||||
).WithSID("org-admin-access"),
|
||||
// Can manage memberships (but not remove owner)
|
||||
).WithSID("org-admin-access").When(policy.Equals("principal.organization_id", "resource.organization_id")),
|
||||
// Can manage memberships (scoped to own organization)
|
||||
policy.Allow(
|
||||
ActionIAMMembershipGet,
|
||||
ActionIAMMembershipUpdate,
|
||||
).WithSID("membership-admin-access"),
|
||||
// Can manage invitations
|
||||
).WithSID("membership-admin-access").
|
||||
When(policy.Equals("principal.organization_id", "resource.organization_id")),
|
||||
// Can manage invitations (scoped to own organization)
|
||||
policy.Allow(
|
||||
ActionIAMInvitationGet,
|
||||
ActionIAMInvitationDelete,
|
||||
).WithSID("invitation-admin-access"),
|
||||
// Can view and update SAML configurations
|
||||
policy.Allow(ActionIAMSAMLConfigurationGet).WithSID("saml-configuration-admin-access"),
|
||||
).WithSID("invitation-admin-access").
|
||||
When(policy.Equals("principal.organization_id", "resource.organization_id")),
|
||||
// Can view SAML configurations (scoped to own organization)
|
||||
policy.Allow(ActionIAMSAMLConfigurationGet).WithSID("saml-configuration-admin-access").
|
||||
When(policy.Equals("principal.organization_id", "resource.organization_id")),
|
||||
// Cannot delete organization
|
||||
policy.Deny(ActionIAMOrganizationDelete).WithSID("deny-org-delete"),
|
||||
// Cannot remove members (only owner can)
|
||||
@@ -156,7 +162,8 @@ var IAMViewerPolicy = policy.NewPolicy(
|
||||
policy.Allow(
|
||||
ActionIAMOrganizationGet,
|
||||
ActionIAMOrganizationListMembers,
|
||||
).WithSID("org-viewer-access"),
|
||||
// Can view memberships
|
||||
policy.Allow(ActionIAMMembershipGet).WithSID("membership-viewer-access"),
|
||||
).WithSID("org-viewer-access").When(policy.Equals("principal.organization_id", "resource.id")),
|
||||
// Can view memberships (scoped to own organization)
|
||||
policy.Allow(ActionIAMMembershipGet).WithSID("membership-viewer-access").
|
||||
When(policy.Equals("principal.organization_id", "resource.organization_id")),
|
||||
).WithDescription("Read-only IAM access for organization viewers")
|
||||
|
||||
@@ -131,4 +131,3 @@ func ForEntityType(entityType uint16) ResourcePattern {
|
||||
EntityType: &entityType,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
package policy
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
)
|
||||
|
||||
@@ -139,7 +141,22 @@ func (c Condition) Evaluate(ctx ConditionContext) bool {
|
||||
case ConditionIn:
|
||||
for _, v := range c.Values {
|
||||
resolved, ok := resolveValue(v, ctx)
|
||||
if ok && value == resolved {
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Support a comma-separated "set" value, e.g.
|
||||
// principal.organization_ids = "org_1,org_2"
|
||||
if strings.Contains(resolved, ",") {
|
||||
for _, item := range strings.Split(resolved, ",") {
|
||||
if value == strings.TrimSpace(item) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if value == resolved {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -148,7 +165,20 @@ func (c Condition) Evaluate(ctx ConditionContext) bool {
|
||||
case ConditionNotIn:
|
||||
for _, v := range c.Values {
|
||||
resolved, ok := resolveValue(v, ctx)
|
||||
if ok && value == resolved {
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Contains(resolved, ",") {
|
||||
for _, item := range strings.Split(resolved, ",") {
|
||||
if value == strings.TrimSpace(item) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if value == resolved {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,15 +88,28 @@ func TestCondition_Evaluate_Equals(t *testing.T) {
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "equals - match resource.user_id reference",
|
||||
name: "equals - match resource.identity_id reference",
|
||||
condition: Condition{
|
||||
Operator: ConditionEquals,
|
||||
Key: "principal.id",
|
||||
Values: []string{"resource.user_id"},
|
||||
Values: []string{"resource.identity_id"},
|
||||
},
|
||||
ctx: ConditionContext{
|
||||
Principal: map[string]string{"id": "user_123"},
|
||||
Resource: map[string]string{"user_id": "user_123"},
|
||||
Resource: map[string]string{"identity_id": "user_123"},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "equals - match principal.email to resource.email reference",
|
||||
condition: Condition{
|
||||
Operator: ConditionEquals,
|
||||
Key: "principal.email",
|
||||
Values: []string{"resource.email"},
|
||||
},
|
||||
ctx: ConditionContext{
|
||||
Principal: map[string]string{"email": "user@example.com"},
|
||||
Resource: map[string]string{"email": "user@example.com"},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
|
||||
@@ -16,21 +16,21 @@ package iam
|
||||
|
||||
import "go.probo.inc/probo/pkg/iam/policy"
|
||||
|
||||
// PolicySet holds role-based and self-management policies.
|
||||
// PolicySet holds organization-scoped (role) policies and identity-scoped policies.
|
||||
// Services create their own PolicySet and combine them when creating the Authorizer.
|
||||
type PolicySet struct {
|
||||
// RolePolicies maps role names to policies.
|
||||
RolePolicies map[string][]*policy.Policy
|
||||
|
||||
// SelfManagePolicies are applied to all authenticated users.
|
||||
SelfManagePolicies []*policy.Policy
|
||||
// IdentityScopedPolicies are applied to all authenticated users, independent of organization membership.
|
||||
IdentityScopedPolicies []*policy.Policy
|
||||
}
|
||||
|
||||
// NewPolicySet creates an empty PolicySet.
|
||||
func NewPolicySet() *PolicySet {
|
||||
return &PolicySet{
|
||||
RolePolicies: make(map[string][]*policy.Policy),
|
||||
SelfManagePolicies: make([]*policy.Policy, 0),
|
||||
RolePolicies: make(map[string][]*policy.Policy),
|
||||
IdentityScopedPolicies: make([]*policy.Policy, 0),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,9 +40,9 @@ func (ps *PolicySet) AddRolePolicy(role string, policies ...*policy.Policy) *Pol
|
||||
return ps
|
||||
}
|
||||
|
||||
// AddSelfManagePolicy adds policies applied to all authenticated users.
|
||||
func (ps *PolicySet) AddSelfManagePolicy(policies ...*policy.Policy) *PolicySet {
|
||||
ps.SelfManagePolicies = append(ps.SelfManagePolicies, policies...)
|
||||
// AddIdentityScopedPolicy adds policies applied to all authenticated users (identity-scoped).
|
||||
func (ps *PolicySet) AddIdentityScopedPolicy(policies ...*policy.Policy) *PolicySet {
|
||||
ps.IdentityScopedPolicies = append(ps.IdentityScopedPolicies, policies...)
|
||||
return ps
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ func (ps *PolicySet) Merge(other *PolicySet) *PolicySet {
|
||||
for role, policies := range other.RolePolicies {
|
||||
ps.RolePolicies[role] = append(ps.RolePolicies[role], policies...)
|
||||
}
|
||||
ps.SelfManagePolicies = append(ps.SelfManagePolicies, other.SelfManagePolicies...)
|
||||
ps.IdentityScopedPolicies = append(ps.IdentityScopedPolicies, other.IdentityScopedPolicies...)
|
||||
return ps
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ func IAMPolicySet() *PolicySet {
|
||||
AddRolePolicy("VIEWER", IAMViewerPolicy).
|
||||
AddRolePolicy("EMPLOYEE", IAMViewerPolicy).
|
||||
AddRolePolicy("AUDITOR", IAMViewerPolicy).
|
||||
AddSelfManagePolicy(
|
||||
AddIdentityScopedPolicy(
|
||||
IAMSelfManageIdentityPolicy,
|
||||
IAMSelfManageSessionPolicy,
|
||||
IAMSelfManageInvitationPolicy,
|
||||
|
||||
@@ -28,13 +28,13 @@ func TestPolicySet_AddAndMerge(t *testing.T) {
|
||||
iamPolicies := NewPolicySet().
|
||||
AddRolePolicy("OWNER", policy.NewPolicy("iam-owner", "IAM Owner", policy.Allow("iam:*"))).
|
||||
AddRolePolicy("ADMIN", policy.NewPolicy("iam-admin", "IAM Admin", policy.Allow("iam:read:*"))).
|
||||
AddSelfManagePolicy(policy.NewPolicy("iam-self", "IAM Self", policy.Allow("iam:identity:get")))
|
||||
AddIdentityScopedPolicy(policy.NewPolicy("iam-self", "IAM Self", policy.Allow("iam:identity:get")))
|
||||
|
||||
// Create second policy set (simulating Documents service)
|
||||
docsPolicies := NewPolicySet().
|
||||
AddRolePolicy("OWNER", policy.NewPolicy("docs-owner", "Docs Owner", policy.Allow("docs:*"))).
|
||||
AddRolePolicy("VIEWER", policy.NewPolicy("docs-viewer", "Docs Viewer", policy.Allow("docs:read:*"))).
|
||||
AddSelfManagePolicy(policy.NewPolicy("docs-self", "Docs Self", policy.Allow("docs:own:*")))
|
||||
AddIdentityScopedPolicy(policy.NewPolicy("docs-self", "Docs Self", policy.Allow("docs:own:*")))
|
||||
|
||||
// Merge them
|
||||
combined := iamPolicies.Merge(docsPolicies)
|
||||
@@ -53,8 +53,8 @@ func TestPolicySet_AddAndMerge(t *testing.T) {
|
||||
require.Len(t, viewerPolicies, 1, "should have 1 VIEWER policy")
|
||||
|
||||
// Test self-manage policies from both services
|
||||
selfPolicies := combined.SelfManagePolicies
|
||||
require.Len(t, selfPolicies, 2, "should have 2 self-manage policies")
|
||||
identityPolicies := combined.IdentityScopedPolicies
|
||||
require.Len(t, identityPolicies, 2, "should have 2 identity-scoped policies")
|
||||
}
|
||||
|
||||
func TestIAMPolicySet(t *testing.T) {
|
||||
@@ -69,5 +69,5 @@ func TestIAMPolicySet(t *testing.T) {
|
||||
}
|
||||
|
||||
// Should have self-manage policies
|
||||
assert.NotEmpty(t, policySet.SelfManagePolicies, "expected self-manage policies")
|
||||
assert.NotEmpty(t, policySet.IdentityScopedPolicies, "expected identity-scoped policies")
|
||||
}
|
||||
|
||||
@@ -247,3 +247,28 @@ func (s *Service) GetSAMLconfiguration(ctx context.Context, samlConfigurationID
|
||||
|
||||
return samlConfiguration, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetPersonalAPIKey(ctx context.Context, personalAPIKeyID gid.GID) (*coredata.PersonalAPIKey, error) {
|
||||
personalAPIKey := &coredata.PersonalAPIKey{}
|
||||
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
err := personalAPIKey.LoadByID(ctx, conn, personalAPIKeyID)
|
||||
if err != nil {
|
||||
if err == coredata.ErrResourceNotFound {
|
||||
return NewPersonalAPIKeyNotFoundError(personalAPIKeyID)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot load personal API key: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return personalAPIKey, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user