Skip to content

Commit

Permalink
Initial proposal for /allow endpoint
Browse files Browse the repository at this point in the history
This creates an `/allow` endpoint which does the actual permission
check.

It takes three mandatory query parameters:

* `tenant`: The tenant URN
* `resource`: The resource URN
* `action`: The action identifier tag

It also adds a simple OpenAPI v3 spec document.

The intent is to have an easy, general and fairly opinionated endpoint
to do permission checks on. This can be taken programmatically without
much logic in... say... an API Gateway, to do the needed checks without
adding much logic other than gathering the mandatory parameters.

Signed-off-by: Juan Antonio Osorio <[email protected]>
  • Loading branch information
JAORMX committed Apr 11, 2023
1 parent 71af0a4 commit 2fa58e0
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 13 deletions.
66 changes: 54 additions & 12 deletions internal/api/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,72 @@ package api

import (
"errors"
"fmt"
"net/http"

"github.com/gin-gonic/gin"
"go.infratographer.com/permissions-api/internal/query"
"go.infratographer.com/x/urnx"
)

// checkAction will check if a subject is allowed to perform an action on a resource
// scoped to the tenant.
// 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.
//
// Note that this expects a JWT token to be present in the request. This token must
// contain the subject of the request in the "sub" claim.
//
// The following query parameters are required:
// - resource: the resource URN to check
// - tenant: the tenant URN to check
// - action: the action to check
func (r *Router) checkAction(c *gin.Context) {
resourceURNStr := c.Param("urn")
action := c.Param("action")

ctx, span := tracer.Start(c.Request.Context(), "api.checkAction")
defer span.End()

resourceURN, err := urnx.Parse(resourceURNStr)
// Get the query parameters. These are mandatory.
resourceURNStr, hasQuery := c.GetQuery("resource")
if !hasQuery {
c.JSON(http.StatusBadRequest, gin.H{"message": "missing resource query parameter"})
return
}

tenantURNStr, hasQuery := c.GetQuery("tenant")
if !hasQuery {
c.JSON(http.StatusBadRequest, gin.H{"message": "missing tenant query parameter"})
return
}

action, hasQuery := c.GetQuery("action")
if !hasQuery {
c.JSON(http.StatusBadRequest, gin.H{"message": "missing action query parameter"})
return
}

// Query parameter validation
// Note that we currently only check the tenant as a scope. The
// resource is not checked as of yet.
_, err := urnx.Parse(resourceURNStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": "error processing resource URN", "error": err.Error()})
return
}

resource, err := r.engine.NewResourceFromURN(resourceURN)
tenantURN, err := urnx.Parse(tenantURNStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": "error processing resource URN", "error": err.Error()})
c.JSON(http.StatusBadRequest, gin.H{"message": "error processing tenant URN", "error": err.Error()})
return
}

tenantResource, err := r.engine.NewResourceFromURN(tenantURN)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": "error processing tenant resource URN", "error": err.Error()})
return
}

// Subject validation
subject, err := currentSubject(c)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error(), "message": "failed to get the subject"})
Expand All @@ -40,13 +80,15 @@ func (r *Router) checkAction(c *gin.Context) {
return
}

err = r.engine.SubjectHasPermission(ctx, subjectResource, action, resource, "")
if err != nil {
if errors.Is(err, query.ErrActionNotAssigned) {
c.JSON(http.StatusForbidden, gin.H{"message": "subject does not have requested action"})
return
}
// Check the permissions
err = r.engine.SubjectHasPermission(ctx, subjectResource, action, tenantResource, "")
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)
c.JSON(http.StatusForbidden, gin.H{"message": msg})

return
} else if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": "an error occurred checking permissions", "error": err.Error()})

return
Expand Down
3 changes: 2 additions & 1 deletion internal/api/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ func (r *Router) Routes(rg *gin.RouterGroup) {
v1.POST("/roles/:role_id/assignments", r.assignmentCreate)
v1.GET("/roles/:role_id/assignments", r.assignmentsList)

v1.GET("/has/:action/on/:urn", r.checkAction)
// /allow is the permissions check endpoint
v1.GET("/allow", r.checkAction)
}
}

Expand Down
72 changes: 72 additions & 0 deletions openapi-v1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
openapi: "3.1.0"
info:
version: 0.0.1
title: Permissions API
description: Permissions API is an API to manage permissions for infratographer.
contact:
name: Infratographer Authors
url: https://github.com/infratographer
license:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0.html
#servers:
# - url: http://localhost/api/v1
paths:
/allow:
get:
description:
operationId: allow
parameters:
- $ref: '#/components/parameters/tenantParam'
- $ref: '#/components/parameters/resourceParam'
- $ref: '#/components/parameters/actionParam'
responses:
'200':
description: allow response
content:
application/json:
schema:
type: object
'403':
description: forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
default:
description: unexpected error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'

components:
schemas:
Error:
type: object
required:
- message
properties:
message:
type: string

parameters:
tenantParam:
in: query
name: tenant
required: true
schema:
type: string
resourceParam:
in: query
name: resource
required: true
schema:
type: string
actionParam:
in: query
name: action
required: true
schema:
type: string

0 comments on commit 2fa58e0

Please sign in to comment.