feat: create api.Auth wrapping authorization management

This commit is contained in:
xdrm-brackets 2021-05-18 09:34:01 +02:00
parent e3d24ae1ef
commit 18d809c4ca
No known key found for this signature in database
GPG Key ID: 99F952DD5DE2439E
2 changed files with 170 additions and 0 deletions

62
api/auth.go Normal file
View File

@ -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
}

108
api/auth_test.go Normal file
View File

@ -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")
})
}
}