diff --git a/api/auth.go b/api/auth.go index cc162b7..343437b 100644 --- a/api/auth.go +++ b/api/auth.go @@ -1,62 +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 -} +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 +} diff --git a/api/auth_test.go b/api/auth_test.go index 969ec35..8d63b42 100644 --- a/api/auth_test.go +++ b/api/auth_test.go @@ -1,114 +1,114 @@ -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: "empty requirements 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") - }) - } -} +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: "empty requirements 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") + }) + } +}