Skip to content

Commit

Permalink
openapi3filter: add Unwrap() method to SecurityRequirementsError (#905
Browse files Browse the repository at this point in the history
)

* fix: add Unwrap method to SecurityRequirementsError

* openapi3filter: add Example for AuthenticationFunc

---------

Co-authored-by: Nicholas Jackson <[email protected]>
  • Loading branch information
nickajacks1 and Nicholas Jackson authored Feb 11, 2024
1 parent 55bc380 commit 439335c
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .github/docs/openapi3filter.txt
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,8 @@ type SecurityRequirementsError struct {

func (err *SecurityRequirementsError) Error() string

func (err SecurityRequirementsError) Unwrap() []error

type StatusCoder interface {
StatusCode() int
}
Expand Down
6 changes: 6 additions & 0 deletions openapi3filter/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,9 @@ func (err *SecurityRequirementsError) Error() string {

return buff.String()
}

var _ interface{ Unwrap() []error } = SecurityRequirementsError{}

func (err SecurityRequirementsError) Unwrap() []error {
return err.Errors
}
117 changes: 117 additions & 0 deletions openapi3filter/validate_request_example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package openapi3filter

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

"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/routers/gorillamux"
)

func ExampleAuthenticationFunc() {
const spec = `
openapi: 3.0.0
info:
title: 'Validator'
version: 0.0.1
components:
securitySchemes:
OAuth2:
type: oauth2
flows:
clientCredentials:
tokenUrl: /oauth2/token
scopes:
secrets.read: Ability to read secrets
secrets.write: Ability to write secrets
paths:
/secret:
post:
security:
- OAuth2:
- secrets.write
responses:
'200':
description: Ok
'401':
description: Unauthorized
`
var (
errUnauthenticated = errors.New("login required")
errForbidden = errors.New("permission denied")
)

userScopes := map[string][]string{
"Alice": {"secrets.read"},
"Bob": {"secrets.read", "secrets.write"},
}

authenticationFunc := func(_ context.Context, ai *AuthenticationInput) error {
user := ai.RequestValidationInput.Request.Header.Get("X-User")
if user == "" {
return errUnauthenticated
}

for _, requiredScope := range ai.Scopes {
var allowed bool
for _, scope := range userScopes[user] {
if scope == requiredScope {
allowed = true
break
}
}
if !allowed {
return errForbidden
}
}

return nil
}

loader := openapi3.NewLoader()
doc, _ := loader.LoadFromData([]byte(spec))
router, _ := gorillamux.NewRouter(doc)

validateRequest := func(req *http.Request) {
route, pathParams, _ := router.FindRoute(req)

validationInput := &RequestValidationInput{
Request: req,
PathParams: pathParams,
Route: route,
Options: &Options{
AuthenticationFunc: authenticationFunc,
},
}
err := ValidateRequest(context.TODO(), validationInput)
switch {
case errors.Is(err, errUnauthenticated):
fmt.Println("username is required")
case errors.Is(err, errForbidden):
fmt.Println("user is not allowed to perform this action")
case err == nil:
fmt.Println("ok")
default:
log.Fatal(err)
}
}

req1, _ := http.NewRequest(http.MethodPost, "/secret", nil)
req1.Header.Set("X-User", "Alice")

req2, _ := http.NewRequest(http.MethodPost, "/secret", nil)
req2.Header.Set("X-User", "Bob")

req3, _ := http.NewRequest(http.MethodPost, "/secret", nil)

validateRequest(req1)
validateRequest(req2)
validateRequest(req3)
// output:
// user is not allowed to perform this action
// ok
// username is required
}

0 comments on commit 439335c

Please sign in to comment.