Skip to content

Commit

Permalink
Juju 7123/add role type to internal openfga (#1448)
Browse files Browse the repository at this point in the history
* add role to parse tag

* feat(db): update db with roles

Updates db with roles entries.

* test(roleentry): adds some basic tests

* feat(database): addRole db method

* feat(dbmodel): migration was missing comma

* fix(schema-version): missing schema version in migration caused infinite migration loop

* feat(database): adds database methods for roles according to the specification

* feat(database): use List and Count for roles

* chore(testname): fix test name for groups

* feat(internal/openfga): add roles to internal/openfga

Allows the removal of a role from OpenFGA and plugs them into resourceTypes, adds their relation
constant, and tests we can relate users->role, user->group--group#member->assignee->role

* docs(removerole godoc): remove role godoc update

---------

Co-authored-by: SimoneDutto <simone.dutto@canonical.com>
  • Loading branch information
ale8k and SimoneDutto authored Nov 20, 2024
1 parent eff1524 commit 9e1c84d
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 9 deletions.
15 changes: 11 additions & 4 deletions internal/openfga/names/names.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import (
var (
// MemberRelation represents a member relation between entities.
MemberRelation cofga.Relation = "member"
// AssigneeRelation represents a assignee relation between entities.
AssigneeRelation cofga.Relation = "assignee"
// AdministratorRelation represents an administrator relation between entities.
AdministratorRelation cofga.Relation = "administrator"
// ControllerRelation represents a controller relation between entities.
Expand Down Expand Up @@ -59,7 +61,8 @@ type ResourceTagger interface {
names.ModelTag |
names.ApplicationOfferTag |
names.CloudTag |
jimmnames.ServiceAccountTag
jimmnames.ServiceAccountTag |
jimmnames.RoleTag

Id() string
Kind() string
Expand Down Expand Up @@ -102,9 +105,13 @@ func ConvertGenericTag(t names.Tag) *Tag {
// applicationoffer resource, so we specify user as BlankKindTag("controller"))
func BlankKindTag(kind string) (*Tag, error) {
switch kind {
case names.UserTagKind, jimmnames.GroupTagKind,
names.ControllerTagKind, names.ModelTagKind,
names.ApplicationOfferTagKind, names.CloudTagKind,
case names.UserTagKind,
jimmnames.GroupTagKind,
jimmnames.RoleTagKind,
names.ControllerTagKind,
names.ModelTagKind,
names.ApplicationOfferTagKind,
names.CloudTagKind,
jimmnames.ServiceAccountTagKind:
return &Tag{
Kind: cofga.Kind(kind),
Expand Down
44 changes: 39 additions & 5 deletions internal/openfga/openfga.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (

var (
// resourceTypes contains a list of all resource kinds (i.e. tags) used throughout JIMM.
resourceTypes = [...]string{names.UserTagKind, names.ModelTagKind, names.ControllerTagKind, names.ApplicationOfferTagKind, jimmnames.GroupTagKind, jimmnames.ServiceAccountTagKind}
resourceTypes = [...]string{names.UserTagKind, names.ModelTagKind, names.ControllerTagKind, names.ApplicationOfferTagKind, jimmnames.GroupTagKind, jimmnames.ServiceAccountTagKind, jimmnames.RoleTagKind}
)

// Tuple represents a relation between an object and a target.
Expand All @@ -39,18 +39,20 @@ type Relation = cofga.Relation
// Juju/JIMM objects. These are included here for ease of use
// and avoiding string constants.
var (
// UserType represents a user object.
UserType Kind = names.UserTagKind
// ModelType represents a model object.
ModelType Kind = names.ModelTagKind
// GroupType represents a group object.
GroupType Kind = jimmnames.GroupTagKind
// RoleType represents a role object.
RoleType Kind = jimmnames.RoleTagKind
// ApplicationOfferType represents an application offer object.
ApplicationOfferType Kind = names.ApplicationOfferTagKind
// CloudType represents a cloud object.
CloudType Kind = names.CloudTagKind
// ControllerType represents a controller object.
ControllerType Kind = names.ControllerTagKind
// GroupType represents a group object.
GroupType Kind = jimmnames.GroupTagKind
// UserType represents a user object.
UserType Kind = names.UserTagKind
// ServiceAccountType represents a service account.
ServiceAccountType Kind = jimmnames.ServiceAccountTagKind
)
Expand Down Expand Up @@ -308,6 +310,38 @@ func (o *OFGAClient) RemoveApplicationOffer(ctx context.Context, offer names.App
return nil
}

// RemoveRole removes a role.
func (o *OFGAClient) RemoveRole(ctx context.Context, role jimmnames.RoleTag) error {
// Remove all access to a group. I.e. user->group
if err := o.removeTuples(
ctx,
Tuple{
Relation: ofganames.AssigneeRelation,
Target: ofganames.ConvertTag(role),
},
); err != nil {
return errors.E(err)
}
// Next remove all access that a group had. I.e. group->model
// We need to loop through all resource types because the OpenFGA Read API does not provide
// means for only specifying a user resource, it must be paired with an object type.
for _, kind := range resourceTypes {
kt, err := ofganames.BlankKindTag(kind)
if err != nil {
return errors.E(err)
}
newTuple := Tuple{
Object: ofganames.ConvertTagWithRelation(role, ofganames.AssigneeRelation),
Target: kt,
}
err = o.removeTuples(ctx, newTuple)
if err != nil {
return errors.E(err)
}
}
return nil
}

// RemoveGroup removes a group.
func (o *OFGAClient) RemoveGroup(ctx context.Context, group jimmnames.GroupTag) error {
// Remove all access to a group. I.e. user->group
Expand Down
90 changes: 90 additions & 0 deletions internal/openfga/openfga_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,96 @@ func (s *openFGATestSuite) TestRemoveApplicationOffer(c *gc.C) {
c.Assert(allowed, gc.Equals, false)
}

func (s *openFGATestSuite) TestRemoveRoleWithDirectAccess(c *gc.C) {
ctx := context.Background()

user1 := ofganames.ConvertTag(names.NewUserTag("user1@canonical.com"))
role1 := ofganames.ConvertTag(jimmnames.NewRoleTag(uuid.NewString()))

tuples := []openfga.Tuple{
{
Object: user1,
Relation: ofganames.AssigneeRelation,
Target: role1,
},
}

err := s.ofgaClient.AddRelation(
context.Background(),
tuples...,
)
c.Assert(err, gc.IsNil)

err = s.ofgaClient.RemoveRole(ctx, jimmnames.NewRoleTag(role1.ID))
c.Assert(err, gc.IsNil)

allowed, err := s.ofgaClient.CheckRelation(
context.TODO(),
openfga.Tuple{
Object: user1,
Relation: ofganames.AssigneeRelation,
Target: role1,
},
false,
)
c.Assert(err, gc.Equals, nil)
c.Assert(allowed, gc.Equals, false)
}

func (s *openFGATestSuite) TestRemoveRoleWithAccessViaGroup(c *gc.C) {
ctx := context.Background()

user1 := ofganames.ConvertTag(names.NewUserTag("user1@canonical.com"))
group1 := jimmnames.NewGroupTag(uuid.NewString())
role1 := ofganames.ConvertTag(jimmnames.NewRoleTag(uuid.NewString()))

tuples := []openfga.Tuple{
{
Object: user1,
Relation: ofganames.MemberRelation,
Target: ofganames.ConvertTag(group1),
},
{
Object: ofganames.ConvertTagWithRelation(group1, ofganames.MemberRelation),
Relation: ofganames.AssigneeRelation,
Target: role1,
},
}

err := s.ofgaClient.AddRelation(
context.Background(),
tuples...,
)
c.Assert(err, gc.IsNil)

allowed, err := s.ofgaClient.CheckRelation(
context.TODO(),
openfga.Tuple{
Object: user1,
Relation: ofganames.AssigneeRelation,
Target: role1,
},
false,
)
c.Assert(err, gc.Equals, nil)
c.Assert(allowed, gc.Equals, true)

err = s.ofgaClient.RemoveRole(ctx, jimmnames.NewRoleTag(role1.ID))
c.Assert(err, gc.IsNil)

allowed, err = s.ofgaClient.CheckRelation(
context.TODO(),
openfga.Tuple{
Object: user1,
Relation: ofganames.AssigneeRelation,
Target: role1,
},
false,
)
c.Assert(err, gc.Equals, nil)
c.Assert(allowed, gc.Equals, false)
}

func (s *openFGATestSuite) TestRemoveGroup(c *gc.C) {
group1 := jimmnames.NewGroupTag(uuid.NewString())
group2 := jimmnames.NewGroupTag(uuid.NewString())
Expand Down

0 comments on commit 9e1c84d

Please sign in to comment.