raystack / raystack/frontier

Use computed permissions for group membership instead of direct relations

Open
#1,479 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Go
Stars
344
Forks
47
Avg merge
4d 4h
Merged PRs (30d)
26

Description

Problem

Group membership currently uses direct group#member@user relations, while organization and project membership use policies (role bindings). This inconsistency means groups require special handling.

Current State

Resource How membership works
Organization Policies (role bindings)
Project Policies (role bindings)
Group Direct group#member@user relation

When a user is added to a group, we create both a policy AND a direct relation.

Source: core/group/service.go:168-191

// AddMember adds a subject(user) to group as member
func (s Service) AddMember(ctx context.Context, groupID string, principal authenticate.Principal) error {
    // first create a policy for the user as member of the group
    if err := s.addMemberPolicy(ctx, groupID, principal); err != nil {
        return err
    }

    // then create a relation between group and user as member
    rel := relation.Relation{
        Object: relation.Object{
            ID:        groupID,
            Namespace: schema.GroupNamespace,
        },
        Subject: relation.Subject{
            ID:        principal.ID,
            Namespace: principal.Type,
        },
        RelationName: schema.MemberRelationName,
    }
    if _, err := s.relationService.Create(ctx, rel); err != nil {
        return err
    }
    return nil
}

Same pattern exists for addOwner at lines 193-221.

Proposed State

Resource How membership works
Organization Policies (role bindings)
Project Policies (role bindings)
Group Policies (role bindings)

Group membership becomes computed from policies, just like org and project.

How it works

Schema change:

// Before
definition group {
    relation member: app/user  // direct relation
    permission get = ... + member
}

// After
definition group {
    relation granted: app/rolebinding
    permission members = granted->app_group_member  // computed from policies
    permission get = ...
}

SpiceDB resolves group members by:

  1. Find all role bindings granted to the group
  2. Filter to those with "Group Member" role
  3. Return their bearers

Tested in AuthZed Playground - this approach works.

Benefits

Benefit Description
Consistency All resources (org, project, group) use the same pattern
Single source of truth Membership determined by policies only, no sync issues
Simpler code Remove direct relation management for groups
SDK simplicity Same role-based API for all resources

Potential Downsides

Concern Impact Mitigation
Query complexity Membership is computed, not direct lookup SpiceDB optimizes and caches these
Schema migration Need to update SpiceDB schema Can be done incrementally
Breaking change External systems using group#member relation Document in release notes

Code Changes

  1. Update base_schema.zed - change group#member from relation to computed permission
  2. core/group/service.go - AddMember: remove relationService.Create call, keep only policy
  3. core/group/service.go - addOwner: remove relationService.Create call, keep only policy
  4. Update references from group#member to group#members (the computed permission)

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with base_schema.zed and core/group/service.go:168-221, then find references to group#member. Verify how organization and project policies expose membership before changing the group schema and removing direct relation creation from AddMember and addOwner. Done means group membership uses policies consistently and all group#member references use the computed permission.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
authorization, backend
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.