Commit c75ceda

mo khan <mo@mokhan.ca>
2025-04-02 15:45:37
feat: embed cedar policies in policies package
1 parent c851e7d
pkg/policies/init.go
@@ -0,0 +1,72 @@
+package policies
+
+import (
+	"embed"
+	_ "embed"
+	"encoding/json"
+	"fmt"
+	"io/fs"
+	"log"
+
+	"github.com/cedar-policy/cedar-go"
+	"github.com/cedar-policy/cedar-go/types"
+	"github.com/xlgmokha/x/pkg/x"
+)
+
+//go:embed *.cedar
+var files embed.FS
+
+var All *cedar.PolicySet = cedar.NewPolicySet()
+
+const entitiesJSON = `[
+  {
+    "uid": { "type": "User", "id": "alice" },
+    "attrs": { "age": 18 },
+    "parents": []
+  },
+  {
+    "uid": { "type": "Photo", "id": "VacationPhoto94.jpg" },
+    "attrs": {},
+    "parents": [{ "type": "Album", "id": "jane_vacation" }]
+  }
+]`
+
+func init() {
+	err := fs.WalkDir(files, ".", func(path string, d fs.DirEntry, err error) error {
+		if err != nil {
+			return err
+		}
+
+		if d.IsDir() {
+			return nil
+		}
+
+		content, err := fs.ReadFile(files, path)
+		if err != nil {
+			return err
+		}
+
+		var policy cedar.Policy
+		if err := policy.UnmarshalCedar(content); err != nil {
+			return err
+		}
+
+		All.Add(cedar.PolicyID(path), &policy)
+		return nil
+	})
+
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+func Allowed(request cedar.Request) bool {
+	var entities cedar.EntityMap
+	x.Check(json.Unmarshal([]byte(entitiesJSON), &entities))
+
+	ok, diagnostic := All.IsAuthorized(entities, request)
+	if len(diagnostic.Errors) > 0 || len(diagnostic.Reasons) > 0 {
+		fmt.Printf("%v %v\n", diagnostic.Errors, diagnostic.Reasons)
+	}
+	return ok == types.Allow
+}
pkg/policies/project.cedar
@@ -0,0 +1,5 @@
+permit (
+	principal == User::"alice",
+	action == Action::"view",
+	resource in Album::"jane_vacation"
+);
pkg/rpc/ability_handler.go
@@ -2,14 +2,25 @@ package rpc
 
 import (
 	context "context"
+
+	"github.com/cedar-policy/cedar-go"
+	"gitlab.com/mokhax/spike/pkg/policies"
 )
 
 type AbilityHandler struct {
 	UnimplementedAbilityServer
 }
 
+func NewAbilityHandler() *AbilityHandler {
+	return &AbilityHandler{}
+}
+
 func (h *AbilityHandler) Allowed(ctx context.Context, req *AllowRequest) (*AllowReply, error) {
-	return &AllowReply{
-		Result: false,
-	}, nil
+	ok := policies.Allowed(cedar.Request{
+		Principal: cedar.NewEntityUID("User", cedar.String(req.Subject)),
+		Action:    cedar.NewEntityUID("Action", cedar.String(req.Permission)),
+		Resource:  cedar.NewEntityUID("Album", cedar.String(req.Resource)),
+		Context:   cedar.NewRecord(cedar.RecordMap{}),
+	})
+	return &AllowReply{Result: ok}, nil
 }
pkg/rpc/server.go
@@ -6,6 +6,6 @@ import (
 
 func New(options ...grpc.ServerOption) *grpc.Server {
 	server := grpc.NewServer(options...)
-	RegisterAbilityServer(server, &AbilityHandler{})
+	RegisterAbilityServer(server, NewAbilityHandler())
 	return server
 }
pkg/rpc/server_test.go
@@ -31,7 +31,7 @@ func TestServer(t *testing.T) {
 	defer connection.Close()
 	client := NewAbilityClient(connection)
 
-	t.Run("returns a result", func(t *testing.T) {
+	t.Run("returns false", func(t *testing.T) {
 		reply, err := client.Allowed(t.Context(), &AllowRequest{
 			Subject:    "",
 			Permission: "",
@@ -40,4 +40,14 @@ func TestServer(t *testing.T) {
 		require.NoError(t, err)
 		assert.False(t, reply.Result)
 	})
+
+	t.Run("returns true", func(t *testing.T) {
+		reply, err := client.Allowed(t.Context(), &AllowRequest{
+			Subject:    "alice",
+			Permission: "view",
+			Resource:   "jane_vacation",
+		})
+		require.NoError(t, err)
+		assert.True(t, reply.Result)
+	})
 }
test/e2e_test.go
@@ -20,6 +20,10 @@ import (
 )
 
 func TestAuthx(t *testing.T) {
+	if env.Fetch("SKIP_E2E", "") != "" {
+		t.Skip()
+	}
+
 	_ = playwright.Install()
 
 	pw := x.Must(playwright.Run())