Handle membership deletions and role updates in iam policies
Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -245,8 +245,10 @@ LEFT JOIN
|
||||
func (m *Membership) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) {
|
||||
q := `
|
||||
SELECT
|
||||
identity_id
|
||||
, organization_id
|
||||
identity_id,
|
||||
organization_id,
|
||||
role,
|
||||
source
|
||||
FROM
|
||||
iam_memberships
|
||||
WHERE
|
||||
@@ -256,7 +258,14 @@ LIMIT 1;
|
||||
|
||||
var identityID gid.GID
|
||||
var organizationID gid.GID
|
||||
if err := conn.QueryRow(ctx, q, m.ID).Scan(&identityID, &organizationID); err != nil {
|
||||
var role MembershipRole
|
||||
var source MembershipSource
|
||||
if err := conn.QueryRow(ctx, q, m.ID).Scan(
|
||||
&identityID,
|
||||
&organizationID,
|
||||
&role,
|
||||
&source,
|
||||
); err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, ErrResourceNotFound
|
||||
}
|
||||
@@ -266,6 +275,8 @@ LIMIT 1;
|
||||
return map[string]string{
|
||||
"identity_id": identityID.String(),
|
||||
"organization_id": organizationID.String(),
|
||||
"role": role.String(),
|
||||
"source": source.String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -449,7 +460,7 @@ JOIN
|
||||
LEFT JOIN
|
||||
iam_membership_profiles mp ON mp.membership_id = mbr.id
|
||||
WHERE
|
||||
%s
|
||||
%s
|
||||
`
|
||||
|
||||
query = fmt.Sprintf(query, scope.SQLFragment(), cursor.SQLFragment())
|
||||
|
||||
@@ -112,12 +112,15 @@ func (a *Authorizer) authorize(ctx context.Context, conn pg.Conn, params Authori
|
||||
}
|
||||
|
||||
// Only set principal.organization_id if they have a role in this org
|
||||
var principalOrgID string
|
||||
var scopedPrincipalAttrs map[string]string
|
||||
if membership != nil && role != "" {
|
||||
principalOrgID = membership.OrganizationID.String()
|
||||
scopedPrincipalAttrs = map[string]string{
|
||||
"organization_id": membership.OrganizationID.String(),
|
||||
"role": membership.Role.String(),
|
||||
}
|
||||
}
|
||||
|
||||
principalAttrs, err := a.buildPrincipalAttributes(ctx, conn, params.Principal, principalOrgID)
|
||||
principalAttrs, err := a.buildPrincipalAttributes(ctx, conn, params.Principal, scopedPrincipalAttrs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot build principal attributes: %w", err)
|
||||
}
|
||||
@@ -176,12 +179,12 @@ func (a *Authorizer) buildPrincipalAttributes(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
principalID gid.GID,
|
||||
organizationID string,
|
||||
defaultAttrs map[string]string,
|
||||
) (map[string]string, error) {
|
||||
attrs := map[string]string{
|
||||
"id": principalID.String(),
|
||||
"organization_id": organizationID,
|
||||
"id": principalID.String(),
|
||||
}
|
||||
maps.Copy(attrs, defaultAttrs)
|
||||
|
||||
if entity, ok := coredata.NewEntityFromID(principalID); ok {
|
||||
if attributer, ok := entity.(AuthorizationAttributer); ok {
|
||||
|
||||
@@ -48,6 +48,9 @@ const (
|
||||
ActionMembershipUpdate = "iam:membership:update"
|
||||
ActionMembershipDelete = "iam:membership:delete"
|
||||
|
||||
// Membership role actions
|
||||
ActionMembershipRoleSetOwner = "iam:membership-role:set-owner"
|
||||
|
||||
// Membership Profile actions
|
||||
ActionMembershipProfileGet = "iam:membership-profile:get"
|
||||
|
||||
|
||||
@@ -123,9 +123,23 @@ var IAMOwnerPolicy = policy.NewPolicy(
|
||||
WithSID("full-org-access").
|
||||
When(policy.Equals("principal.organization_id", "resource.organization_id")),
|
||||
|
||||
// Full access to member management (scoped to own organization)
|
||||
policy.Allow("iam:membership:*").
|
||||
WithSID("full-membership-access").
|
||||
// Full access to member management (scoped to own organization), except deletion on SCIM sourced memberships
|
||||
policy.Allow(
|
||||
ActionMembershipGet,
|
||||
ActionMembershipList,
|
||||
ActionMembershipUpdate,
|
||||
).
|
||||
WithSID("membership-owner-access").
|
||||
When(policy.Equals("principal.organization_id", "resource.organization_id")),
|
||||
policy.Allow(
|
||||
ActionMembershipDelete,
|
||||
).
|
||||
WithSID("membership-deletion-owner-access").
|
||||
When(policy.NotEquals("resource.source", "SCIM")),
|
||||
|
||||
// Can set other members OWNER
|
||||
policy.Allow(ActionMembershipRoleSetOwner).
|
||||
WithSID("membership-role-owner-access").
|
||||
When(policy.Equals("principal.organization_id", "resource.organization_id")),
|
||||
|
||||
// Full access to membership profiles (scoped to own organization)
|
||||
@@ -185,11 +199,19 @@ var IAMAdminPolicy = policy.NewPolicy(
|
||||
// Can manage memberships (scoped to own organization)
|
||||
policy.Allow(
|
||||
ActionMembershipGet,
|
||||
ActionMembershipUpdate,
|
||||
).
|
||||
WithSID("membership-admin-access").
|
||||
When(policy.Equals("principal.organization_id", "resource.organization_id")),
|
||||
|
||||
policy.Allow(
|
||||
ActionMembershipUpdate,
|
||||
).
|
||||
WithSID("membership-role-admin-access").
|
||||
When(
|
||||
policy.Equals("principal.organization_id", "resource.organization_id"),
|
||||
policy.NotEquals("resource.role", "OWNER"),
|
||||
),
|
||||
|
||||
// Can view membership profiles (scoped to own organization)
|
||||
policy.Allow(ActionMembershipProfileGet).
|
||||
WithSID("membership-profile-admin-access").
|
||||
|
||||
@@ -950,6 +950,12 @@ func (r *mutationResolver) UpdateMembership(ctx context.Context, input types.Upd
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if input.Role == coredata.MembershipRoleOwner {
|
||||
if err := r.authorize(ctx, input.MembershipID, iam.ActionMembershipRoleSetOwner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
membership, err := r.iam.OrganizationService.UpdateMempership(ctx, input.OrganizationID, input.MembershipID, input.Role)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot update membership", log.Error(err))
|
||||
|
||||
Reference in New Issue
Block a user