Skip to content

Commit

Permalink
Fix client code and use resource-level authorization (#75)
Browse files Browse the repository at this point in the history
* Fix client code and use resource-level authorization

The provided client package was not using the correct authorization
HTTP resource. This commit fixes that, and also removes tenant as a
query param because we no longer need it to make authorization
decisions.

Signed-off-by: John Schaeffer <[email protected]>

* Update pkg/client/v1/auth.go

Co-authored-by: Mike Mason <[email protected]>
Signed-off-by: John Schaeffer <[email protected]>

* Update pkg/client/v1/auth.go

Co-authored-by: Mike Mason <[email protected]>
Signed-off-by: John Schaeffer <[email protected]>

* remove unused path import

Signed-off-by: Mike Mason <[email protected]>

---------

Signed-off-by: John Schaeffer <[email protected]>
Signed-off-by: John Schaeffer <[email protected]>
Signed-off-by: Mike Mason <[email protected]>
Co-authored-by: Mike Mason <[email protected]>
Co-authored-by: Mike Mason <[email protected]>
  • Loading branch information
3 people authored Apr 21, 2023
1 parent ea03a0b commit 6ee45c4
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 35 deletions.
38 changes: 11 additions & 27 deletions internal/api/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import (
"go.infratographer.com/x/urnx"
)

// checkAction will check if a subject is allowed to perform an action on a resource
// scoped to the tenant.
// checkAction will check if a subject is allowed to perform an action on a resource.
// This is the permissions check endpoint.
// It will return a 200 if the subject is allowed to perform the action on the resource.
// It will return a 403 if the subject is not allowed to perform the action on the resource.
Expand All @@ -20,49 +19,34 @@ import (
// contain the subject of the request in the "sub" claim.
//
// The following query parameters are required:
// - tenant: the tenant URN to check
// - action: the action to check
//
// The following query parameters are optional:
// - resource: the resource URN to check
// - action: the action to check
func (r *Router) checkAction(c echo.Context) error {
ctx, span := tracer.Start(c.Request().Context(), "api.checkAction")
defer span.End()

// Get the query parameters. These are mandatory.
tenantURNStr, hasQuery := getParam(c, "tenant")
if !hasQuery {
return echo.NewHTTPError(http.StatusBadRequest, "missing tenant query parameter")
}

action, hasQuery := getParam(c, "action")
if !hasQuery {
return echo.NewHTTPError(http.StatusBadRequest, "missing action query parameter")
}

// Optional query parameters
resourceURNStr, hasResourceParam := getParam(c, "resource")
if !hasResourceParam {
return echo.NewHTTPError(http.StatusBadRequest, "missing resource query parameter")
}

// Query parameter validation
// Note that we currently only check the tenant as a scope. The
// resource is not checked as of yet.
tenantURN, err := urnx.Parse(tenantURNStr)
resourceURN, err := urnx.Parse(resourceURNStr)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "error processing tenant URN").SetInternal(err)
return echo.NewHTTPError(http.StatusBadRequest, "error processing resource URN").SetInternal(err)
}

tenantResource, err := r.engine.NewResourceFromURN(tenantURN)
resource, err := r.engine.NewResourceFromURN(resourceURN)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "error processing tenant resource URN").SetInternal(err)
}

if hasResourceParam {
_, err := urnx.Parse(resourceURNStr)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "error processing resource URN").SetInternal(err)
}
}

// Subject validation
subject, err := currentSubject(c)
if err != nil {
Expand All @@ -75,10 +59,10 @@ func (r *Router) checkAction(c echo.Context) error {
}

// Check the permissions
err = r.engine.SubjectHasPermission(ctx, subjectResource, action, tenantResource, "")
err = r.engine.SubjectHasPermission(ctx, subjectResource, action, resource, "")
if err != nil && errors.Is(err, query.ErrActionNotAssigned) {
msg := fmt.Sprintf("subject '%s' does not have permission to perform action '%s' on resource '%s' scoped on tenant '%s'",
subject, action, resourceURNStr, tenantURNStr)
msg := fmt.Sprintf("subject '%s' does not have permission to perform action '%s' on resource '%s'",
subject, action, resourceURNStr)

return echo.NewHTTPError(http.StatusForbidden, msg).SetInternal(err)
} else if err != nil {
Expand Down
23 changes: 15 additions & 8 deletions pkg/client/v1/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"fmt"
"net/http"
"net/url"
"path"
"strings"

"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
Expand Down Expand Up @@ -59,14 +58,18 @@ func New(url string, doerClient Doer) (*Client, error) {
}

// Allowed checks if the client subject is permitted exec the action on the resource
func (c *Client) Allowed(ctx context.Context, action string, resourceURNPrefix string) (bool, error) {
func (c *Client) Allowed(ctx context.Context, action string, resourceURN string) (bool, error) {
ctx, span := tracer.Start(ctx, "SubjectHasAction", trace.WithAttributes(
attribute.String("action", action),
attribute.String("resource", resourceURNPrefix),
attribute.String("resource", resourceURN),
))
defer span.End()

err := c.get(ctx, fmt.Sprintf("/has/%s/on/%s", action, resourceURNPrefix), map[string]string{})
values := url.Values{}
values.Add("action", action)
values.Add("resource", resourceURN)

err := c.get(ctx, "/allow", values, map[string]string{})
if err != nil {
if errors.Is(err, ErrPermissionDenied) {
return false, nil
Expand All @@ -85,22 +88,26 @@ type ServerResponse struct {
StatusCode int
}

func (c Client) get(ctx context.Context, endpoint string, resp interface{}) error {
request, err := newGetRequest(ctx, c.url, endpoint)
func (c Client) get(ctx context.Context, endpoint string, query url.Values, resp interface{}) error {
request, err := newGetRequest(ctx, c.url, endpoint, query)
if err != nil {
return err
}

return c.do(request, &resp)
}

func newGetRequest(ctx context.Context, uri, endpoint string) (*http.Request, error) {
func newGetRequest(ctx context.Context, uri, endpoint string, query url.Values) (*http.Request, error) {
u, err := url.Parse(uri)
if err != nil {
return nil, err
}

u.Path = path.Join(apiVersion, endpoint)
u = u.JoinPath(apiVersion, endpoint)

if len(query) > 0 {
u.RawQuery = query.Encode()
}

return http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
}
Expand Down

0 comments on commit 6ee45c4

Please sign in to comment.