github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/util/auth/rules/rules.go (about) 1 // Copyright 2020 Asim Aslam 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // Original source: github.com/micro/go-micro/v3/auth/rules.go 16 17 package rules 18 19 import ( 20 "fmt" 21 "sort" 22 "strings" 23 24 "github.com/tickoalcantara12/micro/v3/service/auth" 25 ) 26 27 // VerifyAccess an account has access to a resource using the rules provided. If the account does not have 28 // access an error will be returned. If there are no rules provided which match the resource, an error 29 // will be returned 30 func VerifyAccess(rules []*auth.Rule, acc *auth.Account, res *auth.Resource, opts ...auth.VerifyOption) error { 31 // the rule is only to be applied if the type matches the resource or is catch-all (*) 32 validTypes := []string{"*", res.Type} 33 34 // the rule is only to be applied if the name matches the resource or is catch-all (*) 35 validNames := []string{"*", res.Name} 36 37 options := auth.VerifyOptions{} 38 for _, o := range opts { 39 o(&options) 40 } 41 42 // rules can have wildcard excludes on endpoints since this can also be a path for web services, 43 // e.g. /foo/* would include /foo/bar. We also want to check for wildcards and the exact endpoint 44 validEndpoints := []string{"*", res.Endpoint} 45 if comps := strings.Split(res.Endpoint, "/"); len(comps) > 1 { 46 for i := 1; i < len(comps)+1; i++ { 47 wildcard := fmt.Sprintf("%v/*", strings.Join(comps[0:i], "/")) 48 validEndpoints = append(validEndpoints, wildcard) 49 } 50 } 51 52 // filter the rules to the ones which match the criteria above 53 filteredRules := make([]*auth.Rule, 0) 54 for _, rule := range rules { 55 if !include(validTypes, rule.Resource.Type) { 56 continue 57 } 58 if !include(validNames, rule.Resource.Name) { 59 continue 60 } 61 if !include(validEndpoints, rule.Resource.Endpoint) { 62 continue 63 } 64 filteredRules = append(filteredRules, rule) 65 } 66 67 // sort the filtered rules by priority, highest to lowest 68 sort.SliceStable(filteredRules, func(i, j int) bool { 69 return filteredRules[i].Priority > filteredRules[j].Priority 70 }) 71 72 // loop through the rules and check for a rule which applies to this account 73 for _, rule := range filteredRules { 74 // a blank scope indicates the rule applies to everyone, even nil accounts 75 if rule.Scope == auth.ScopePublic && rule.Access == auth.AccessDenied { 76 return auth.ErrForbidden 77 } else if rule.Scope == auth.ScopePublic && rule.Access == auth.AccessGranted { 78 return nil 79 } 80 81 // all further checks require an account 82 if acc == nil { 83 continue 84 } 85 86 // TODO should this live here or further up? 87 if rule.Scope != auth.ScopeAnyNamespaceAccount && acc.Issuer != options.Namespace { 88 return auth.ErrForbidden 89 } 90 // TODO what does options.Context do? 91 92 // this rule applies to any account 93 if (rule.Scope == auth.ScopeAccount || rule.Scope == auth.ScopeAnyNamespaceAccount) && rule.Access == auth.AccessDenied { 94 return auth.ErrForbidden 95 } else if (rule.Scope == auth.ScopeAccount || rule.Scope == auth.ScopeAnyNamespaceAccount) && rule.Access == auth.AccessGranted { 96 return nil 97 } 98 99 // if the account has the necessary scope 100 if include(acc.Scopes, rule.Scope) && rule.Access == auth.AccessDenied { 101 return auth.ErrForbidden 102 } else if include(acc.Scopes, rule.Scope) && rule.Access == auth.AccessGranted { 103 return nil 104 } 105 } 106 107 // if no rules matched then return forbidden 108 return auth.ErrForbidden 109 } 110 111 // include is a helper function which checks to see if the slice contains the value. includes is 112 // not case sensitive. 113 func include(slice []string, val string) bool { 114 for _, s := range slice { 115 if strings.ToLower(s) == strings.ToLower(val) { 116 return true 117 } 118 } 119 return false 120 }