Skip to content

feat: premissions revamp#2398

Open
golanglemonade wants to merge 12 commits into
mainfrom
feat-permsissions-revamp
Open

feat: premissions revamp#2398
golanglemonade wants to merge 12 commits into
mainfrom
feat-permsissions-revamp

Conversation

@golanglemonade
Copy link
Copy Markdown
Member

@golanglemonade golanglemonade commented May 14, 2026

⚠️ Breaking Changes

This PR contains revamped changes to permissions constructs and requires data migration.

  • FGA Model - this switches to using a fga.mod file in order to break up permissions into more manageable files. These are grouped based on functionality (e.g. compliance, trust center, regsitry, etc.
  • All base crud permissions are now generated based on the ent schema and are located in fga/model/generated/crud.fga. This contains all the can_<action>_<object> permissions that are used for both API Token scopes as well as base sets of permissions
  • New roles.fga file that contains more defined role types, e.g. compliance_manager, policy_manager, risk_manager - this will allow for easier designation of roles to user. These can be added on top of organization roles. For example, a member can be given policy_manger role which will give them full access to manage all policies in the organization.
    • This file uses special annotations to allow for easier management without duplication:
      • @inherit: This inherits all the same permissions as the specified roles, e.g. compliance_manager inherits the permissions given to can_manage_compliance, can_manage_policies, can_manage_registry, can_manage_risk
      • @crud: This specifies which objects the role provides crud access to
  • New Roles:
    • Super Admin: Users with this role will have the same permissions as the owner with the exception of being able to delete the organization. Organizations can have 0->many super admins
    • Auditor: This will be a mostly read-only role users can give to their auditor and in the future be able to specifiy what they have access to
  • Scoped API Tokens - The update will allow API Tokens to be scoped to specific objects + operations allowing for more fined grained access on the tokens. This deprecates the old can_view, can_edit, can_delete, group_manager scopes in favor of new scopes. THE UI NEEDS TO BE UPDATED TO ACCOMODATE THIS CHANGE
  • Adds new parent_context relation which replaces the parent relation for organization level checks for all objects with the exception of files. This requires a migration of parent relations for organization objects to parent_context. We cannot use contextual tuples because of parent hierarchy, e.g. this fails when you get to something like control_implementation where the access is based on the control and the control is based on the organization - we'd have to pass the full hierachy for this to work with the structure we have today.
  • Removes adding user tuples when objects are created; previously this meant if you changed roles your access wasn't removed for any objects you created. Now, it is based on the roles - so if you can manage policies, you can see the policy you created and do not need a user specific tuple.

Other Changes:

  • Logs email when checkAllowedEmailDomain fails so we know the user attempted to be added to the organization
  • Changes the entitlement reconciliation failed logs when a constraint error to an warn log so test logs aren't full of these errors but we still see the warning in production. All other errors are kept as error level logs.
  • Updates default org update logic to order by personal org desc so we don't set the default org to the personal org if there are other orgs available to the user
  • Adds helpers for creating a fresh org, cleaning up org after
  • Moves all tests that create fresh orgs and do not do river checks (most of the trust center tests) to use t.Parallel()

Deps:

@theopenlane-bender
Copy link
Copy Markdown

🔧 Configuration Changes Detected

This PR contains changes that will affect the Helm chart configuration. A draft infrastructure PR has been automatically created to preview these changes:

📋 Draft PR: https://github.com/theopenlane/openlane-infra/pull/928

Changes Preview:

✅ Updated ConfigMap template

  • 🔄 Merged Helm values.yaml
  • 🔐 External secrets configuration updated
  • ✅ Updated ConfigMap template

The draft infrastructure PR will be closed automatically after this core PR is merged.

@golanglemonade golanglemonade changed the title Feat permsissions revamp feat: premissions revamp May 19, 2026
Signed-off-by: Sarah Funkhouser <147884153+golanglemonade@users.noreply.github.com>
Signed-off-by: Sarah Funkhouser <147884153+golanglemonade@users.noreply.github.com>
Signed-off-by: Sarah Funkhouser <147884153+golanglemonade@users.noreply.github.com>
@golanglemonade golanglemonade force-pushed the feat-permsissions-revamp branch from b0b52b4 to de58a11 Compare May 19, 2026 15:58
Signed-off-by: Sarah Funkhouser <147884153+golanglemonade@users.noreply.github.com>
Signed-off-by: Sarah Funkhouser <147884153+golanglemonade@users.noreply.github.com>
Signed-off-by: Sarah Funkhouser <147884153+golanglemonade@users.noreply.github.com>
Signed-off-by: Sarah Funkhouser <147884153+golanglemonade@users.noreply.github.com>
Signed-off-by: Sarah Funkhouser <147884153+golanglemonade@users.noreply.github.com>
Signed-off-by: Sarah Funkhouser <147884153+golanglemonade@users.noreply.github.com>
Signed-off-by: Sarah Funkhouser <147884153+golanglemonade@users.noreply.github.com>
Signed-off-by: Sarah Funkhouser <147884153+golanglemonade@users.noreply.github.com>
Signed-off-by: Sarah Funkhouser <147884153+golanglemonade@users.noreply.github.com>
@golanglemonade golanglemonade marked this pull request as ready for review May 20, 2026 19:01
@golanglemonade golanglemonade requested a review from a team as a code owner May 20, 2026 19:01
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
13.3% Coverage on New Code (required ≥ 20%)

See analysis details on SonarQube Cloud

_, scopesModified = m.AppendedScopes()
}

if scopesModified && m.Op().Is(ent.OpUpdateOne) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the hook triggers on OpUpdate and OpUpdateOne but you only have logic for OpUpdateOne

@@ -39,7 +37,9 @@ func HookObjectOwnedTuples(parents []string, ownerRelation string, skipCreateUse

var addTuples []fgax.TupleKey

if skip := skipCreateUserPermissions(ctx, m); !skip {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is something changing with the order of operations that allows us to safely remove this?

).
Order(
// order by personal orgs last so that if there is another org available it will be set as the default instead of the personal org
organization.ByPersonalOrg(sql.OrderAsc()),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why would we need to perform ordering when the Where condition already precludes the previously set organization ID ?

caller, ok := auth.CallerFromContext(ctx)
if !ok || caller == nil || caller.IsAnonymous() {
logx.FromContext(ctx).Info().Msg("unable to get caller from context")
caller, ok := auth.CallerFromContext(ctx)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it might be easier to do GetAuthzSubjectType or one of the other helpers here

// allow member to invite members
if strings.EqualFold(role.String(), fgax.MemberRelation) {
switch strings.ToLower(role.String()) {
case strings.ToLower(fgax.MemberRelation):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the fgax constants are all already lower, no real reason to have strings.ToLower prefixing all these

expected string
}{
{name: "view query", objectType: "Control", relation: "can_view", expected: "can_view_control"},
{name: "update op", objectType: "Task", relation: "can_view", op: ent.OpUpdate, expected: "can_edit_task"},
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update operation checks can_view but on OpUpdate and expected can edit?

{name: "view query", objectType: "Control", relation: "can_view", expected: "can_view_control"},
{name: "update op", objectType: "Task", relation: "can_view", op: ent.OpUpdate, expected: "can_edit_task"},
{name: "edit relation", objectType: "Evidence", relation: "can_edit", expected: "can_edit_evidence"},
{name: "delete op", objectType: "File", relation: "can_edit", op: ent.OpDeleteOne, expected: "can_delete_file"},
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be can_delete?

return privacy.Allow
}

if auth.IsAPITokenAuthentication(ctx) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this duplicative of the check that's at the top of the function already?


// scopedRelation returns the scoped relation based on the object type, relation, and operation. A operation is checked for for create, update, delete. If instead a specific relation should be checked, that should be passed instead of the operation
func scopedRelation(objectType string, relation string, op *ent.Op) string {
object := strcase.SnakeCase(objectType)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why manipulate the object type input like this?

func AllowIfTokenHasMutationScope() privacy.MutationRuleFunc {
return privacy.MutationRuleFunc(func(ctx context.Context, m ent.Mutation) error {
objectType := m.Type()
if objectType == "" {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible for m.Type() to return empty?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants