Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get role resources #147

Merged
merged 3 commits into from
Jul 24, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 58 additions & 1 deletion internal/api/roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,39 @@ func (r *Router) roleCreate(c echo.Context) error {
return c.JSON(http.StatusCreated, resp)
}

func (r *Router) roleGet(c echo.Context) error {
roleIDStr := c.Param("role_id")

ctx, span := tracer.Start(c.Request().Context(), "api.roleGet", trace.WithAttributes(attribute.String("id", roleIDStr)))
defer span.End()

roleResourceID, err := gidx.Parse(roleIDStr)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "error getting resource").SetInternal(err)
}

roleResource, err := r.engine.NewResourceFromID(roleResourceID)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "error getting resource").SetInternal(err)
}

role, err := r.engine.GetRole(ctx, roleResource, "")
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "error getting resource").SetInternal(err)
}

resp := roleResponse{
ID: role.ID,
Actions: role.Actions,
}

return c.JSON(http.StatusOK, resp)
}

func (r *Router) rolesList(c echo.Context) error {
resourceIDStr := c.Param("id")

ctx, span := tracer.Start(c.Request().Context(), "api.roleGet", trace.WithAttributes(attribute.String("id", resourceIDStr)))
ctx, span := tracer.Start(c.Request().Context(), "api.rolesList", trace.WithAttributes(attribute.String("id", resourceIDStr)))
defer span.End()

resourceID, err := gidx.Parse(resourceIDStr)
Expand Down Expand Up @@ -109,3 +138,31 @@ func (r *Router) roleDelete(c echo.Context) error {

return c.JSON(http.StatusOK, resp)
}

func (r *Router) roleGetResource(c echo.Context) error {
roleIDStr := c.Param("role_id")

ctx, span := tracer.Start(c.Request().Context(), "api.roleGetResource", trace.WithAttributes(attribute.String("id", roleIDStr)))
defer span.End()

roleResourceID, err := gidx.Parse(roleIDStr)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "error getting resource").SetInternal(err)
}

roleResource, err := r.engine.NewResourceFromID(roleResourceID)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "error getting resource").SetInternal(err)
}

resource, err := r.engine.GetRoleResource(ctx, roleResource, "")
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "error getting resource").SetInternal(err)
}

resp := resourceResponse{
ID: resource.ID,
}

return c.JSON(http.StatusOK, resp)
}
2 changes: 2 additions & 0 deletions internal/api/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ func (r *Router) Routes(rg *echo.Group) {
v1.GET("/resources/:id/relationships", r.relationshipListFrom)
v1.GET("/relationships/from/:id", r.relationshipListFrom)
v1.GET("/relationships/to/:id", r.relationshipListTo)
v1.GET("/roles/:role_id", r.roleGet)
v1.DELETE("/roles/:id", r.roleDelete)
v1.GET("/roles/:role_id/resource", r.roleGetResource)
v1.POST("/roles/:role_id/assignments", r.assignmentCreate)
v1.DELETE("/roles/:role_id/assignments", r.assignmentDelete)
v1.GET("/roles/:role_id/assignments", r.assignmentsList)
Expand Down
4 changes: 4 additions & 0 deletions internal/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ type roleResponse struct {
Actions []string `json:"actions"`
}

type resourceResponse struct {
ID gidx.PrefixedID `json:"id"`
}

type deleteRoleResponse struct {
Success bool `json:"success"`
}
Expand Down
10 changes: 10 additions & 0 deletions internal/query/mock/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ func (e *Engine) CreateRole(ctx context.Context, res types.Resource, actions []s
return role, "", nil
}

// GetRole returns nothing but satisfies the Engine interface.
func (e *Engine) GetRole(ctx context.Context, roleResource types.Resource, queryToken string) (types.Role, error) {
return types.Role{}, nil
}

// GetRoleResource returns nothing but satisfies the Engine interface.
func (e *Engine) GetRoleResource(ctx context.Context, roleResource types.Resource, queryToken string) (types.Resource, error) {
return types.Resource{}, nil
}

// ListAssignments returns nothing but satisfies the Engine interface.
func (e *Engine) ListAssignments(ctx context.Context, role types.Role, queryToken string) ([]types.Resource, error) {
return nil, nil
Expand Down
69 changes: 69 additions & 0 deletions internal/query/relations.go
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,75 @@ func (e *engine) listRoleResourceActions(ctx context.Context, role types.Resourc
return resourceActions, nil
}

// GetRole gets the role with it's actions.
func (e *engine) GetRole(ctx context.Context, roleResource types.Resource, queryToken string) (types.Role, error) {
var (
resActions map[types.Resource][]string
err error
)

for _, resType := range e.schemaRoleables {
resActions, err = e.listRoleResourceActions(ctx, roleResource, resType.Name, queryToken)
if err != nil {
return types.Role{}, err
}

// roles are only ever created for a single resource, so we can break after the first one is found.
if len(resActions) != 0 {
break
}
}

if len(resActions) > 1 {
e.logger.Warnw("role is assigned to more than one resource", "role.id", roleResource.ID.String())
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO it'd probably be better for us to return an error here, since this is something that shouldn't happen.


// returns the first resources actions.
for _, actions := range resActions {
for i, action := range actions {
actions[i] = relationToAction(action)
}

return types.Role{
ID: roleResource.ID,
Actions: actions,
}, nil
}

return types.Role{}, ErrRoleNotFound
}

// GetRoleResource gets the role's assigned resource.
func (e *engine) GetRoleResource(ctx context.Context, roleResource types.Resource, queryToken string) (types.Resource, error) {
var (
resActions map[types.Resource][]string
err error
)

for _, resType := range e.schemaRoleables {
resActions, err = e.listRoleResourceActions(ctx, roleResource, resType.Name, queryToken)
if err != nil {
return types.Resource{}, err
}

// roles are only ever created for a single resource, so we can break after the first one is found.
if len(resActions) != 0 {
break
}
}

if len(resActions) > 1 {
e.logger.Warnw("role is assigned to more than one resource", "role.id", roleResource.ID.String())
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.


// returns the first resources actions.
for resource := range resActions {
return resource, nil
}

return types.Resource{}, ErrRoleNotFound
}

// DeleteRole removes all role actions from the assigned resource.
func (e *engine) DeleteRole(ctx context.Context, roleResource types.Resource, queryToken string) (string, error) {
var (
Expand Down
55 changes: 54 additions & 1 deletion internal/query/relations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func cleanDB(ctx context.Context, t *testing.T, client *authzed.Client, namespac
}
}

func TestRoles(t *testing.T) {
func TestCreateRoles(t *testing.T) {
namespace := "testroles"
ctx := context.Background()
e := testEngine(ctx, t, namespace)
Expand Down Expand Up @@ -141,6 +141,59 @@ func TestRoles(t *testing.T) {
testingx.RunTests(ctx, t, testCases, testFn)
}

func TestGetRoles(t *testing.T) {
namespace := "testroles"
ctx := context.Background()
e := testEngine(ctx, t, namespace)
tenID, err := gidx.NewID("tnntten")
require.NoError(t, err)
tenRes, err := e.NewResourceFromID(tenID)
require.NoError(t, err)

role, queryToken, err := e.CreateRole(ctx, tenRes, []string{"loadbalancer_get"})
require.NoError(t, err)
roleRes, err := e.NewResourceFromID(role.ID)
require.NoError(t, err)

missingRes, err := e.NewResourceFromID(gidx.PrefixedID("permrol-notfound"))
require.NoError(t, err)

testCases := []testingx.TestCase[types.Resource, types.Role]{
{
Name: "GetRoleNotFound",
Input: missingRes,
CheckFn: func(ctx context.Context, t *testing.T, res testingx.TestResult[types.Role]) {
assert.ErrorIs(t, res.Err, ErrRoleNotFound)
},
},
{
Name: "GetSuccess",
Input: roleRes,
CheckFn: func(ctx context.Context, t *testing.T, res testingx.TestResult[types.Role]) {
expActions := []string{
"loadbalancer_get",
}

assert.NoError(t, res.Err)
require.NotEmpty(t, res.Success.ID)

assert.Equal(t, expActions, res.Success.Actions)
},
},
}

testFn := func(ctx context.Context, roleResource types.Resource) testingx.TestResult[types.Role] {
roles, err := e.GetRole(ctx, roleResource, queryToken)

return testingx.TestResult[types.Role]{
Success: roles,
Err: err,
}
}

testingx.RunTests(ctx, t, testCases, testFn)
}

func TestRoleDelete(t *testing.T) {
namespace := "testroles"
ctx := context.Background()
Expand Down
2 changes: 2 additions & 0 deletions internal/query/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ type Engine interface {
UnassignSubjectRole(ctx context.Context, subject types.Resource, role types.Role) (string, error)
CreateRelationships(ctx context.Context, rels []types.Relationship) (string, error)
CreateRole(ctx context.Context, res types.Resource, actions []string) (types.Role, string, error)
GetRole(ctx context.Context, roleResource types.Resource, queryToken string) (types.Role, error)
GetRoleResource(ctx context.Context, roleResource types.Resource, queryToken string) (types.Resource, error)
ListAssignments(ctx context.Context, role types.Role, queryToken string) ([]types.Resource, error)
ListRelationshipsFrom(ctx context.Context, resource types.Resource, queryToken string) ([]types.Relationship, error)
ListRelationshipsTo(ctx context.Context, resource types.Resource, queryToken string) ([]types.Relationship, error)
Expand Down