Skip to content

Commit

Permalink
feat: Another Batch of Permissions (#885)
Browse files Browse the repository at this point in the history
  • Loading branch information
FoseFx authored Nov 20, 2024
1 parent 688aac0 commit 30eae46
Show file tree
Hide file tree
Showing 44 changed files with 608 additions and 104 deletions.
14 changes: 11 additions & 3 deletions libs/hwauthz/authz.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,16 @@ type Relation string
// See https://authzed.com/docs/spicedb/concepts/schema#permissions
type Permission Relation

// ObjectType is a string alias for the
// Type of Object definitions, (e.g., "user")
type ObjectType string

// An Object must relate to an
// [Object Definition](https://authzed.com/docs/spicedb/concepts/schema#object-type-definitions) in the spicedb schema.
// We use this to build a v1.ObjectReference used for a Relationship
type Object interface {
// Type of the Object definition, (e.g., "user")
Type() string
Type() ObjectType
// ID is a globally unique and stable identifier, likely a UUID
ID() string
}
Expand Down Expand Up @@ -84,10 +88,10 @@ func (r *Relationship) String() string {

func (r *Relationship) SpanAttributeKeyValue() []attribute.KeyValue {
return []attribute.KeyValue{
attribute.String("hwauthz.resource.type", r.Resource.Type()),
attribute.String("hwauthz.resource.type", string(r.Resource.Type())),
attribute.String("hwauthz.resource.id", r.Resource.ID()),
attribute.String("hwauthz.relation", string(r.Relation)),
attribute.String("hwauthz.subject.type", r.Subject.Type()),
attribute.String("hwauthz.subject.type", string(r.Subject.Type())),
attribute.String("hwauthz.subject.id", r.Subject.ID()),
}
}
Expand Down Expand Up @@ -181,6 +185,10 @@ type AuthZ interface {
// BulkMust performs many checks and errors if any one fails.
// Also see Must and BulkCheck
BulkMust(ctx context.Context, checks ...PermissionCheck) error
// LookupResources yields all resource ids, where (subject, relation, resourceType:resource) is a valid relation
// Useful, where the set of accessible resources is much smaller than the query results.
// Use this to first lookup permitted resources, and then restrict your database query to them.
LookupResources(ctx context.Context, subject Object, relation Relation, resourceType ObjectType) ([]string, error)
}

// Error returns err, if not nil or StatusErrorPermissionDenied, if permissionGranted is false
Expand Down
19 changes: 19 additions & 0 deletions libs/hwauthz/commonPerm/generic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package commonPerm

import "hwauthz"

// GenericObject implements hwauthz.Object and can be used for ad-hoc Objects
// Use this only if it is not feasible to write a proper hwauthz.Object-implementing type
// For example for tests, or if the type is only used once or so
type GenericObject struct {
Typ hwauthz.ObjectType
Id string
}

func (s GenericObject) Type() hwauthz.ObjectType {
return s.Typ
}

func (s GenericObject) ID() string {
return s.Id
}
10 changes: 6 additions & 4 deletions libs/hwauthz/commonPerm/userOrg.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import (
"common/auth"
"context"

"hwauthz"

"github.com/google/uuid"
)

type User uuid.UUID

func (t User) Type() string { return "user" }
func (t User) ID() string { return uuid.UUID(t).String() }
func (t User) Type() hwauthz.ObjectType { return "user" }
func (t User) ID() string { return uuid.UUID(t).String() }

func UserFromCtx(ctx context.Context) User {
userID := auth.MustGetUserID(ctx)
Expand All @@ -19,8 +21,8 @@ func UserFromCtx(ctx context.Context) User {

type Organization uuid.UUID

func (p Organization) Type() string { return "organization" }
func (p Organization) ID() string { return uuid.UUID(p).String() }
func (p Organization) Type() hwauthz.ObjectType { return "organization" }
func (p Organization) ID() string { return uuid.UUID(p).String() }

func OrganizationFromCtx(ctx context.Context) Organization {
organizationID := auth.MustGetOrganizationID(ctx)
Expand Down
47 changes: 46 additions & 1 deletion libs/hwauthz/spicedb/spicedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package spicedb

import (
"context"
"errors"
"fmt"
"hwutil"
"io"
"telemetry"

v1 "github.com/authzed/authzed-go/proto/authzed/api/v1"
Expand Down Expand Up @@ -49,7 +51,7 @@ func SetupSpiceDb(endpoint, token string) *authzed.Client {
// fromObject builds a new v1.ObjectReference from a hwauthz.Object
func fromObject(object hwauthz.Object) *v1.ObjectReference {
return &v1.ObjectReference{
ObjectType: object.Type(),
ObjectType: string(object.Type()),
ObjectId: object.ID(),
}
}
Expand Down Expand Up @@ -242,6 +244,12 @@ func (s *SpiceDBAuthZ) Must(ctx context.Context, check hwauthz.PermissionCheck)
}

func (s *SpiceDBAuthZ) BulkMust(ctx context.Context, checks ...hwauthz.PermissionCheck) error {
if len(checks) == 0 {
return nil
}
if len(checks) == 1 {
return s.Must(ctx, checks[0])
}
results, err := s.BulkCheck(ctx, checks)
if err != nil {
return err
Expand All @@ -253,3 +261,40 @@ func (s *SpiceDBAuthZ) BulkMust(ctx context.Context, checks ...hwauthz.Permissio
}
return nil
}

func (s *SpiceDBAuthZ) LookupResources(
ctx context.Context, subject hwauthz.Object, relation hwauthz.Relation, resourceType hwauthz.ObjectType,
) ([]string, error) {
// open stream
stream, err := s.client.LookupResources(ctx, &v1.LookupResourcesRequest{
Consistency: nil,
ResourceObjectType: string(resourceType),
Permission: string(relation),
Subject: &v1.SubjectReference{
Object: fromObject(subject),
},
Context: nil,
// TODO: pagination
OptionalLimit: 0,
OptionalCursor: nil,
})
if err != nil {
return nil, fmt.Errorf("spicedb: could not lookup resources: %w", err)
}

resources := make([]string, 0)

// collect it until EOF
for {
res, err := stream.Recv()
if errors.Is(err, io.EOF) {
break
} else if err != nil {
return nil, fmt.Errorf("spicedb: could not lookup resources: %w", err)
}

resources = append(resources, res.GetResourceObjectId())
}

return resources, nil
}
60 changes: 41 additions & 19 deletions libs/hwauthz/spicedb/spicedb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package spicedb

import (
"context"
"hwauthz/commonPerm"
"hwtesting"
"os"
"os/signal"
Expand All @@ -10,6 +11,8 @@ import (
"testing"
"time"

"github.com/google/uuid"

zlog "github.com/rs/zerolog/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -47,19 +50,6 @@ func TestMain(m *testing.M) {
os.Exit(exitCode)
}

type testObject struct {
typ string
id string
}

func (s testObject) Type() string {
return s.typ
}

func (s testObject) ID() string {
return s.id
}

func TestBulkCheck(t *testing.T) {
ctx := context.Background()
client := NewSpiceDBAuthZ()
Expand All @@ -72,14 +62,14 @@ func TestBulkCheck(t *testing.T) {
tx := hwauthz.NewTx(client, nil, nil)

for i := range checks {
sub := testObject{
typ: "user",
id: strconv.Itoa(i),
sub := commonPerm.GenericObject{
Typ: "user",
Id: strconv.Itoa(i),
}
relation := hwauthz.Permission("member")
resc := testObject{
typ: "organization",
id: strconv.Itoa(i),
resc := commonPerm.GenericObject{
Typ: "organization",
Id: strconv.Itoa(i),
}
checks[i] = hwauthz.NewPermissionCheck(sub, relation, resc)

Expand Down Expand Up @@ -109,3 +99,35 @@ func TestBulkCheck(t *testing.T) {
}
}
}

func TestLookupResources(t *testing.T) {
ctx := context.Background()
client := NewSpiceDBAuthZ()

// preparations
sub := commonPerm.GenericObject{
Typ: "user",
Id: uuid.New().String(),
}
relation := hwauthz.Relation("member")

// create test relations
tx := hwauthz.NewTx(client, nil, nil)
for i := range 3 {
resc := commonPerm.GenericObject{
Typ: "organization",
Id: strconv.Itoa(i),
}
relationship := hwauthz.NewRelationship(sub, relation, resc)
tx.Create(relationship)
}
_, err := tx.Commit(ctx)
require.NoError(t, err)

// LookupResources
subjects, err := client.LookupResources(ctx, sub, relation, "organization")
require.NoError(t, err)

// assert
assert.ElementsMatch(t, []string{"0", "1", "2"}, subjects)
}
6 changes: 6 additions & 0 deletions libs/hwauthz/test/true_authz.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,9 @@ func (a *TrueAuthZ) BulkCheck(_ context.Context, checks []hwauthz.PermissionChec
func (s *TrueAuthZ) BulkMust(_ context.Context, _ ...hwauthz.PermissionCheck) error {
return nil
}

func (s *TrueAuthZ) LookupResources(
_ context.Context, _ hwauthz.Object, _ hwauthz.Relation, _ hwauthz.ObjectType,
) ([]string, error) {
return []string{}, nil
}
4 changes: 2 additions & 2 deletions services/property-svc/cmd/service/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ func Main(version string, ready func()) {

propertyHandlers := ph.NewPropertyHandlers(aggregateStore, authz)
propertySetHandlers := psh.NewPropertySetHandlers(aggregateStore, authz)
propertyViewHandlers := pvih.NewPropertyViewHandlers(aggregateStore)
propertyValueHandlers := pvh.NewPropertyValueHandlers(aggregateStore)
propertyViewHandlers := pvih.NewPropertyViewHandlers(aggregateStore, authz)
propertyValueHandlers := pvh.NewPropertyValueHandlers(aggregateStore, authz)

common.StartNewGRPCServer(ctx, common.ResolveAddrFromEnv(), func(server *daprd.Server) {
grpcServer := server.GrpcServer()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (

type PropertySet uuid.UUID

func (t PropertySet) Type() string { return "property_set" }
func (t PropertySet) ID() string { return uuid.UUID(t).String() }
func (t PropertySet) Type() hwauthz.ObjectType { return "property_set" }
func (t PropertySet) ID() string { return uuid.UUID(t).String() }

// Direct Relations

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ package v1
import (
"common"
"context"
"hwauthz"
"hwauthz/commonPerm"
"hwdb"
"hwes"

"property-svc/internal/property/perm"

"github.com/google/uuid"

"property-svc/internal/property-value/aggregate"
Expand All @@ -20,14 +24,22 @@ type AttachPropertyValueCommandHandler func(
subjectID uuid.UUID,
) (common.ConsistencyToken, error)

func NewAttachPropertyValueCommandHandler(as hwes.AggregateStore) AttachPropertyValueCommandHandler {
func NewAttachPropertyValueCommandHandler(
as hwes.AggregateStore, authz hwauthz.AuthZ,
) AttachPropertyValueCommandHandler {
return func(
ctx context.Context,
propertyValueID uuid.UUID,
propertyID uuid.UUID,
value interface{},
subjectID uuid.UUID,
) (common.ConsistencyToken, error) {
user := commonPerm.UserFromCtx(ctx)
check := hwauthz.NewPermissionCheck(user, perm.PropertyCanUserUpdateValue, perm.Property(propertyID))
if err := authz.Must(ctx, check); err != nil {
return 0, err
}

propertyValueRepo := property_value_repo.New(hwdb.GetDB())
var a *aggregate.PropertyValueAggregate

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package handlers

import (
"hwauthz"
"hwes"

commandsV1 "property-svc/internal/property-value/commands/v1"
Expand All @@ -20,17 +21,17 @@ type Handlers struct {
Queries *Queries
}

func NewPropertyValueHandlers(as hwes.AggregateStore) *Handlers {
func NewPropertyValueHandlers(as hwes.AggregateStore, authz hwauthz.AuthZ) *Handlers {
return &Handlers{
Commands: &Commands{
V1: &commandsV1.PropertyValueCommands{
AttachPropertyValue: commandsV1.NewAttachPropertyValueCommandHandler(as),
AttachPropertyValue: commandsV1.NewAttachPropertyValueCommandHandler(as, authz),
},
},
Queries: &Queries{
V1: &queriesV1.PropertyValueQueries{
GetPropertyValueByID: queriesV1.NewGetPropertyValueByIDQueryHandler(as),
GetRelevantPropertyValues: queriesV1.NewGetRelevantPropertyValuesQueryHandler(as),
GetRelevantPropertyValues: queriesV1.NewGetRelevantPropertyValuesQueryHandler(as, authz),
},
},
}
Expand Down
Loading

0 comments on commit 30eae46

Please sign in to comment.