feature: authentication middlewares #20
|
@ -0,0 +1,62 @@
|
|||
package api
|
||||
|
||||
// Auth can be used by http middleware to
|
||||
// 1) consult required roles in @Auth.Required
|
||||
// 2) update active roles in @Auth.Active
|
||||
type Auth struct {
|
||||
// required roles for this request
|
||||
// - the first dimension of the array reads as a OR
|
||||
// - the second dimension reads as a AND
|
||||
//
|
||||
// Example:
|
||||
// [ [A, B], [C, D] ] reads: roles (A and B) or (C and D) are required
|
||||
//
|
||||
// Warning: must not be mutated
|
||||
Required [][]string
|
||||
|
||||
// active roles to be updated by authentication
|
||||
// procedures (e.g. jwt)
|
||||
Active []string
|
||||
}
|
||||
|
||||
// Granted returns whether the authorization is granted
|
||||
// i.e. Auth.Active fulfills Auth.Required
|
||||
func (a Auth) Granted() bool {
|
||||
var nothingRequired = true
|
||||
|
||||
// first dimension: OR ; at least one is valid
|
||||
for _, required := range a.Required {
|
||||
// empty list
|
||||
if len(required) < 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
nothingRequired = false
|
||||
|
||||
// second dimension: AND ; all required must be fulfilled
|
||||
if a.fulfills(required) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return nothingRequired
|
||||
}
|
||||
|
||||
// returns whether Auth.Active fulfills (contains) all @required roles
|
||||
func (a Auth) fulfills(required []string) bool {
|
||||
for _, requiredRole := range required {
|
||||
var found = false
|
||||
for _, activeRole := range a.Active {
|
||||
if activeRole == requiredRole {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// missing role -> fail
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// all @required are fulfilled
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCombination(t *testing.T) {
|
||||
tcases := []struct {
|
||||
Name string
|
||||
Required [][]string
|
||||
Active []string
|
||||
Granted bool
|
||||
}{
|
||||
{
|
||||
Name: "no requirement none given",
|
||||
Required: [][]string{},
|
||||
Active: []string{},
|
||||
Granted: true,
|
||||
},
|
||||
{
|
||||
Name: "no requirement 1 given",
|
||||
Required: [][]string{},
|
||||
Active: []string{"a"},
|
||||
Granted: true,
|
||||
},
|
||||
{
|
||||
Name: "no requirement some given",
|
||||
Required: [][]string{},
|
||||
Active: []string{"a", "b"},
|
||||
Granted: true,
|
||||
},
|
||||
|
||||
{
|
||||
Name: "1 required none given",
|
||||
Required: [][]string{{"a"}},
|
||||
Active: []string{},
|
||||
Granted: false,
|
||||
},
|
||||
{
|
||||
Name: "1 required fulfilled",
|
||||
Required: [][]string{{"a"}},
|
||||
Active: []string{"a"},
|
||||
Granted: true,
|
||||
},
|
||||
{
|
||||
Name: "1 required mismatch",
|
||||
Required: [][]string{{"a"}},
|
||||
Active: []string{"b"},
|
||||
Granted: false,
|
||||
},
|
||||
{
|
||||
Name: "2 required none gien",
|
||||
Required: [][]string{{"a", "b"}},
|
||||
Active: []string{},
|
||||
Granted: false,
|
||||
},
|
||||
{
|
||||
Name: "2 required other given",
|
||||
Required: [][]string{{"a", "b"}},
|
||||
Active: []string{"c"},
|
||||
Granted: false,
|
||||
},
|
||||
{
|
||||
Name: "2 required one given",
|
||||
Required: [][]string{{"a", "b"}},
|
||||
Active: []string{"a"},
|
||||
Granted: false,
|
||||
},
|
||||
{
|
||||
Name: "2 required fulfilled",
|
||||
Required: [][]string{{"a", "b"}},
|
||||
Active: []string{"a", "b"},
|
||||
Granted: true,
|
||||
},
|
||||
{
|
||||
Name: "2 or 2 required first fulfilled",
|
||||
Required: [][]string{{"a", "b"}, {"c", "d"}},
|
||||
Active: []string{"a", "b"},
|
||||
Granted: true,
|
||||
},
|
||||
{
|
||||
Name: "2 or 2 required second fulfilled",
|
||||
Required: [][]string{{"a", "b"}, {"c", "d"}},
|
||||
Active: []string{"c", "d"},
|
||||
Granted: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tcase := range tcases {
|
||||
t.Run(tcase.Name, func(t *testing.T) {
|
||||
|
||||
auth := Auth{
|
||||
Required: tcase.Required,
|
||||
Active: tcase.Active,
|
||||
}
|
||||
|
||||
// all right
|
||||
if tcase.Granted == auth.Granted() {
|
||||
return
|
||||
}
|
||||
|
||||
if tcase.Granted && !auth.Granted() {
|
||||
t.Fatalf("expected granted authorization")
|
||||
}
|
||||
t.Fatalf("unexpected granted authorization")
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue