Repository: theopenlane/core
Pull request #591: simplify policy validation by using internal context for tests
Author: login: adelowo
Labels: name: httpserve
name: graphapi
Created: 2025-04-02T21:51:56Z | Merged: 2025-04-04T18:00:53Z

Added a new internal context that can be used in the tests. I have set this up as a pre policy so it will always 
run first and do a quick pass through if it is running in tests


Diff:
diff --git a/internal/ent/hooks/usersettings.go b/internal/ent/hooks/usersettings.go
index 09b791afe3..b9f7bcc529 100644
--- a/internal/ent/hooks/usersettings.go
+++ b/internal/ent/hooks/usersettings.go
@@ -35,8 +35,8 @@ func HookUserSetting() ent.Hook {
 
 // allowDefaultOrgUpdate checks if the user has access to the organization being updated as their default org
 func allowDefaultOrgUpdate(ctx context.Context, m *generated.UserSettingMutation, orgID string) bool {
-	// allow if explicitly allowed
-	if _, allow := privacy.DecisionFromContext(ctx); allow {
+	// allow if explicitly allowed or if it's an internal request
+	if _, allow := privacy.DecisionFromContext(ctx); allow || rule.IsInternalRequest(ctx) {
 		return true
 	}
 
diff --git a/internal/ent/interceptors/filter.go b/internal/ent/interceptors/filter.go
index 01696b13c0..a6c311de39 100644
--- a/internal/ent/interceptors/filter.go
+++ b/internal/ent/interceptors/filter.go
@@ -16,6 +16,7 @@ import (
 
 	"github.com/theopenlane/core/internal/ent/generated/intercept"
 	"github.com/theopenlane/core/internal/ent/generated/privacy"
+	"github.com/theopenlane/core/internal/ent/privacy/rule"
 	"github.com/theopenlane/core/internal/ent/privacy/utils"
 )
 
@@ -34,7 +35,7 @@ func FilterListQuery() ent.Interceptor {
 // the FilterQueryResults function should be used in most cases due to performance issues of ListObjectsRequest
 func AddIDPredicate(ctx context.Context, q intercept.Query) error {
 	// by pass checks on invite or pre-allowed request
-	if _, allow := privacy.DecisionFromContext(ctx); allow {
+	if _, allow := privacy.DecisionFromContext(ctx); allow || rule.IsInternalRequest(ctx) {
 		return nil
 	}
 
diff --git a/internal/ent/interceptors/user.go b/internal/ent/interceptors/user.go
index 3a2d37e3dc..78a5c3baa3 100644
--- a/internal/ent/interceptors/user.go
+++ b/internal/ent/interceptors/user.go
@@ -14,6 +14,7 @@ import (
 	"github.com/theopenlane/core/internal/ent/generated/intercept"
 	"github.com/theopenlane/core/internal/ent/generated/privacy"
 	"github.com/theopenlane/core/internal/ent/generated/user"
+	"github.com/theopenlane/core/internal/ent/privacy/rule"
 	"github.com/theopenlane/core/internal/ent/privacy/utils"
 )
 
@@ -23,7 +24,7 @@ func TraverseUser() ent.Interceptor {
 		// bypass filter if the request is allowed, this happens when a user is
 		// being created, via invite or other method by another authenticated user
 		// or in tests
-		if _, allow := privacy.DecisionFromContext(ctx); allow {
+		if _, allow := privacy.DecisionFromContext(ctx); allow || rule.IsInternalRequest(ctx) {
 			return nil
 		}
 
diff --git a/internal/ent/interceptors/usersetting.go b/internal/ent/interceptors/usersetting.go
index 55b7980d42..1c4eccd749 100644
--- a/internal/ent/interceptors/usersetting.go
+++ b/internal/ent/interceptors/usersetting.go
@@ -14,14 +14,13 @@ import (
 	"github.com/theopenlane/core/internal/ent/generated/privacy"
 	"github.com/theopenlane/core/internal/ent/generated/user"
 	"github.com/theopenlane/core/internal/ent/generated/usersetting"
+	"github.com/theopenlane/core/internal/ent/privacy/rule"
 )
 
 func InterceptorUserSetting() ent.Interceptor {
 	return intercept.TraverseUserSetting(func(ctx context.Context, q *generated.UserSettingQuery) error {
-		// bypass filter if the request is allowed, this happens when a user is
-		// being created, via invite or other method by another authenticated user
-		// or in tests
-		if _, allow := privacy.DecisionFromContext(ctx); allow {
+		// bypass filter if the request is allowed or if it's an internal request
+		if _, allow := privacy.DecisionFromContext(ctx); allow || rule.IsInternalRequest(ctx) {
 			return nil
 		}
 
diff --git a/internal/ent/privacy/policy/base.go b/internal/ent/privacy/policy/base.go
index 10f2e82ec2..e16c6f36d0 100644
--- a/internal/ent/privacy/policy/base.go
+++ b/internal/ent/privacy/policy/base.go
@@ -4,12 +4,17 @@ import (
 	"entgo.io/ent"
 
 	"github.com/theopenlane/core/internal/ent/generated/privacy"
+	"github.com/theopenlane/core/internal/ent/privacy/rule"
 )
 
 // prePolicy is executed before privacy policy
 var prePolicy = privacy.Policy{
-	Query:    privacy.QueryPolicy{},
-	Mutation: privacy.MutationPolicy{},
+	Query: privacy.QueryPolicy{
+		rule.AllowIfInternalRequest(),
+	},
+	Mutation: privacy.MutationPolicy{
+		rule.AllowIfInternalRequest(),
+	},
 }
 
 // postPolicy is executed after privacy policy
diff --git a/internal/ent/privacy/rule/internal.go b/internal/ent/privacy/rule/internal.go
new file mode 100644
index 0000000000..930709235b
--- /dev/null
+++ b/internal/ent/privacy/rule/internal.go
@@ -0,0 +1,32 @@
+package rule
+
+import (
+	"context"
+
+	"github.com/theopenlane/utils/contextx"
+
+	"github.com/theopenlane/core/internal/ent/generated/privacy"
+)
+
+type internalAllowContextKey struct{}
+
+func WithInternalContext(ctx context.Context) context.Context {
+	return contextx.With(ctx, internalAllowContextKey{})
+}
+
+// IsInternalRequest checks if the context has an internal request key
+func IsInternalRequest(ctx context.Context) bool {
+	_, ok := contextx.From[internalAllowContextKey](ctx)
+	return ok
+}
+
+// AllowIfInternalRequest is a pre-policy rule that allows all operations if the context has an internal request key
+func AllowIfInternalRequest() privacy.QueryMutationRule {
+	return privacy.ContextQueryMutationRule(func(ctx context.Context) error {
+		if IsInternalRequest(ctx) {
+			return privacy.Allow
+		}
+
+		return privacy.Skip
+	})
+}
diff --git a/internal/ent/privacy/rule/organization.go b/internal/ent/privacy/rule/organization.go
index c8624aa15f..c1933f8365 100644
--- a/internal/ent/privacy/rule/organization.go
+++ b/internal/ent/privacy/rule/organization.go
@@ -30,8 +30,8 @@ type genericMutation interface {
 // This rule assumes that the organization id and user id are set in the context
 // and only checks for access to the single organization
 func CheckCurrentOrgAccess(ctx context.Context, m ent.Mutation, relation string) error {
-	// skip if permission is already set to allow
-	if _, allow := privacy.DecisionFromContext(ctx); allow {
+	// skip if permission is already set to allow or if it's an internal request
+	if _, allow := privacy.DecisionFromContext(ctx); allow || IsInternalRequest(ctx) {
 		return privacy.Allow
 	}
 
@@ -57,6 +57,11 @@ func CheckCurrentOrgAccess(ctx context.Context, m ent.Mutation, relation string)
 // CheckOrgAccessBasedOnRequest checks if the authenticated user has access to the organizations that are requested
 // in the organization query based on the relation provided
 func CheckOrgAccessBasedOnRequest(ctx context.Context, relation string, query *generated.OrganizationQuery) error {
+	// skip if it's an internal request
+	if IsInternalRequest(ctx) {
+		return privacy.Allow
+	}
+
 	// run the query with allow context to get the list of organizations
 	// the user is trying to access
 	allowCtx := privacy.DecisionContext(ctx, privacy.Allow)
@@ -83,8 +88,8 @@ func CheckOrgAccessBasedOnRequest(ctx context.Context, relation string, query *g
 
 // checkOrgAccess checks if the authenticated user has access to the organization
 func checkOrgAccess(ctx context.Context, relation, organizationID string) error {
-	// skip if permission is already set to allow
-	if _, allow := privacy.DecisionFromContext(ctx); allow {
+	// skip if permission is already set to allow or if it's an internal request
+	if _, allow := privacy.DecisionFromContext(ctx); allow || IsInternalRequest(ctx) {
 		return nil
 	}
 
diff --git a/internal/graphapi/models_test.go b/internal/graphapi/models_test.go
index 4392c6721f..af80bbf41b 100644
--- a/internal/graphapi/models_test.go
+++ b/internal/graphapi/models_test.go
@@ -11,7 +11,7 @@ import (
 	"github.com/stretchr/testify/require"
 
 	ent "github.com/theopenlane/core/internal/ent/generated"
-	"github.com/theopenlane/core/internal/ent/generated/privacy"
+	"github.com/theopenlane/core/internal/ent/privacy/rule"
 	"github.com/theopenlane/core/pkg/enums"
 	"github.com/theopenlane/core/pkg/models"
 )
@@ -312,10 +312,7 @@ func (c *Cleanup[DeleteExec]) MustDelete(ctx context.Context, suite *GraphTestSu
 // setContext is a helper function to set the context for the client
 // setting privacy to allow and adding the client to the context
 func setContext(ctx context.Context, db *ent.Client) context.Context {
-	ctx = privacy.DecisionContext(ctx, privacy.Allow)
-
-	// add client to context
-	return ent.NewContext(ctx, db)
+	return ent.NewContext(rule.WithInternalContext(ctx), db)
 }
 
 // MustNew organization builder is used to create, without authz checks, orgs in the database
diff --git a/internal/httpserve/authmanager/authmanager.go b/internal/httpserve/authmanager/authmanager.go
index 1cd18691aa..a4d02de745 100644
--- a/internal/httpserve/authmanager/authmanager.go
+++ b/internal/httpserve/authmanager/authmanager.go
@@ -389,8 +389,8 @@ func (a *Client) getPersonalOrgID(ctx context.Context, user *generated.User) (*g
 
 // skipOrgValidation checks if the org validation should be skipped based on the context
 func skipOrgValidation(ctx context.Context) bool {
-	// skip if explicitly allowed
-	if _, allow := privacy.DecisionFromContext(ctx); allow {
+	// skip if explicitly allowed or if it's an internal request
+	if _, allow := privacy.DecisionFromContext(ctx); allow || rule.IsInternalRequest(ctx) {
 		return true
 	}
 


Reviews:
user: login: golanglemonade
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-03T21:57:07Z
user: login: adelowo
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-03T22:06:33Z
user: login: golanglemonade
body: more tests still need to be updated, but merging so that we can use in new tests and not have to update them later
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-04T18:00:47Z

Review comments:
diff_hunk: @@ -0,0 +1,34 @@
+package rule
+
+import (
+	"context"
+
+	"github.com/theopenlane/core/internal/ent/generated/privacy"
+)
+
+type contextKey string
+
+const (
+	internalCtx contextKey = "internalCtx"
path: internal/ent/privacy/rule/internal.go
user: login: golanglemonade
body: you can just use a type, e..g:

```
 type internalAllowContextKey struct{}
```

This should be used in conjunction with our `contextx` packge: https://github.com/theopenlane/utils/blob/main/contextx/contextx.go

so you can just do 


```
 contextx.With(ctx, &internalAllowContextKey{token})
 ```
 
 and get it back wiith
 ```
 _, allow := contextx.From[internalAllowContextKey](ctx)
```
 
created_at: 2025-04-03T21:57:06Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,34 @@
+package rule
+
+import (
+	"context"
+
+	"github.com/theopenlane/core/internal/ent/generated/privacy"
+)
+
+type contextKey string
+
+const (
+	internalCtx contextKey = "internalCtx"
path: internal/ent/privacy/rule/internal.go
user: login: adelowo
body: resolved in https://github.com/theopenlane/core/pull/591/commits/a289c9395d305083c25b677cb64d1928568e91b2
created_at: 2025-04-03T22:06:33Z
author_association: MEMBER

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-04-03T23:03:59Z
body: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=theopenlane_core&pullRequest=591) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [9.1% Coverage on New Code](https://sonarcloud.io/component_measures?id=theopenlane_core&pullRequest=591&metric=new_coverage&view=list) (required ≥ 20%)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=theopenlane_core&pullRequest=591)


author_association: NONE