100 lines
3.1 KiB
Go
100 lines
3.1 KiB
Go
|
package rules
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"sort"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/micro/go-micro/v2/auth"
|
||
|
)
|
||
|
|
||
|
// Verify an account has access to a resource using the rules provided. If the account does not have
|
||
|
// access an error will be returned. If there are no rules provided which match the resource, an error
|
||
|
// will be returned
|
||
|
func Verify(namespace string, rules []*auth.Rule, acc *auth.Account, res *auth.Resource) error {
|
||
|
// ensure the account has the necessary scope. Some rules allow for public access so we don't
|
||
|
// error if the account is nil.
|
||
|
if acc != nil && !acc.HasScope("namespace."+namespace) {
|
||
|
return fmt.Errorf("Missing required scope: %v", "namespace."+namespace)
|
||
|
}
|
||
|
|
||
|
// the rule is only to be applied if the type matches the resource or is catch-all (*)
|
||
|
validTypes := []string{"*", res.Type}
|
||
|
|
||
|
// the rule is only to be applied if the name matches the resource or is catch-all (*)
|
||
|
validNames := []string{"*", res.Name}
|
||
|
|
||
|
// rules can have wildcard excludes on endpoints since this can also be a path for web services,
|
||
|
// e.g. /foo/* would include /foo/bar. We also want to check for wildcards and the exact endpoint
|
||
|
validEndpoints := []string{"*", res.Endpoint}
|
||
|
if comps := strings.Split(res.Endpoint, "/"); len(comps) > 1 {
|
||
|
for i := 1; i < len(comps); i++ {
|
||
|
wildcard := fmt.Sprintf("%v/*", strings.Join(comps[0:i], "/"))
|
||
|
validEndpoints = append(validEndpoints, wildcard)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// filter the rules to the ones which match the criteria above
|
||
|
filteredRules := make([]*auth.Rule, 0)
|
||
|
for _, rule := range rules {
|
||
|
if !include(validTypes, rule.Resource.Type) {
|
||
|
continue
|
||
|
}
|
||
|
if !include(validNames, rule.Resource.Name) {
|
||
|
continue
|
||
|
}
|
||
|
if !include(validEndpoints, rule.Resource.Endpoint) {
|
||
|
continue
|
||
|
}
|
||
|
filteredRules = append(filteredRules, rule)
|
||
|
}
|
||
|
|
||
|
// sort the filtered rules by priority, highest to lowest
|
||
|
sort.SliceStable(filteredRules, func(i, j int) bool {
|
||
|
return filteredRules[i].Priority > filteredRules[j].Priority
|
||
|
})
|
||
|
|
||
|
// loop through the rules and check for a rule which applies to this account
|
||
|
for _, rule := range filteredRules {
|
||
|
// a blank role indicates the rule applies to everyone, even nil accounts
|
||
|
if rule.Role == "" && rule.Access == auth.AccessDenied {
|
||
|
return auth.ErrForbidden
|
||
|
} else if rule.Role == "" && rule.Access == auth.AccessGranted {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// all furter checks require an account
|
||
|
if acc == nil {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// this rule applies to any account
|
||
|
if rule.Role == "*" && rule.Access == auth.AccessDenied {
|
||
|
return auth.ErrForbidden
|
||
|
} else if rule.Role == "" && rule.Access == auth.AccessGranted {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// if the account has the necessary role
|
||
|
if include(acc.Roles, rule.Role) && rule.Access == auth.AccessDenied {
|
||
|
return auth.ErrForbidden
|
||
|
} else if rule.Role == "" && rule.Access == auth.AccessGranted {
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// if no rules matched then return forbidden
|
||
|
return auth.ErrForbidden
|
||
|
}
|
||
|
|
||
|
// include is a helper function which checks to see if the slice contains the value. includes is
|
||
|
// not case sensitive.
|
||
|
func include(slice []string, val string) bool {
|
||
|
for _, s := range slice {
|
||
|
if strings.ToLower(s) == strings.ToLower(val) {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|