-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Juju 7124/implement jimm role package (#1455)
* 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 * feat(rolemanager): implements RoleManager * feat(rolemanagertest): update rolemanager test jimmtest ref * chore(pr): cleanup interfaces and use concrete types --------- Co-authored-by: SimoneDutto <simone.dutto@canonical.com>
- Loading branch information
1 parent
8e8fef3
commit cb701b8
Showing
5 changed files
with
435 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// Copyright 2024 Canonical. | ||
|
||
package role | ||
|
||
// RoleManager is a type alias to export roleManager for use in tests. | ||
type RoleManager = roleManager |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
// Copyright 2024 Canonical. | ||
|
||
package role | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/juju/zaputil/zapctx" | ||
|
||
"github.com/canonical/jimm/v3/internal/common/pagination" | ||
"github.com/canonical/jimm/v3/internal/db" | ||
"github.com/canonical/jimm/v3/internal/dbmodel" | ||
"github.com/canonical/jimm/v3/internal/errors" | ||
"github.com/canonical/jimm/v3/internal/openfga" | ||
) | ||
|
||
// roleManager provides a means to manage roles within JIMM. | ||
type roleManager struct { | ||
store *db.Database | ||
authSvc *openfga.OFGAClient | ||
} | ||
|
||
// NewRoleManager returns a new RoleManager that persists the roles in the provided store. | ||
func NewRoleManager(store *db.Database, authSvc *openfga.OFGAClient) (*roleManager, error) { | ||
if store == nil { | ||
return nil, errors.E("role store cannot be nil") | ||
} | ||
if authSvc == nil { | ||
return nil, errors.E("role authorisation service cannot be nil") | ||
} | ||
return &roleManager{store, authSvc}, nil | ||
} | ||
|
||
// AddRole adds a role to JIMM. | ||
func (rm *roleManager) AddRole(ctx context.Context, user *openfga.User, roleName string) (*dbmodel.RoleEntry, error) { | ||
const op = errors.Op("roleManager.AddRole") | ||
zapctx.Debug(ctx, fmt.Sprintf("Running %s", op)) | ||
|
||
if !user.JimmAdmin { | ||
return nil, errors.E(op, errors.CodeUnauthorized, "unauthorized") | ||
} | ||
|
||
re, err := rm.store.AddRole(ctx, roleName) | ||
if err != nil { | ||
return nil, errors.E(op, err) | ||
} | ||
return re, nil | ||
} | ||
|
||
// GetRoleByUUID returns a role based on the provided UUID. | ||
func (rm *roleManager) GetRoleByUUID(ctx context.Context, user *openfga.User, uuid string) (*dbmodel.RoleEntry, error) { | ||
const op = errors.Op("roleManager.GetRoleByUUID") | ||
zapctx.Debug(ctx, fmt.Sprintf("Running %s", op)) | ||
|
||
return rm.getRole(ctx, user, &dbmodel.RoleEntry{UUID: uuid}) | ||
} | ||
|
||
// GetRoleByName returns a role based on the provided name. | ||
func (rm *roleManager) GetRoleByName(ctx context.Context, user *openfga.User, name string) (*dbmodel.RoleEntry, error) { | ||
const op = errors.Op("roleManager.GetRoleByName") | ||
zapctx.Debug(ctx, fmt.Sprintf("Running %s", op)) | ||
|
||
return rm.getRole(ctx, user, &dbmodel.RoleEntry{Name: name}) | ||
} | ||
|
||
// RemoveRole removes the role from JIMM in both the store and authorisation store. | ||
func (rm *roleManager) RemoveRole(ctx context.Context, user *openfga.User, roleName string) error { | ||
const op = errors.Op("roleManager.RemoveRole") | ||
zapctx.Debug(ctx, fmt.Sprintf("Running %s", op)) | ||
|
||
if !user.JimmAdmin { | ||
return errors.E(op, errors.CodeUnauthorized, "unauthorized") | ||
} | ||
|
||
re := &dbmodel.RoleEntry{ | ||
Name: roleName, | ||
} | ||
err := rm.store.GetRole(ctx, re) | ||
if err != nil { | ||
return errors.E(op, err) | ||
} | ||
|
||
// TODO(ale8k): | ||
// Would be nice to have a way to create a transaction to get, remove tuples, if successful, delete role | ||
// somehow. We could pass a callback and change the db methods? | ||
if err := rm.authSvc.RemoveRole(ctx, re.ResourceTag()); err != nil { | ||
return errors.E(op, err) | ||
} | ||
|
||
if err := rm.store.RemoveRole(ctx, re); err != nil { | ||
return errors.E(op, err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// RenameRole renames a role in JIMM's DB. | ||
func (rm *roleManager) RenameRole(ctx context.Context, user *openfga.User, uuid, newName string) error { | ||
const op = errors.Op("roleManager.RenameRole") | ||
zapctx.Debug(ctx, fmt.Sprintf("Running %s", op)) | ||
|
||
if !user.JimmAdmin { | ||
return errors.E(op, errors.CodeUnauthorized, "unauthorized") | ||
} | ||
|
||
err := rm.store.UpdateRoleName(ctx, uuid, newName) | ||
if err != nil { | ||
return errors.E(op, err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// ListRoles returns a list of roles known to JIMM. | ||
// `match` will filter the list fuzzy matching role's name or uuid. | ||
func (rm *roleManager) ListRoles(ctx context.Context, user *openfga.User, pagination pagination.LimitOffsetPagination, match string) ([]dbmodel.RoleEntry, error) { | ||
const op = errors.Op("roleManager.ListRoles") | ||
zapctx.Debug(ctx, fmt.Sprintf("Running %s", op)) | ||
|
||
if !user.JimmAdmin { | ||
return nil, errors.E(op, errors.CodeUnauthorized, "unauthorized") | ||
} | ||
|
||
res, err := rm.store.ListRoles(ctx, pagination.Limit(), pagination.Offset(), match) | ||
if err != nil { | ||
return nil, errors.E(op, err) | ||
} | ||
return res, nil | ||
} | ||
|
||
// CountRoles returns the number of roles that exist. | ||
func (rm *roleManager) CountRoles(ctx context.Context, user *openfga.User) (int, error) { | ||
const op = errors.Op("roleManager.CountRoles") | ||
zapctx.Debug(ctx, fmt.Sprintf("Running %s", op)) | ||
|
||
if !user.JimmAdmin { | ||
return 0, errors.E(op, errors.CodeUnauthorized, "unauthorized") | ||
} | ||
count, err := rm.store.CountRoles(ctx) | ||
if err != nil { | ||
return 0, errors.E(op, err) | ||
} | ||
return count, nil | ||
} | ||
|
||
// getRole returns a role based on the provided UUID or name. | ||
func (rm *roleManager) getRole(ctx context.Context, user *openfga.User, role *dbmodel.RoleEntry) (*dbmodel.RoleEntry, error) { | ||
const op = errors.Op("roleManager.getRole") | ||
zapctx.Debug(ctx, fmt.Sprintf("Running %s", op)) | ||
|
||
if !user.JimmAdmin { | ||
return nil, errors.E(op, errors.CodeUnauthorized, "unauthorized") | ||
} | ||
|
||
if err := rm.store.GetRole(ctx, role); err != nil { | ||
return nil, errors.E(op, err) | ||
} | ||
|
||
return role, nil | ||
} |
Oops, something went wrong.