Skip to content

Commit

Permalink
add patch identity entitlements
Browse files Browse the repository at this point in the history
  • Loading branch information
SimoneDutto committed Aug 26, 2024
1 parent b4fc265 commit 743ff12
Show file tree
Hide file tree
Showing 2 changed files with 180 additions and 6 deletions.
58 changes: 54 additions & 4 deletions internal/rebac_admin/identities.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import (
"github.com/canonical/jimm/v3/internal/common/pagination"
"github.com/canonical/jimm/v3/internal/jujuapi"
"github.com/canonical/jimm/v3/internal/openfga"
"github.com/canonical/jimm/v3/internal/openfga/names"
ofganames "github.com/canonical/jimm/v3/internal/openfga/names"
"github.com/canonical/jimm/v3/internal/rebac_admin/utils"
apiparams "github.com/canonical/jimm/v3/pkg/api/params"
"github.com/juju/names/v5"

v1 "github.com/canonical/rebac-admin-ui-handlers/v1"
"github.com/canonical/rebac-admin-ui-handlers/v1/resources"
Expand Down Expand Up @@ -112,7 +113,7 @@ func (s *identitiesService) GetIdentityGroups(ctx context.Context, identityId st
filter := utils.CreateTokenPaginationFilter(params.Size, params.NextToken, params.NextPageToken)
tuples, cNextToken, err := s.jimm.ListRelationshipTuples(ctx, user, apiparams.RelationshipTuple{
Object: objUser.ResourceTag().String(),
Relation: names.MemberRelation.String(),
Relation: ofganames.MemberRelation.String(),
TargetObject: openfga.GroupType.String(),
}, int32(filter.Limit()), filter.Token())

Expand Down Expand Up @@ -155,7 +156,7 @@ func (s *identitiesService) PatchIdentityGroups(ctx context.Context, identityId
for _, p := range groupPatches {
t := apiparams.RelationshipTuple{
Object: objUser.ResourceTag().String(),
Relation: names.MemberRelation.String(),
Relation: ofganames.MemberRelation.String(),
TargetObject: p.Group,
}
if p.Op == "add" {
Expand Down Expand Up @@ -214,5 +215,54 @@ func (s *identitiesService) GetIdentityEntitlements(ctx context.Context, identit

// PatchIdentityEntitlements performs addition or removal of an Entitlement to/from an Identity.
func (s *identitiesService) PatchIdentityEntitlements(ctx context.Context, identityId string, entitlementPatches []resources.IdentityEntitlementsPatchItem) (bool, error) {
return false, v1.NewNotImplementedError("get identity roles not implemented")
user, err := utils.GetUserFromContext(ctx)
if err != nil {
return false, err
}
objUser, err := s.jimm.FetchIdentity(ctx, identityId)
if err != nil {
return false, v1.NewNotFoundError(fmt.Sprintf("User with id %s not found", identityId))
}
var toAdd []apiparams.RelationshipTuple
var toRemove []apiparams.RelationshipTuple
var errList utils.MultiErr
toTargetTag := func(entitlementPatch resources.IdentityEntitlementsPatchItem) (names.Tag, error) {
return utils.ValidateDecomposedTag(
entitlementPatch.Entitlement.EntityType,
entitlementPatch.Entitlement.EntityId,
)
}
for _, entitlementPatch := range entitlementPatches {
tag, err := toTargetTag(entitlementPatch)
if err != nil {
errList.AppendError(err)
continue
}
t := apiparams.RelationshipTuple{
Object: objUser.Tag().String(),
Relation: entitlementPatch.Entitlement.Entitlement,
TargetObject: tag.String(),
}
if entitlementPatch.Op == resources.IdentityEntitlementsPatchItemOpAdd {
toAdd = append(toAdd, t)
} else {
toRemove = append(toRemove, t)
}
}
if err := errList.Error(); err != nil {
return false, err
}
if toAdd != nil {
err := s.jimm.AddRelation(ctx, user, toAdd)
if err != nil {
return false, err
}
}
if toRemove != nil {
err := s.jimm.RemoveRelation(ctx, user, toRemove)
if err != nil {
return false, err
}
}
return true, nil
}
128 changes: 126 additions & 2 deletions internal/rebac_admin/identities_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

rebac_handlers "github.com/canonical/rebac-admin-ui-handlers/v1"
"github.com/canonical/rebac-admin-ui-handlers/v1/resources"
"github.com/juju/names/v5"
gc "gopkg.in/check.v1"

"github.com/canonical/jimm/v3/internal/jimmtest"
Expand All @@ -14,7 +15,6 @@ import (
"github.com/canonical/jimm/v3/internal/rebac_admin"
"github.com/canonical/jimm/v3/pkg/api/params"
jimmnames "github.com/canonical/jimm/v3/pkg/names"
"github.com/juju/names/v5"
)

type identitiesSuite struct {
Expand Down Expand Up @@ -113,7 +113,7 @@ func (s *identitiesSuite) TestIdentityGetGroups(c *gc.C) {
// TestIdentityEntitlements tests the listing of entitlements for a specific identityId.
// Setup: add controllers, models to a user and add the user to a group.
func (s *identitiesSuite) TestIdentityEntitlements(c *gc.C) {
// add user to 3 controllers and 3 models
// initialization
ctx := context.Background()
identitySvc := rebac_admin.NewidentitiesService(s.JIMM)
groupTag := s.AddGroup(c, "test-group")
Expand Down Expand Up @@ -143,6 +143,7 @@ func (s *identitiesSuite) TestIdentityEntitlements(c *gc.C) {
err = s.JIMM.OpenFGAClient.AddRelation(ctx, tuples...)
c.Assert(err, gc.IsNil)

// test
ctx = rebac_handlers.ContextWithIdentity(ctx, s.AdminUser)
emptyPageToken := ""
req := resources.GetIdentitiesItemEntitlementsParams{NextPageToken: &emptyPageToken}
Expand Down Expand Up @@ -185,3 +186,126 @@ func (s *identitiesSuite) TestIdentityEntitlements(c *gc.C) {
c.Assert(controllerEntitlementCount, gc.Equals, 3)
c.Assert(groupEntitlementCount, gc.Equals, 1)
}

// patchIdentitiesEntitlementTestEnv is used to create entries in JIMM's database.
// The rebacAdminSuite does not spin up a Juju controller so we cannot use
// regular JIMM methods to create resources. It is also necessary to have resources
// present in the database in order for ListRelationshipTuples to work correctly.
const patchIdentitiesEntitlementTestEnv = `clouds:
- name: test-cloud
type: test-provider
regions:
- name: test-cloud-region
cloud-credentials:
- owner: [email protected]
name: cred-1
cloud: test-cloud
controllers:
- name: controller-1
uuid: 00000001-0000-0000-0000-000000000001
cloud: test-cloud
region: test-cloud-region
models:
- name: model-1
uuid: 00000002-0000-0000-0000-000000000001
controller: controller-1
cloud: test-cloud
region: test-cloud-region
cloud-credential: cred-1
owner: [email protected]
- name: model-2
uuid: 00000002-0000-0000-0000-000000000002
controller: controller-1
cloud: test-cloud
region: test-cloud-region
cloud-credential: cred-1
owner: [email protected]
- name: model-3
uuid: 00000003-0000-0000-0000-000000000003
controller: controller-1
cloud: test-cloud
region: test-cloud-region
cloud-credential: cred-1
owner: [email protected]
- name: model-4
uuid: 00000004-0000-0000-0000-000000000004
controller: controller-1
cloud: test-cloud
region: test-cloud-region
cloud-credential: cred-1
owner: [email protected]
`

// TestPatchIdentityEntitlements tests the patching of entitlements for a specific identityId,
// adding and removing relations after the setup.
// Setup: add user to a group, and add models to the user.
func (s *identitiesSuite) TestPatchIdentityEntitlements(c *gc.C) {
// initialization
ctx := context.Background()
identitySvc := rebac_admin.NewidentitiesService(s.JIMM)
tester := jimmtest.GocheckTester{C: c}
env := jimmtest.ParseEnvironment(tester, patchGroupEntitlementTestEnv)
env.PopulateDB(tester, s.JIMM.Database)
oldModels := []string{env.Models[0].UUID, env.Models[1].UUID}
newModels := []string{env.Models[2].UUID, env.Models[3].UUID}
user := names.NewUserTag("[email protected]")
s.AddUser(c, user.Id())
tuple := openfga.Tuple{
Object: ofganames.ConvertTag(user),
Relation: ofganames.AdministratorRelation,
}

var tuples []openfga.Tuple
for i := range 2 {
t := tuple
t.Target = ofganames.ConvertTag(names.NewModelTag(oldModels[i]))
tuples = append(tuples, t)
}
err := s.JIMM.OpenFGAClient.AddRelation(ctx, tuples...)
c.Assert(err, gc.IsNil)
allowed, err := s.JIMM.OpenFGAClient.CheckRelation(ctx, tuples[0], false)
c.Assert(err, gc.IsNil)
c.Assert(allowed, gc.Equals, true)
// Above we have added granted the user with administrator permission to 2 models.
// Below, we will request those 2 relations to be removed and add 2 different relations.

entitlementPatches := []resources.IdentityEntitlementsPatchItem{
{Entitlement: resources.EntityEntitlement{
Entitlement: ofganames.AdministratorRelation.String(),
EntityId: newModels[0],
EntityType: openfga.ModelType.String(),
}, Op: resources.IdentityEntitlementsPatchItemOpAdd},
{Entitlement: resources.EntityEntitlement{
Entitlement: ofganames.AdministratorRelation.String(),
EntityId: newModels[1],
EntityType: openfga.ModelType.String(),
}, Op: resources.IdentityEntitlementsPatchItemOpAdd},
{Entitlement: resources.EntityEntitlement{
Entitlement: ofganames.AdministratorRelation.String(),
EntityId: oldModels[0],
EntityType: openfga.ModelType.String(),
}, Op: resources.IdentityEntitlementsPatchItemOpRemove},
{Entitlement: resources.EntityEntitlement{
Entitlement: ofganames.AdministratorRelation.String(),
EntityId: oldModels[1],
EntityType: openfga.ModelType.String(),
}, Op: resources.IdentityEntitlementsPatchItemOpRemove},
}
ctx = rebac_handlers.ContextWithIdentity(ctx, s.AdminUser)
res, err := identitySvc.PatchIdentityEntitlements(ctx, user.Id(), entitlementPatches)
c.Assert(err, gc.IsNil)
c.Assert(res, gc.Equals, true)

for i := range 2 {
exists, err := s.JIMM.OpenFGAClient.CheckRelation(ctx, tuples[i], false)
c.Assert(err, gc.IsNil)
c.Assert(exists, gc.Equals, false)
}
for i := range 2 {
newTuple := tuples[0]
newTuple.Target = ofganames.ConvertTag(names.NewModelTag(newModels[i]))
allowed, err = s.JIMM.OpenFGAClient.CheckRelation(ctx, newTuple, false)
c.Assert(err, gc.IsNil)
c.Assert(allowed, gc.Equals, true)
}
}

0 comments on commit 743ff12

Please sign in to comment.