github.com/xmidt-org/webpa-common@v1.11.9/basculechecks/capabilitiesvalidator.go (about) 1 /** 2 * Copyright 2020 Comcast Cable Communications Management, LLC 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 */ 17 18 package basculechecks 19 20 import ( 21 "context" 22 "errors" 23 "fmt" 24 25 "github.com/goph/emperror" 26 "github.com/spf13/cast" 27 "github.com/xmidt-org/bascule" 28 ) 29 30 var ( 31 ErrNoVals = errors.New("expected at least one value") 32 ErrNoAuth = errors.New("couldn't get request info: authorization not found") 33 ErrNoToken = errors.New("no token found in Auth") 34 ErrNoValidCapabilityFound = errors.New("no valid capability for endpoint") 35 ErrNilAttributes = errors.New("nil attributes interface") 36 ErrNoURL = errors.New("invalid URL found in Auth") 37 ) 38 39 const ( 40 CapabilityKey = "capabilities" 41 ) 42 43 var ( 44 partnerKeys = []string{"allowedResources", "allowedPartners"} 45 ) 46 47 func PartnerKeys() []string { 48 return partnerKeys 49 } 50 51 // CapabilityChecker is an object that can determine if a capability provides 52 // authorization to the endpoint. 53 type CapabilityChecker interface { 54 Authorized(string, string, string) bool 55 } 56 57 // CapabilitiesValidator checks the capabilities provided in a 58 // bascule.Authentication object to determine if a request is authorized. It 59 // can also provide a function to be used in authorization middleware that 60 // pulls the Authentication object from a context before checking it. 61 type CapabilitiesValidator struct { 62 Checker CapabilityChecker 63 } 64 65 // CreateValidator creates a function that determines whether or not a 66 // client is authorized to make a request to an endpoint. It uses the 67 // bascule.Authentication from the context to get the information needed by the 68 // CapabilityChecker to determine authorization. 69 func (c CapabilitiesValidator) CreateValidator(errorOut bool) bascule.ValidatorFunc { 70 return func(ctx context.Context, _ bascule.Token) error { 71 auth, ok := bascule.FromContext(ctx) 72 if !ok { 73 if errorOut { 74 return ErrNoAuth 75 } 76 return nil 77 } 78 79 _, err := c.Check(auth, ParsedValues{}) 80 if err != nil && errorOut { 81 return err 82 } 83 84 return nil 85 } 86 } 87 88 // Check takes the needed values out of the given Authentication object in 89 // order to determine if a request is authorized. It determines this through 90 // iterating through each capability and calling the CapabilityChecker. If no 91 // capability authorizes the client for the given endpoint and method, it is 92 // unauthorized. 93 func (c CapabilitiesValidator) Check(auth bascule.Authentication, _ ParsedValues) (string, error) { 94 if auth.Token == nil { 95 return TokenMissingValues, ErrNoToken 96 } 97 vals, reason, err := getCapabilities(auth.Token.Attributes()) 98 if err != nil { 99 return reason, err 100 } 101 102 if auth.Request.URL == nil { 103 return TokenMissingValues, ErrNoURL 104 } 105 reqURL := auth.Request.URL.EscapedPath() 106 method := auth.Request.Method 107 err = c.checkCapabilities(vals, reqURL, method) 108 if err != nil { 109 return NoCapabilitiesMatch, err 110 } 111 return "", nil 112 } 113 114 // checkCapabilities uses a CapabilityChecker to check if each capability 115 // provided is authorized. If an authorized capability is found, no error is 116 // returned. 117 func (c CapabilitiesValidator) checkCapabilities(capabilities []string, reqURL string, method string) error { 118 for _, val := range capabilities { 119 if c.Checker.Authorized(val, reqURL, method) { 120 return nil 121 } 122 } 123 return emperror.With(ErrNoValidCapabilityFound, "capabilitiesFound", capabilities, "urlToMatch", reqURL, "methodToMatch", method) 124 125 } 126 127 // getCapabilities runs some error checks while getting the list of 128 // capabilities from the attributes. 129 func getCapabilities(attributes bascule.Attributes) ([]string, string, error) { 130 if attributes == nil { 131 return []string{}, UndeterminedCapabilities, ErrNilAttributes 132 } 133 134 val, ok := attributes.Get(CapabilityKey) 135 if !ok { 136 return []string{}, UndeterminedCapabilities, fmt.Errorf("couldn't get capabilities using key %v", CapabilityKey) 137 } 138 139 vals, err := cast.ToStringSliceE(val) 140 if err != nil { 141 return []string{}, UndeterminedCapabilities, fmt.Errorf("capabilities \"%v\" not the expected string slice: %v", val, err) 142 } 143 144 if len(vals) == 0 { 145 return []string{}, EmptyCapabilitiesList, ErrNoVals 146 } 147 148 return vals, "", nil 149 150 }