From 82e0bd63bef8d92b7f49f6dbaf024126b6171393 Mon Sep 17 00:00:00 2001 From: Mike Mason Date: Mon, 24 Jul 2023 19:21:51 +0000 Subject: [PATCH] add endpoint to discover the resource a role is assigned to --- internal/api/roles.go | 28 ++++++++++++++++++++++++++++ internal/api/router.go | 1 + internal/api/types.go | 4 ++++ internal/query/mock/mock.go | 5 +++++ internal/query/relations.go | 31 +++++++++++++++++++++++++++++++ internal/query/service.go | 1 + 6 files changed, 70 insertions(+) diff --git a/internal/api/roles.go b/internal/api/roles.go index 089a2b200..9893a43a5 100644 --- a/internal/api/roles.go +++ b/internal/api/roles.go @@ -138,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) +} diff --git a/internal/api/router.go b/internal/api/router.go index cb337032f..458071bd7 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -51,6 +51,7 @@ func (r *Router) Routes(rg *echo.Group) { 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) diff --git a/internal/api/types.go b/internal/api/types.go index 5db354c5e..dbab6e9ad 100644 --- a/internal/api/types.go +++ b/internal/api/types.go @@ -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"` } diff --git a/internal/query/mock/mock.go b/internal/query/mock/mock.go index d7d4bad9c..5105f0770 100644 --- a/internal/query/mock/mock.go +++ b/internal/query/mock/mock.go @@ -62,6 +62,11 @@ func (e *Engine) GetRole(ctx context.Context, roleResource types.Resource, query 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 diff --git a/internal/query/relations.go b/internal/query/relations.go index 65431adee..8d6f2313c 100644 --- a/internal/query/relations.go +++ b/internal/query/relations.go @@ -610,6 +610,37 @@ func (e *engine) GetRole(ctx context.Context, roleResource types.Resource, query 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()) + } + + // 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 ( diff --git a/internal/query/service.go b/internal/query/service.go index 38a01043a..c984b65ab 100644 --- a/internal/query/service.go +++ b/internal/query/service.go @@ -18,6 +18,7 @@ type Engine interface { 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)